ÿØÿà JFIF    ÿÛ „  ( %!1"%)+...383-7(-.+  -%%--------+------------/----------------------------ÿÀ  ¶" ÿÄ     ÿÄ @    !1AQaq‘¡"±ÁÑð2’BRráS‚ñ3b¢CT²Ò #cÿÄ    ÿÄ '   1!AQ2a"‘±#ÿÚ   ? ò(/p‘—–¢·,M|¯G»R£üešT. ôŸEoCFñ‡ 4ÔkåóxR|µÁ¨ÓDà!Ç´µ˜‡p ÍQ‹§!þž^õWsŒy  åÁ§$á«%ºq˜™JyyË0y«^Ϗ -3ÃÏËÈ£Æ\P’ä„s&•‹Üs üô'àWt¹pñCp$bÚF`·7.^)¾!)ÒÙ˜–ÅÏš^h¾ø³·ìe±ŒaeÖ—Í,e=B}Jéߦ$$½—šÙÜCC¯¶è8β–Åkèwx LÉngT±æ¹£Ócœ¿Í/©Éÿ ‰ÚCÒ­mq;Tt¶–}0"zÖPcd1š­¤tˆ„ÐÓâqú[™öUCŠ2޿د­É(¸ßúçšmW,Ò ºf‹Ån™ÄLêÁ8üÁkÁ€L¥ÍuJ1œôrÂSŒÓöt!7EUíïb)AËi¬â-ÙHk8òÁiXYwÅ)O^"ÇØriÛô{»wRUKÙÏÚlÓ¬€‚’שT ] ªb—ÊîKC^×M“xùäñz¬zJ× {4[¹M'IÆeH1LCWÕ]œûº WDæà°OÅ‚¹c²ÞuA–rL`HšpCez‡V’— Ž‚Ë„–ñ­'µR0ÕÖ=À:±G»C\nÆÉ5*¢ †3šhQ4ýÒi$Õ1"Ü]¢p-q3zcQ$ªö¨¥æf›‘»´ÝÚHá„e²JÇ–iÊ:·à¬Øc5Åk»Laª*wi [0Ówk ¦a¦0Õ²ÅÅ‚T¸’³q:Æ<¹ú Ò˜Æ}2VE•Ѐp¡Ô3Úº …h³^LJºçì¥Ánó|˜ÖˆÎˆ+« ¦dø‘Mº–d\ªDðÛ„Ì×LÝ——Ð9Ç|•FèçVm™¨€™3õS–9Äå¢<̉Ìm¢±`·º˜&Z²ž Ë®D«¶»6™‰©­HÈ Àá«h­i³04æ’hxœi3ï¨(ÓL·†‹¶ËpŒ ƒÜÒ? :õdsÀ –3æÓ·Üjâ¬9—]8n¼&]I F£¼…ì–—Œ0v°LǐÅfÛ2TÖ’aµ˜N@ÖBSÏ^à´4-±°b×nÈÈœÆD=KdŒø…ÄÈK,›ÖR§fjü$^ÔÎ!„6^ s:Üun\ôkCÞò÷<—Lúl Í›G¹ÀPá¼Nc¡šÒ…ÙWͳœ±vÊáЄ×9‹P‰cEØ‘™›µŸP<×g1 º©Ø¬wiÁ^l5× I/';Ë+´Z‡ÓÄFÕp[Èð’ HÄ ó 9¬Dl4“é±Ï”V=^Hð;žLädÉa‚°Ë,#/Ž~²BÔÛ QãñQtIeórVB,ÏÂf‹4#Μc‹É3ZIÖ Œ¢¥·üxa‡Ãçè„jŠ[5 Ä!U?,3žÎ×…ðdÓ÷háŠA‰ìÄ4÷ÃÜZÃE{‰®+W\ZÍE[‰»µk»MÝ­`¢©†˜ÃV‹-f¢¡†¢a«eŠ%«Y¨©q%jâd,Ôq`) ¤¦…šˆ ¦E>0Ùb²t–Ÿ  ÷jsςҒKÈcø(é›;k(’#ôŒQ ™C> õ³M9óºÆµ¤çâ;uÀ)5>RÔ¸fÓ~Ȧ—’-$fe«"FâUˆÊwµPÏu w+"åÉó¨6s™Û9(À¨ù©hE°½†n`m~“@©›p!Vkhµ Á1šbEqqÊ@ºèl›2 –Yã%‘‚hÛ{a¶‡ÅÞM²¬çL34®Çl]®‹ŽØŒm'RÜe35æöˆ­Á­ cP9šeÁköo´L³Ì=p8]”‡B¶,šº|ɏdzXˆØk*ÇÚ{#ñ‰pã(€¶‡9ý'šÜ³½¯kƒ†Â’ëSOƒ•Å®H6+a¢µˆ‹Y¨b˜b+XˆÖ!°u!¢"µˆˆn2ˆ Å0Ä`Å Ô®c(ÓÜGº•Ä»Œ âWî%q.áÐÄ÷î%umÍ£+ÜQ,Vn¦-GphU¸¢X­&,Gt ·K¢ÅÅ·¥RÄÊÉbKn N­R2g$VµÕó Qr#–ÓZUÏð0ÎtÙÀœz ëöo“Îk¯n„T°"SÞ+³/…€½"ižãO%Í%&Δâ‘ËØ´IˆÓ°Ë‰ÞM3.™yçÁzit›`4C`k¢¼á!álÄï sÂ[×!¤l¾æo"ƒIÔ'@ªu)3›ô®“@Z à¾] üœ'‡4¸PÞkÁ-qÄH‰ÓÍBd´ÿ —¹°ÄA&ÊUC„öz‚‚RæŠtf¬>˜ÑidšEçÑL÷áý¡V±h§­c¿¥¨ƒ@g¼…Ýö^ÞËc.ž„æ“I[ ª+·zÐÒÚ,½Ð»¶€C›7jk$๖€Ž¾ÅÙpp¶ÍþéΤ ½ØÎŒq ñpæžÅ¥#Øâ49ÃÂK^MYV“,¶Éz_òæ:…;Õ«¦I3âJ£¤û5$7Èxün\â]Êe¯(Ÿ&Ž‹¶2<6Ćf× æ¢ƱqWı[(ÙÂŽç‚T7¨@ÈÉÍß‚ïÙ"&3TYlGށ†)†#5ŠA©^T2ÆÁ†)©†©†¤yGXÁ†©"†)©¼ÅV WS†#©]R–r‹q5ÕbâWþBh¯u+ªÅÄ×ü„nÑ^êkªÁj‰jeœWˆÕÕ`µDµ2Ì+Ä µDµµ5Ô{¤Þ0RF’H÷AÛ<ÊÁ§ ƈæ0Ñ£ê4 öÚµ[¬/1²€â<-¬šö²r×0o×w ´xÐÒË„ g01¬„åEM蛇Ã\6µE›‘fÙO k÷CºnÓ1`iîÒ² áÂ!ÑLÄÿ ³¬\õ*:M=Ç»c‹2[‰ØsÕLò\ŒK L^™üÄN\¶#» Ç\……ßïIœÉ$¸ÎñÍ®ÔHÇ Ûí/´>Li”„†g^$ 7NÍÑÑ2M2ªNg=k½Ð}Ÿl6‡ —JWˆ”ÞéT·+µ¦üÂFè¢Vs6ýÆ4»2+2IÝ9ô9®ÇGÙÇ1+Ä8;*Õ§ž½D*îÑWÚçá3Ma¤·ˆ¥t}š²Ü…Q+Æüµ8HíS”üxGéÁˆ¦Ël„éÈ 5 µÄƒ6äÜ(kIš¯S†fÖ&¸®×v|Äyˆ*禌Éù@7-®Ä[]ÎÖ¿ê`º…=:…žJVnݳvðÖKE¼‚ »mà ÷f®:Ë ™–4rð¡4` ܹçž~‹C=œ…¾Ïj|VDtK.ƒ"9 Ï ­–éXá³6WÒïIL-Ї Ç PKD|\^ manage_account.phpnu[. /** * Manage one payment accounts * * @package core_payment * @copyright 2020 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once(__DIR__ . '/../config.php'); require_once($CFG->libdir . '/adminlib.php'); $id = optional_param('id', 0, PARAM_INT); $delete = optional_param('delete', false, PARAM_BOOL); $restore = optional_param('restore', false, PARAM_BOOL); $pageurl = new moodle_url('/payment/manage_account.php'); admin_externalpage_setup('paymentaccounts', '', [], $pageurl); $enabledplugins = \core\plugininfo\paygw::get_enabled_plugins(); $account = new \core_payment\account($id); require_capability('moodle/payment:manageaccounts', $account->get_context()); if ($delete && !$account->get('archived') && confirm_sesskey()) { \core_payment\helper::delete_payment_account($account); redirect(new moodle_url('/payment/accounts.php')); } if ($restore && $account->get('archived') && confirm_sesskey()) { \core_payment\helper::restore_payment_account($account); redirect(new moodle_url('/payment/accounts.php')); } $PAGE->set_secondary_active_tab('siteadminnode'); $PAGE->set_primary_active_tab('siteadminnode'); if ($id == 0) { $PAGE->navbar->add(get_string('createaccount', 'payment'), $PAGE->url); } else { $PAGE->navbar->add(get_string('editpaymentaccount', 'payment'), $PAGE->url); } $PAGE->set_heading($id ? format_string($account->get('name')) : get_string('createaccount', 'payment')); $form = new \core_payment\form\account($pageurl->out(false), ['persistent' => $account]); if ($form->is_cancelled()) { redirect(new moodle_url('/payment/accounts.php')); } else if ($data = $form->get_data()) { \core_payment\helper::save_payment_account($data); redirect(new moodle_url('/payment/accounts.php')); } echo $OUTPUT->header(); $form->display(); echo $OUTPUT->footer(); PKD|\]iVrQQgateway/paypal/styles.cssnu[.core_payment_gateways_modal .paypal .icon { height: 40px; width: auto; }PKD|\[{{gateway/paypal/db/services.phpnu[. /** * External functions and service definitions for the PayPal payment gateway plugin. * * @package paygw_paypal * @copyright 2020 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $functions = [ 'paygw_paypal_get_config_for_js' => [ 'classname' => 'paygw_paypal\external\get_config_for_js', 'classpath' => '', 'description' => 'Returns the configuration settings to be used in js', 'type' => 'read', 'ajax' => true, ], 'paygw_paypal_create_transaction_complete' => [ 'classname' => 'paygw_paypal\external\transaction_complete', 'classpath' => '', 'description' => 'Takes care of what needs to be done when a PayPal transaction comes back as complete.', 'type' => 'write', 'ajax' => true, 'loginrequired' => false, ], ]; PKD|\ gateway/paypal/db/install.phpnu[. /** * paygw_paypal installer script. * * @package paygw_paypal * @copyright 2020 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ function xmldb_paygw_paypal_install() { global $CFG; // Enable the Paypal payment gateway on installation. It still needs to be configured and enabled for accounts. $order = (!empty($CFG->paygw_plugins_sortorder)) ? explode(',', $CFG->paygw_plugins_sortorder) : []; set_config('paygw_plugins_sortorder', join(',', array_merge($order, ['paypal']))); } PKD|\ݗvvgateway/paypal/db/upgrade.phpnu[. /** * Upgrade script for paygw_paypal. * * @package paygw_paypal * @copyright 2021 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Upgrade the plugin. * * @param int $oldversion the version we are upgrading from * @return bool always true */ function xmldb_paygw_paypal_upgrade(int $oldversion): bool { global $DB; $dbman = $DB->get_manager(); if ($oldversion < 2021052501) { // Define key paymentid (foreign-unique) to be added to paygw_paypal. $table = new xmldb_table('paygw_paypal'); $key = new xmldb_key('paymentid', XMLDB_KEY_FOREIGN_UNIQUE, ['paymentid'], 'payments', ['id']); // Launch add key paymentid. $dbman->add_key($table, $key); // Paypal savepoint reached. upgrade_plugin_savepoint(true, 2021052501, 'paygw', 'paypal'); } // Automatically generated Moodle v4.2.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.3.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.4.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.5.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v5.0.0 release upgrade line. // Put any upgrade step following this. return true; } PKD|\㻠gateway/paypal/db/install.xmlnu[
PKD|\R^ 'gateway/paypal/lang/en/paygw_paypal.phpnu[. /** * Strings for component 'paygw_paypal', language 'en' * * @package paygw_paypal * @copyright 2019 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['amountmismatch'] = 'The amount you attempted to pay does not match the required fee. Your account has not been debited.'; $string['authorising'] = 'Authorising the payment. Please wait...'; $string['brandname'] = 'Brand name'; $string['brandname_help'] = 'An optional label that overrides the business name for the PayPal account on the PayPal site.'; $string['cannotfetchorderdatails'] = 'Could not fetch payment details from PayPal. Your account has not been debited.'; $string['clientid'] = 'Client ID'; $string['clientid_help'] = 'The client ID that PayPal generated for your application.'; $string['environment'] = 'Environment'; $string['environment_help'] = 'You can set this to Sandbox if you are using sandbox accounts (for testing purpose only).'; $string['gatewaydescription'] = 'PayPal is an authorised payment gateway provider for processing credit card transactions.'; $string['gatewayname'] = 'PayPal'; $string['internalerror'] = 'An internal error has occurred. Please contact us.'; $string['live'] = 'Live'; $string['paymentnotcleared'] = 'payment not cleared by PayPal.'; $string['pluginname'] = 'PayPal'; $string['pluginname_desc'] = 'The PayPal plugin allows you to receive payments via PayPal.'; $string['privacy:metadata'] = 'The PayPal plugin does not store any personal data.'; $string['repeatedorder'] = 'This order has already been processed earlier.'; $string['sandbox'] = 'Sandbox'; $string['secret'] = 'Secret'; $string['secret_help'] = 'The secret that PayPal generated for your application.'; PKD|\R7҃҃gateway/paypal/pix/img.svgnu[ PKD|\<Zgateway/paypal/version.phpnu[. /** * Version information * * @package paygw_paypal * @copyright 2019 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'paygw_paypal'; // Full name of the plugin (used for diagnostics). PKD|\y8gateway/paypal/classes/external/transaction_complete.phpnu[. /** * This class contains a list of webservice functions related to the PayPal payment gateway. * * @package paygw_paypal * @copyright 2020 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ declare(strict_types=1); namespace paygw_paypal\external; use core_external\external_api; use core_external\external_value; use core_external\external_function_parameters; use core_payment\helper; use core_payment\helper as payment_helper; use paygw_paypal\paypal_helper; class transaction_complete extends external_api { /** * Returns description of method parameters. * * @return external_function_parameters */ public static function execute_parameters() { return new external_function_parameters([ 'component' => new external_value(PARAM_COMPONENT, 'The component name'), 'paymentarea' => new external_value(PARAM_AREA, 'Payment area in the component'), 'itemid' => new external_value(PARAM_INT, 'The item id in the context of the component area'), 'orderid' => new external_value(PARAM_TEXT, 'The order id coming back from PayPal'), ]); } /** * Perform what needs to be done when a transaction is reported to be complete. * This function does not take cost as a parameter as we cannot rely on any provided value. * * @param string $component Name of the component that the itemid belongs to * @param string $paymentarea * @param int $itemid An internal identifier that is used by the component * @param string $orderid PayPal order ID * @return array */ public static function execute(string $component, string $paymentarea, int $itemid, string $orderid): array { global $USER, $DB; self::validate_parameters(self::execute_parameters(), [ 'component' => $component, 'paymentarea' => $paymentarea, 'itemid' => $itemid, 'orderid' => $orderid, ]); $config = (object)helper::get_gateway_configuration($component, $paymentarea, $itemid, 'paypal'); $sandbox = $config->environment == 'sandbox'; $payable = payment_helper::get_payable($component, $paymentarea, $itemid); $currency = $payable->get_currency(); // Add surcharge if there is any. $surcharge = helper::get_gateway_surcharge('paypal'); $amount = helper::get_rounded_cost($payable->get_amount(), $currency, $surcharge); $paypalhelper = new paypal_helper($config->clientid, $config->secret, $sandbox); $orderdetails = $paypalhelper->get_order_details($orderid); $success = false; $message = ''; if ($orderdetails) { if ($orderdetails['status'] == paypal_helper::ORDER_STATUS_APPROVED && $orderdetails['intent'] == paypal_helper::ORDER_INTENT_CAPTURE) { $item = $orderdetails['purchase_units'][0]; if ($item['amount']['value'] == $amount && $item['amount']['currency_code'] == $currency) { $capture = $paypalhelper->capture_order($orderid); if ($capture && $capture['status'] == paypal_helper::CAPTURE_STATUS_COMPLETED) { $success = true; // Everything is correct. Let's give them what they paid for. try { $paymentid = payment_helper::save_payment($payable->get_account_id(), $component, $paymentarea, $itemid, (int) $USER->id, $amount, $currency, 'paypal'); // Store PayPal extra information. $record = new \stdClass(); $record->paymentid = $paymentid; $record->pp_orderid = $orderid; $DB->insert_record('paygw_paypal', $record); payment_helper::deliver_order($component, $paymentarea, $itemid, $paymentid, (int) $USER->id); } catch (\Exception $e) { debugging('Exception while trying to process payment: ' . $e->getMessage(), DEBUG_DEVELOPER); $success = false; $message = get_string('internalerror', 'paygw_paypal'); } } else { $success = false; $message = get_string('paymentnotcleared', 'paygw_paypal'); } } else { $success = false; $message = get_string('amountmismatch', 'paygw_paypal'); } } else { $success = false; $message = get_string('paymentnotcleared', 'paygw_paypal'); } } else { // Could not capture authorization! $success = false; $message = get_string('cannotfetchorderdatails', 'paygw_paypal'); } return [ 'success' => $success, 'message' => $message, ]; } /** * Returns description of method result value. * * @return external_function_parameters */ public static function execute_returns() { return new external_function_parameters([ 'success' => new external_value(PARAM_BOOL, 'Whether everything was successful or not.'), 'message' => new external_value(PARAM_RAW, 'Message (usually the error message).'), ]); } } PKD|\0 0 5gateway/paypal/classes/external/get_config_for_js.phpnu[. /** * This class contains a list of webservice functions related to the PayPal payment gateway. * * @package paygw_paypal * @copyright 2020 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ declare(strict_types=1); namespace paygw_paypal\external; use core_payment\helper; use core_external\external_api; use core_external\external_function_parameters; use core_external\external_value; use core_external\external_single_structure; class get_config_for_js extends external_api { /** * Returns description of method parameters. * * @return external_function_parameters */ public static function execute_parameters(): external_function_parameters { return new external_function_parameters([ 'component' => new external_value(PARAM_COMPONENT, 'Component'), 'paymentarea' => new external_value(PARAM_AREA, 'Payment area in the component'), 'itemid' => new external_value(PARAM_INT, 'An identifier for payment area in the component'), ]); } /** * Returns the config values required by the PayPal JavaScript SDK. * * @param string $component * @param string $paymentarea * @param int $itemid * @return string[] */ public static function execute(string $component, string $paymentarea, int $itemid): array { self::validate_parameters(self::execute_parameters(), [ 'component' => $component, 'paymentarea' => $paymentarea, 'itemid' => $itemid, ]); $config = helper::get_gateway_configuration($component, $paymentarea, $itemid, 'paypal'); $payable = helper::get_payable($component, $paymentarea, $itemid); $surcharge = helper::get_gateway_surcharge('paypal'); return [ 'clientid' => $config['clientid'], 'brandname' => $config['brandname'], 'cost' => helper::get_rounded_cost($payable->get_amount(), $payable->get_currency(), $surcharge), 'currency' => $payable->get_currency(), ]; } /** * Returns description of method result value. * * @return external_single_structure */ public static function execute_returns(): external_single_structure { return new external_single_structure([ 'clientid' => new external_value(PARAM_TEXT, 'PayPal client ID'), 'brandname' => new external_value(PARAM_TEXT, 'Brand name'), 'cost' => new external_value(PARAM_FLOAT, 'Cost with gateway surcharge'), 'currency' => new external_value(PARAM_TEXT, 'Currency'), ]); } } PKD|\ȭ  +gateway/paypal/classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for paygw_paypal. * * @package paygw_paypal * @category privacy * @copyright 2020 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace paygw_paypal\privacy; use core_payment\privacy\paygw_provider; use core_privacy\local\request\writer; /** * Privacy Subsystem implementation for paygw_paypal. * * @copyright 2020 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider, paygw_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } /** * Export all user data for the specified payment record, and the given context. * * @param \context $context Context * @param array $subcontext The location within the current context that the payment data belongs * @param \stdClass $payment The payment record */ public static function export_payment_data(\context $context, array $subcontext, \stdClass $payment) { global $DB; $subcontext[] = get_string('gatewayname', 'paygw_paypal'); $record = $DB->get_record('paygw_paypal', ['paymentid' => $payment->id]); $data = (object) [ 'orderid' => $record->pp_orderid, ]; writer::with_context($context)->export_data( $subcontext, $data ); } /** * Delete all user data related to the given payments. * * @param string $paymentsql SQL query that selects payment.id field for the payments * @param array $paymentparams Array of parameters for $paymentsql */ public static function delete_data_for_payment_sql(string $paymentsql, array $paymentparams) { global $DB; $DB->delete_records_select('paygw_paypal', "paymentid IN ({$paymentsql})", $paymentparams); } } PKD|\?(gateway/paypal/classes/paypal_helper.phpnu[. /** * Contains helper class to work with PayPal REST API. * * @package core_payment * @copyright 2020 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace paygw_paypal; use curl; defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir . '/filelib.php'); class paypal_helper { /** * @var string The payment was authorized or the authorized payment was captured for the order. */ public const CAPTURE_STATUS_COMPLETED = 'COMPLETED'; /** * @var string The merchant intends to capture payment immediately after the customer makes a payment. */ public const ORDER_INTENT_CAPTURE = 'CAPTURE'; /** * @var string The customer approved the payment. */ public const ORDER_STATUS_APPROVED = 'APPROVED'; /** * @var string The base API URL */ private $baseurl; /** * @var string Client ID */ private $clientid; /** * @var string PayPal App secret */ private $secret; /** * @var string The oath bearer token */ private $token; /** * helper constructor. * * @param string $clientid The client id. * @param string $secret PayPal secret. * @param bool $sandbox Whether we are working with the sandbox environment or not. */ public function __construct(string $clientid, string $secret, bool $sandbox) { $this->clientid = $clientid; $this->secret = $secret; $this->baseurl = $sandbox ? 'https://api.sandbox.paypal.com' : 'https://api.paypal.com'; $this->token = $this->get_token(); } /** * Captures an authorized payment, by ID. * * @param string $authorizationid The PayPal-generated ID for the authorized payment to capture. * @param float $amount The amount to capture. * @param string $currency The currency code for the amount. * @param bool $final Indicates whether this is the final captures against the authorized payment. * @return array|null Formatted API response. */ public function capture_authorization(string $authorizationid, float $amount, string $currency, bool $final = true): ?array { $location = "{$this->baseurl}/v2/payments/authorizations/{$authorizationid}/capture"; $options = [ 'CURLOPT_RETURNTRANSFER' => true, 'CURLOPT_TIMEOUT' => 30, 'CURLOPT_HTTP_VERSION' => CURL_HTTP_VERSION_1_1, 'CURLOPT_SSLVERSION' => CURL_SSLVERSION_TLSv1_2, 'CURLOPT_HTTPHEADER' => [ 'Content-Type: application/json', "Authorization: Bearer {$this->token}", ], ]; $command = [ 'amount' => [ 'value' => (string) $amount, 'currency_code' => $currency, ], 'final_capture' => $final, ]; $command = json_encode($command); $curl = new curl(); $result = $curl->post($location, $command, $options); return json_decode($result, true); } /** * Captures order details from PayPal. * * @param string $orderid The order we want to capture. * @return array|null Formatted API response. */ public function capture_order(string $orderid): ?array { $location = "{$this->baseurl}/v2/checkout/orders/{$orderid}/capture"; $options = [ 'CURLOPT_RETURNTRANSFER' => true, 'CURLOPT_TIMEOUT' => 30, 'CURLOPT_HTTP_VERSION' => CURL_HTTP_VERSION_1_1, 'CURLOPT_SSLVERSION' => CURL_SSLVERSION_TLSv1_2, 'CURLOPT_HTTPHEADER' => [ 'Content-Type: application/json', "Authorization: Bearer {$this->token}", ], ]; $command = '{}'; $curl = new curl(); $result = $curl->post($location, $command, $options); return json_decode($result, true); } public function get_order_details(string $orderid): ?array { $location = "{$this->baseurl}/v2/checkout/orders/{$orderid}"; $options = [ 'CURLOPT_RETURNTRANSFER' => true, 'CURLOPT_TIMEOUT' => 30, 'CURLOPT_HTTP_VERSION' => CURL_HTTP_VERSION_1_1, 'CURLOPT_SSLVERSION' => CURL_SSLVERSION_TLSv1_2, 'CURLOPT_HTTPHEADER' => [ 'Content-Type: application/json', "Authorization: Bearer {$this->token}", ], ]; $curl = new curl(); $result = $curl->get($location, [], $options); return json_decode($result, true); } /** * Request for PayPal REST oath bearer token. * * @return string */ private function get_token(): string { $location = "{$this->baseurl}/v1/oauth2/token"; $options = [ 'CURLOPT_RETURNTRANSFER' => true, 'CURLOPT_TIMEOUT' => 30, 'CURLOPT_HTTP_VERSION' => CURL_HTTP_VERSION_1_1, 'CURLOPT_SSLVERSION' => CURL_SSLVERSION_TLSv1_2, 'CURLOPT_USERPWD' => "{$this->clientid}:{$this->secret}", ]; $command = 'grant_type=client_credentials'; $curl = new curl(); $result = $curl->post($location, $command, $options); $result = json_decode($result, true); return $result['access_token']; } }PKD|\!!"gateway/paypal/classes/gateway.phpnu[. /** * Contains class for PayPal payment gateway. * * @package paygw_paypal * @copyright 2019 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace paygw_paypal; /** * The gateway class for PayPal payment gateway. * * @copyright 2019 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class gateway extends \core_payment\gateway { public static function get_supported_currencies(): array { // See https://developer.paypal.com/docs/api/reference/currency-codes/, // 3-character ISO-4217: https://en.wikipedia.org/wiki/ISO_4217#Active_codes. return [ 'AUD', 'BRL', 'CAD', 'CHF', 'CZK', 'DKK', 'EUR', 'GBP', 'HKD', 'HUF', 'ILS', 'INR', 'JPY', 'MXN', 'MYR', 'NOK', 'NZD', 'PHP', 'PLN', 'RUB', 'SEK', 'SGD', 'THB', 'TRY', 'TWD', 'USD' ]; } /** * Configuration form for the gateway instance * * Use $form->get_mform() to access the \MoodleQuickForm instance * * @param \core_payment\form\account_gateway $form */ public static function add_configuration_to_gateway_form(\core_payment\form\account_gateway $form): void { $mform = $form->get_mform(); $mform->addElement('text', 'brandname', get_string('brandname', 'paygw_paypal')); $mform->setType('brandname', PARAM_TEXT); $mform->addHelpButton('brandname', 'brandname', 'paygw_paypal'); $mform->addElement('text', 'clientid', get_string('clientid', 'paygw_paypal')); $mform->setType('clientid', PARAM_TEXT); $mform->addHelpButton('clientid', 'clientid', 'paygw_paypal'); $mform->addElement('text', 'secret', get_string('secret', 'paygw_paypal')); $mform->setType('secret', PARAM_TEXT); $mform->addHelpButton('secret', 'secret', 'paygw_paypal'); $options = [ 'live' => get_string('live', 'paygw_paypal'), 'sandbox' => get_string('sandbox', 'paygw_paypal'), ]; $mform->addElement('select', 'environment', get_string('environment', 'paygw_paypal'), $options); $mform->addHelpButton('environment', 'environment', 'paygw_paypal'); } /** * Validates the gateway configuration form. * * @param \core_payment\form\account_gateway $form * @param \stdClass $data * @param array $files * @param array $errors form errors (passed by reference) */ public static function validate_gateway_form(\core_payment\form\account_gateway $form, \stdClass $data, array $files, array &$errors): void { if ($data->enabled && (empty($data->brandname) || empty($data->clientid) || empty($data->secret))) { $errors['enabled'] = get_string('gatewaycannotbeenabled', 'payment'); } } } PKD|\ ;gateway/paypal/templates/paypal_button_placeholder.mustachenu[{{! This file is part of Moodle - http://moodle.org/ Moodle is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Moodle is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Moodle. If not, see . }} {{! @template paygw_paypal/paypal_button_placeholder Classes required for JS: * none Data attributes required for JS: * none Context variables required for this template: * none Example context (json): {} }}
PKD|\!(gateway/paypal/amd/src/gateways_modal.jsnu[// This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . /** * This module is responsible for PayPal content in the gateways modal. * * @module paygw_paypal/gateways_modal * @copyright 2020 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import * as Repository from './repository'; import Templates from 'core/templates'; import Truncate from 'core/truncate'; import Modal from 'core/modal'; import ModalEvents from 'core/modal_events'; import {getString} from 'core/str'; /** * Creates and shows a modal that contains a placeholder. * * @returns {Promise} */ const showModalWithPlaceholder = async() => await Modal.create({ body: await Templates.render('paygw_paypal/paypal_button_placeholder', {}), show: true, removeOnClose: true, }); /** * Process the payment. * * @param {string} component Name of the component that the itemId belongs to * @param {string} paymentArea The area of the component that the itemId belongs to * @param {number} itemId An internal identifier that is used by the component * @param {string} description Description of the payment * @returns {Promise} */ export const process = (component, paymentArea, itemId, description) => { return Promise.all([ showModalWithPlaceholder(), Repository.getConfigForJs(component, paymentArea, itemId), ]) .then(([modal, paypalConfig]) => { return Promise.all([ modal, paypalConfig, switchSdk(paypalConfig.clientid, paypalConfig.currency), ]); }) .then(([modal, paypalConfig]) => { // We have to clear the body. The render method in paypal.Buttons will render everything. modal.setBody(''); return new Promise(resolve => { window.paypal.Buttons({ // Set up the transaction. createOrder: function(data, actions) { return actions.order.create({ purchase_units: [{ // eslint-disable-line amount: { currency_code: paypalConfig.currency_code, // eslint-disable-line value: paypalConfig.cost, }, description: Truncate.truncate(description, {length: 127, stripTags: true}), }], application_context: { // eslint-disable-line shipping_preference: 'NO_SHIPPING', // eslint-disable-line brand_name: Truncate.truncate(paypalConfig.brandname, {length: 127, stripTags: true}), // eslint-disable-line }, }); }, // Finalise the transaction. onApprove: function(data) { modal.getRoot().on(ModalEvents.outsideClick, (e) => { // Prevent closing the modal when clicking outside of it. e.preventDefault(); }); modal.setBody(getString('authorising', 'paygw_paypal')); Repository.markTransactionComplete(component, paymentArea, itemId, data.orderID) .then(res => { modal.hide(); return res; }) .then(resolve); } }).render(modal.getBody()[0]); }); }) .then(res => { if (res.success) { return Promise.resolve(res.message); } return Promise.reject(res.message); }); }; /** * Unloads the previously loaded PayPal JavaScript SDK, and loads a new one. * * @param {string} clientId PayPal client ID * @param {string} currency The currency * @returns {Promise} */ const switchSdk = (clientId, currency) => { const sdkUrl = `https://www.paypal.com/sdk/js?client-id=${clientId}¤cy=${currency}`; // Check to see if this file has already been loaded. If so just go straight to the func. if (switchSdk.currentlyloaded === sdkUrl) { return Promise.resolve(); } // PayPal can only work with one currency at the same time. We have to unload the previously loaded script // if it was loaded for a different currency. Weird way indeed, but the only way. // See: https://github.com/paypal/paypal-checkout-components/issues/1180 if (switchSdk.currentlyloaded) { const suspectedScript = document.querySelector(`script[src="${switchSdk.currentlyloaded}"]`); if (suspectedScript) { suspectedScript.parentNode.removeChild(suspectedScript); } } const script = document.createElement('script'); return new Promise(resolve => { if (script.readyState) { script.onreadystatechange = function() { if (this.readyState == 'complete' || this.readyState == 'loaded') { this.onreadystatechange = null; resolve(); } }; } else { script.onload = function() { resolve(); }; } script.setAttribute('src', sdkUrl); document.head.appendChild(script); switchSdk.currentlyloaded = sdkUrl; }); }; /** * Holds the full url of loaded PayPal JavaScript SDK. * * @static * @type {string} */ switchSdk.currentlyloaded = ''; PKD|\8U $gateway/paypal/amd/src/repository.jsnu[// This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . /** * PayPal repository module to encapsulate all of the AJAX requests that can be sent for PayPal. * * @module paygw_paypal/repository * @copyright 2020 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import Ajax from 'core/ajax'; /** * Return the PayPal JavaScript SDK URL. * * @param {string} component Name of the component that the itemId belongs to * @param {string} paymentArea The area of the component that the itemId belongs to * @param {number} itemId An internal identifier that is used by the component * @returns {Promise<{clientid: string, brandname: string, cost: number, currency: string}>} */ export const getConfigForJs = (component, paymentArea, itemId) => { const request = { methodname: 'paygw_paypal_get_config_for_js', args: { component, paymentarea: paymentArea, itemid: itemId, }, }; return Ajax.call([request])[0]; }; /** * Call server to validate and capture payment for order. * * @param {string} component Name of the component that the itemId belongs to * @param {string} paymentArea The area of the component that the itemId belongs to * @param {number} itemId An internal identifier that is used by the component * @param {string} orderId The order id coming back from PayPal * @returns {*} */ export const markTransactionComplete = (component, paymentArea, itemId, orderId) => { const request = { methodname: 'paygw_paypal_create_transaction_complete', args: { component, paymentarea: paymentArea, itemid: itemId, orderid: orderId, }, }; return Ajax.call([request])[0]; }; PKD|\\<<*gateway/paypal/amd/build/repository.min.jsnu[define("paygw_paypal/repository",["exports","core/ajax"],(function(_exports,_ajax){var obj; /** * PayPal repository module to encapsulate all of the AJAX requests that can be sent for PayPal. * * @module paygw_paypal/repository * @copyright 2020 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.markTransactionComplete=_exports.getConfigForJs=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};_exports.getConfigForJs=(component,paymentArea,itemId)=>{const request={methodname:"paygw_paypal_get_config_for_js",args:{component:component,paymentarea:paymentArea,itemid:itemId}};return _ajax.default.call([request])[0]};_exports.markTransactionComplete=(component,paymentArea,itemId,orderId)=>{const request={methodname:"paygw_paypal_create_transaction_complete",args:{component:component,paymentarea:paymentArea,itemid:itemId,orderid:orderId}};return _ajax.default.call([request])[0]}})); //# sourceMappingURL=repository.min.js.mapPKD|\).gateway/paypal/amd/build/gateways_modal.min.jsnu[define("paygw_paypal/gateways_modal",["exports","./repository","core/templates","core/truncate","core/modal","core/modal_events","core/str"],(function(_exports,Repository,_templates,_truncate,_modal,_modal_events,_str){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.process=void 0,Repository=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj} /** * This module is responsible for PayPal content in the gateways modal. * * @module paygw_paypal/gateways_modal * @copyright 2020 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */(Repository),_templates=_interopRequireDefault(_templates),_truncate=_interopRequireDefault(_truncate),_modal=_interopRequireDefault(_modal),_modal_events=_interopRequireDefault(_modal_events);const showModalWithPlaceholder=async()=>await _modal.default.create({body:await _templates.default.render("paygw_paypal/paypal_button_placeholder",{}),show:!0,removeOnClose:!0});_exports.process=(component,paymentArea,itemId,description)=>Promise.all([showModalWithPlaceholder(),Repository.getConfigForJs(component,paymentArea,itemId)]).then((_ref=>{let[modal,paypalConfig]=_ref;return Promise.all([modal,paypalConfig,switchSdk(paypalConfig.clientid,paypalConfig.currency)])})).then((_ref2=>{let[modal,paypalConfig]=_ref2;return modal.setBody(""),new Promise((resolve=>{window.paypal.Buttons({createOrder:function(data,actions){return actions.order.create({purchase_units:[{amount:{currency_code:paypalConfig.currency_code,value:paypalConfig.cost},description:_truncate.default.truncate(description,{length:127,stripTags:!0})}],application_context:{shipping_preference:"NO_SHIPPING",brand_name:_truncate.default.truncate(paypalConfig.brandname,{length:127,stripTags:!0})}})},onApprove:function(data){modal.getRoot().on(_modal_events.default.outsideClick,(e=>{e.preventDefault()})),modal.setBody((0,_str.getString)("authorising","paygw_paypal")),Repository.markTransactionComplete(component,paymentArea,itemId,data.orderID).then((res=>(modal.hide(),res))).then(resolve)}}).render(modal.getBody()[0])}))})).then((res=>res.success?Promise.resolve(res.message):Promise.reject(res.message)));const switchSdk=(clientId,currency)=>{const sdkUrl="https://www.paypal.com/sdk/js?client-id=".concat(clientId,"¤cy=").concat(currency);if(switchSdk.currentlyloaded===sdkUrl)return Promise.resolve();if(switchSdk.currentlyloaded){const suspectedScript=document.querySelector('script[src="'.concat(switchSdk.currentlyloaded,'"]'));suspectedScript&&suspectedScript.parentNode.removeChild(suspectedScript)}const script=document.createElement("script");return new Promise((resolve=>{script.readyState?script.onreadystatechange=function(){"complete"!=this.readyState&&"loaded"!=this.readyState||(this.onreadystatechange=null,resolve())}:script.onload=function(){resolve()},script.setAttribute("src",sdkUrl),document.head.appendChild(script),switchSdk.currentlyloaded=sdkUrl}))};switchSdk.currentlyloaded=""})); //# sourceMappingURL=gateways_modal.min.js.mapPKD|\铊 .gateway/paypal/amd/build/repository.min.js.mapnu[{"version":3,"file":"repository.min.js","sources":["../src/repository.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * PayPal repository module to encapsulate all of the AJAX requests that can be sent for PayPal.\n *\n * @module paygw_paypal/repository\n * @copyright 2020 Shamim Rezaie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\n\n/**\n * Return the PayPal JavaScript SDK URL.\n *\n * @param {string} component Name of the component that the itemId belongs to\n * @param {string} paymentArea The area of the component that the itemId belongs to\n * @param {number} itemId An internal identifier that is used by the component\n * @returns {Promise<{clientid: string, brandname: string, cost: number, currency: string}>}\n */\nexport const getConfigForJs = (component, paymentArea, itemId) => {\n const request = {\n methodname: 'paygw_paypal_get_config_for_js',\n args: {\n component,\n paymentarea: paymentArea,\n itemid: itemId,\n },\n };\n\n return Ajax.call([request])[0];\n};\n\n/**\n * Call server to validate and capture payment for order.\n *\n * @param {string} component Name of the component that the itemId belongs to\n * @param {string} paymentArea The area of the component that the itemId belongs to\n * @param {number} itemId An internal identifier that is used by the component\n * @param {string} orderId The order id coming back from PayPal\n * @returns {*}\n */\nexport const markTransactionComplete = (component, paymentArea, itemId, orderId) => {\n const request = {\n methodname: 'paygw_paypal_create_transaction_complete',\n args: {\n component,\n paymentarea: paymentArea,\n itemid: itemId,\n orderid: orderId,\n },\n };\n\n return Ajax.call([request])[0];\n};\n"],"names":["component","paymentArea","itemId","request","methodname","args","paymentarea","itemid","Ajax","call","orderId","orderid"],"mappings":";;;;;;;yMAiC8B,CAACA,UAAWC,YAAaC,gBAC7CC,QAAU,CACZC,WAAY,iCACZC,KAAM,CACFL,UAAAA,UACAM,YAAaL,YACbM,OAAQL,gBAITM,cAAKC,KAAK,CAACN,UAAU,qCAYO,CAACH,UAAWC,YAAaC,OAAQQ,iBAC9DP,QAAU,CACZC,WAAY,2CACZC,KAAM,CACFL,UAAAA,UACAM,YAAaL,YACbM,OAAQL,OACRS,QAASD,iBAIVF,cAAKC,KAAK,CAACN,UAAU"}PKD|\:7!!2gateway/paypal/amd/build/gateways_modal.min.js.mapnu[{"version":3,"file":"gateways_modal.min.js","sources":["../src/gateways_modal.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * This module is responsible for PayPal content in the gateways modal.\n *\n * @module paygw_paypal/gateways_modal\n * @copyright 2020 Shamim Rezaie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport * as Repository from './repository';\nimport Templates from 'core/templates';\nimport Truncate from 'core/truncate';\nimport Modal from 'core/modal';\nimport ModalEvents from 'core/modal_events';\nimport {getString} from 'core/str';\n\n/**\n * Creates and shows a modal that contains a placeholder.\n *\n * @returns {Promise}\n */\nconst showModalWithPlaceholder = async() => await Modal.create({\n body: await Templates.render('paygw_paypal/paypal_button_placeholder', {}),\n show: true,\n removeOnClose: true,\n});\n\n/**\n * Process the payment.\n *\n * @param {string} component Name of the component that the itemId belongs to\n * @param {string} paymentArea The area of the component that the itemId belongs to\n * @param {number} itemId An internal identifier that is used by the component\n * @param {string} description Description of the payment\n * @returns {Promise}\n */\nexport const process = (component, paymentArea, itemId, description) => {\n return Promise.all([\n showModalWithPlaceholder(),\n Repository.getConfigForJs(component, paymentArea, itemId),\n ])\n .then(([modal, paypalConfig]) => {\n return Promise.all([\n modal,\n paypalConfig,\n switchSdk(paypalConfig.clientid, paypalConfig.currency),\n ]);\n })\n .then(([modal, paypalConfig]) => {\n // We have to clear the body. The render method in paypal.Buttons will render everything.\n modal.setBody('');\n\n return new Promise(resolve => {\n window.paypal.Buttons({\n // Set up the transaction.\n createOrder: function(data, actions) {\n return actions.order.create({\n purchase_units: [{ // eslint-disable-line\n amount: {\n currency_code: paypalConfig.currency_code, // eslint-disable-line\n value: paypalConfig.cost,\n },\n description: Truncate.truncate(description, {length: 127, stripTags: true}),\n }],\n application_context: { // eslint-disable-line\n shipping_preference: 'NO_SHIPPING', // eslint-disable-line\n brand_name: Truncate.truncate(paypalConfig.brandname, {length: 127, stripTags: true}), // eslint-disable-line\n },\n });\n },\n // Finalise the transaction.\n onApprove: function(data) {\n modal.getRoot().on(ModalEvents.outsideClick, (e) => {\n // Prevent closing the modal when clicking outside of it.\n e.preventDefault();\n });\n\n modal.setBody(getString('authorising', 'paygw_paypal'));\n\n Repository.markTransactionComplete(component, paymentArea, itemId, data.orderID)\n .then(res => {\n modal.hide();\n return res;\n })\n .then(resolve);\n }\n }).render(modal.getBody()[0]);\n });\n })\n .then(res => {\n if (res.success) {\n return Promise.resolve(res.message);\n }\n\n return Promise.reject(res.message);\n });\n};\n\n/**\n * Unloads the previously loaded PayPal JavaScript SDK, and loads a new one.\n *\n * @param {string} clientId PayPal client ID\n * @param {string} currency The currency\n * @returns {Promise}\n */\nconst switchSdk = (clientId, currency) => {\n const sdkUrl = `https://www.paypal.com/sdk/js?client-id=${clientId}¤cy=${currency}`;\n\n // Check to see if this file has already been loaded. If so just go straight to the func.\n if (switchSdk.currentlyloaded === sdkUrl) {\n return Promise.resolve();\n }\n\n // PayPal can only work with one currency at the same time. We have to unload the previously loaded script\n // if it was loaded for a different currency. Weird way indeed, but the only way.\n // See: https://github.com/paypal/paypal-checkout-components/issues/1180\n if (switchSdk.currentlyloaded) {\n const suspectedScript = document.querySelector(`script[src=\"${switchSdk.currentlyloaded}\"]`);\n if (suspectedScript) {\n suspectedScript.parentNode.removeChild(suspectedScript);\n }\n }\n\n const script = document.createElement('script');\n\n return new Promise(resolve => {\n if (script.readyState) {\n script.onreadystatechange = function() {\n if (this.readyState == 'complete' || this.readyState == 'loaded') {\n this.onreadystatechange = null;\n resolve();\n }\n };\n } else {\n script.onload = function() {\n resolve();\n };\n }\n\n script.setAttribute('src', sdkUrl);\n document.head.appendChild(script);\n\n switchSdk.currentlyloaded = sdkUrl;\n });\n};\n\n/**\n * Holds the full url of loaded PayPal JavaScript SDK.\n *\n * @static\n * @type {string}\n */\nswitchSdk.currentlyloaded = '';\n"],"names":["showModalWithPlaceholder","async","Modal","create","body","Templates","render","show","removeOnClose","component","paymentArea","itemId","description","Promise","all","Repository","getConfigForJs","then","_ref","modal","paypalConfig","switchSdk","clientid","currency","_ref2","setBody","resolve","window","paypal","Buttons","createOrder","data","actions","order","purchase_units","amount","currency_code","value","cost","Truncate","truncate","length","stripTags","application_context","shipping_preference","brand_name","brandname","onApprove","getRoot","on","ModalEvents","outsideClick","e","preventDefault","markTransactionComplete","orderID","res","hide","getBody","success","message","reject","clientId","sdkUrl","currentlyloaded","suspectedScript","document","querySelector","parentNode","removeChild","script","createElement","readyState","onreadystatechange","this","onload","setAttribute","head","appendChild"],"mappings":";;;;;;;4MAmCMA,yBAA2BC,eAAiBC,eAAMC,OAAO,CAC3DC,WAAYC,mBAAUC,OAAO,yCAA0C,IACvEC,MAAM,EACNC,eAAe,qBAYI,CAACC,UAAWC,YAAaC,OAAQC,cAC7CC,QAAQC,IAAI,CACfd,2BACAe,WAAWC,eAAeP,UAAWC,YAAaC,UAErDM,MAAKC,WAAEC,MAAOC,0BACJP,QAAQC,IAAI,CACfK,MACAC,aACAC,UAAUD,aAAaE,SAAUF,aAAaG,eAGrDN,MAAKO,YAAEL,MAAOC,2BAEXD,MAAMM,QAAQ,IAEP,IAAIZ,SAAQa,UACfC,OAAOC,OAAOC,QAAQ,CAElBC,YAAa,SAASC,KAAMC,gBACjBA,QAAQC,MAAM9B,OAAO,CACxB+B,eAAgB,CAAC,CACbC,OAAQ,CACJC,cAAehB,aAAagB,cAC5BC,MAAOjB,aAAakB,MAExB1B,YAAa2B,kBAASC,SAAS5B,YAAa,CAAC6B,OAAQ,IAAKC,WAAW,MAEzEC,oBAAqB,CACjBC,oBAAqB,cACrBC,WAAYN,kBAASC,SAASpB,aAAa0B,UAAW,CAACL,OAAQ,IAAKC,WAAW,QAK3FK,UAAW,SAAShB,MAChBZ,MAAM6B,UAAUC,GAAGC,sBAAYC,cAAeC,IAE1CA,EAAEC,oBAGNlC,MAAMM,SAAQ,kBAAU,cAAe,iBAEvCV,WAAWuC,wBAAwB7C,UAAWC,YAAaC,OAAQoB,KAAKwB,SACvEtC,MAAKuC,MACFrC,MAAMsC,OACCD,OAEVvC,KAAKS,YAEXpB,OAAOa,MAAMuC,UAAU,UAGjCzC,MAAKuC,KACEA,IAAIG,QACG9C,QAAQa,QAAQ8B,IAAII,SAGxB/C,QAAQgD,OAAOL,IAAII,iBAW5BvC,UAAY,CAACyC,SAAUvC,kBACnBwC,yDAAoDD,8BAAqBvC,aAG3EF,UAAU2C,kBAAoBD,cACvBlD,QAAQa,aAMfL,UAAU2C,gBAAiB,OACrBC,gBAAkBC,SAASC,oCAA6B9C,UAAU2C,uBACpEC,iBACAA,gBAAgBG,WAAWC,YAAYJ,uBAIzCK,OAASJ,SAASK,cAAc,iBAE/B,IAAI1D,SAAQa,UACX4C,OAAOE,WACPF,OAAOG,mBAAqB,WACD,YAAnBC,KAAKF,YAA+C,UAAnBE,KAAKF,kBACjCC,mBAAqB,KAC1B/C,YAIR4C,OAAOK,OAAS,WACZjD,WAIR4C,OAAOM,aAAa,MAAOb,QAC3BG,SAASW,KAAKC,YAAYR,QAE1BjD,UAAU2C,gBAAkBD,WAUpC1C,UAAU2C,gBAAkB"}PKD|\}Rgateway/paypal/settings.phpnu[. /** * Settings for the PayPal payment gateway * * @package paygw_paypal * @copyright 2019 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); if ($ADMIN->fulltree) { $settings->add(new admin_setting_heading('paygw_paypal_settings', '', get_string('pluginname_desc', 'paygw_paypal'))); \core_payment\helper::add_common_gateway_settings($settings, 'paygw_paypal'); } PKD|\뽨qq accounts.phpnu[. /** * Management of payment accounts * * @package core_payment * @copyright 2020 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once(__DIR__ . '/../config.php'); require_once($CFG->libdir . '/adminlib.php'); $showarchived = optional_param('showarchived', false, PARAM_BOOL); admin_externalpage_setup('paymentaccounts'); $PAGE->set_heading(get_string('paymentaccounts', 'payment')); $PAGE->set_primary_active_tab('siteadminnode'); $enabledplugins = \core\plugininfo\paygw::get_enabled_plugins(); echo $OUTPUT->header(); $accounts = \core_payment\helper::get_payment_accounts_to_manage(context_system::instance(), $showarchived); $table = new html_table(); $table->head = [get_string('accountname', 'payment'), get_string('type_paygw_plural', 'plugin'), '']; $table->colclasses = ['', '', 'mdl-right']; $table->data = []; foreach ($accounts as $account) { $gateways = []; $canmanage = has_capability('moodle/payment:manageaccounts', $account->get_context()); foreach ($account->get_gateways() as $gateway) { $status = $gateway->get('enabled') ? $OUTPUT->pix_icon('i/valid', get_string('gatewayenabled', 'payment')) : $OUTPUT->pix_icon('i/invalid', get_string('gatewaydisabled', 'payment')); $gateways[] = $status . ($canmanage ? html_writer::link($gateway->get_edit_url(), $gateway->get_display_name()) : $gateway->get_display_name()); } $name = $account->get_formatted_name(); if (!$account->is_available()) { $name .= ' ' . html_writer::span(get_string('accountnotavailable', 'payment'), 'badge bg-warning text-dark'); } if ($account->get('archived')) { $name .= ' ' . html_writer::span(get_string('accountarchived', 'payment'), 'badge bg-secondary text-dark'); } $menu = new action_menu(); $menu->set_menu_trigger(get_string('edit')); $menu->set_boundary('window'); if ($canmanage) { $menu->add(new action_menu_link_secondary($account->get_edit_url(), null, get_string('edit'))); if (!$account->get('archived')) { $deleteurl = $account->get_edit_url(['delete' => 1, 'sesskey' => sesskey()]); $menu->add(new action_menu_link_secondary($deleteurl, null, get_string('deleteorarchive', 'payment'), [ 'data-modal' => 'confirmation', 'data-modal-type' => 'delete', 'data-modal-content-str' => json_encode(['accountdeleteconfirm', 'payment']), ])); } else { $restoreurl = $account->get_edit_url(['restore' => 1, 'sesskey' => sesskey()]); $menu->add(new action_menu_link_secondary($restoreurl, null, get_string('restoreaccount', 'payment'))); } } $table->data[] = [$name, join(', ', $gateways), $OUTPUT->render($menu)]; } echo html_writer::div(get_string('paymentaccountsexplained', 'payment'), 'pb-2'); if (has_capability('moodle/site:config', context_system::instance())) { // For administrators add a link to "Manage payment gateways" page. $link = html_writer::link(new moodle_url('/admin/settings.php', ['section' => 'managepaymentgateways']), get_string('type_paygwmanage', 'plugin')); $text = get_string('gotomanageplugins', 'payment', $link); echo html_writer::div($text, 'pb-2'); } echo html_writer::div(html_writer::table($table), 'position-relative'); echo html_writer::div(html_writer::link(new moodle_url($PAGE->url, ['showarchived' => !$showarchived]), $showarchived ? get_string('hidearchived', 'payment') : get_string('showarchived', 'payment')), 'mdl-right'); echo $OUTPUT->single_button(new moodle_url('/payment/manage_account.php'), get_string('createaccount', 'payment'), 'get'); echo $OUTPUT->footer(); PKD|\4Wy y classes/account_gateway.phpnu[. /** * Class account_gateway * * @package core_payment * @copyright 2020 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_payment; use core\persistent; /** * Class account_gateway * * @package core_payment * @copyright 2020 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class account_gateway extends persistent { /** * Database table. */ const TABLE = 'payment_gateways'; /** * Return the definition of the properties of this model. * * @return array */ protected static function define_properties(): array { return array( 'accountid' => [ 'type' => PARAM_INT, ], 'gateway' => [ 'type' => PARAM_COMPONENT, ], 'enabled' => [ 'type' => PARAM_BOOL, 'default' => true ], 'config' => [ 'type' => PARAM_RAW, 'optional' => true, 'null' => NULL_ALLOWED, 'default' => null ], ); } /** * Return the gateway name ready for display * * @return string */ public function get_display_name(): string { return get_string('pluginname', 'paygw_' . $this->get('gateway')); } /** * Gateway management url * * @return \moodle_url */ public function get_edit_url(): \moodle_url { $params = $this->get('id') ? ['id' => $this->get('id')] : ['accountid' => $this->get('accountid'), 'gateway' => $this->get('gateway')]; return new \moodle_url('/payment/manage_gateway.php', $params); } /** * Get corresponding account * * @return account */ public function get_account(): account { return new account($this->get('accountid')); } /** * Parse configuration from the json-encoded stored value * * @return array */ public function get_configuration(): array { $config = @json_decode($this->get('config'), true); return ($config && is_array($config)) ? $config : []; } } PKD|\off classes/form/account_gateway.phpnu[. /** * Class account_gateway * * @package core_payment * @copyright 2020 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_payment\form; use core\form\persistent; /** * Class account_gateway * * @package core_payment * @copyright 2020 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class account_gateway extends persistent { /** @var string The persistent class. */ protected static $persistentclass = \core_payment\account_gateway::class; protected static $fieldstoremove = ['accountname', 'gatewayname', 'submitbutton']; /** * Define the form - called by parent constructor */ public function definition() { $mform = $this->_form; $mform->addElement('hidden', 'id'); $mform->addElement('hidden', 'accountid'); $mform->addElement('hidden', 'gateway'); $mform->addElement('static', 'accountname', get_string('accountname', 'payment'), $this->get_gateway_persistent()->get_account()->get_formatted_name()); $mform->addElement('static', 'gatewayname', get_string('type_paygw', 'plugin'), $this->get_gateway_persistent()->get_display_name()); $mform->addElement('advcheckbox', 'enabled', get_string('enable')); /** @var \core_payment\gateway $classname */ $classname = '\paygw_' . $this->get_gateway_persistent()->get('gateway') . '\gateway'; if (class_exists($classname)) { $classname::add_configuration_to_gateway_form($this); } $this->add_action_buttons(); } /** * Form validation * * @param \stdClass $data * @param array $files * @param array $errors */ protected function extra_validation($data, $files, array &$errors) { /** @var \core_payment\gateway $classname */ $classname = '\paygw_' . $this->get_gateway_persistent()->get('gateway') . '\gateway'; if (class_exists($classname)) { $classname::validate_gateway_form($this, $data, $files, $errors); } } /** * Exposes the protected attribute to be accessed by the \core_payment\gateway callback * * @return \MoodleQuickForm */ public function get_mform(): \MoodleQuickForm { return $this->_form; } /** * Exposes the protected attribute to be accessed by the \core_payment\gateway callback * * @return \core_payment\account_gateway */ public function get_gateway_persistent(): \core_payment\account_gateway { return $this->get_persistent(); } /** * Filter out the foreign fields of the persistent. * * This can be overridden to filter out more complex fields. * * @param \stdClass $data The data to filter the fields out of. * @return \stdClass. */ protected function filter_data_for_persistent($data) { $data = parent::filter_data_for_persistent($data); return (object) array_intersect_key((array)$data, \core_payment\account_gateway::properties_definition()); } /** * Overwrite parent method to json encode config * * @return object|\stdClass|null * @throws \coding_exception */ public function get_data() { if (!$data = parent::get_data()) { return $data; } // Everything that is not a property of the account_gateway class is a gateway config. $data = (array)$data; $properties = \core_payment\account_gateway::properties_definition() + ['id' => 1]; $config = array_diff_key($data, $properties, ['timemodified' => 1, 'timecreated' => 1]); $data = array_intersect_key($data, $properties); $data['config'] = json_encode($config); return (object)$data; } /** * Overwrite parent method to json decode config * * @param array|\stdClass $values */ public function set_data($values) { if (($config = isset($values->config) ? @json_decode($values->config, true) : null) && is_array($config)) { $values = (object)((array)$values + $config); } unset($values->config); parent::set_data($values); } } PKD|\NLWo o classes/form/account.phpnu[. /** * Class account * * @package core_payment * @copyright 2020 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_payment\form; use core\form\persistent; /** * Class account * * @package core_payment * @copyright 2020 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class account extends persistent { /** @var string The persistent class. */ protected static $persistentclass = 'core_payment\account'; /** * Define the form - called by parent constructor */ public function definition() { $mform = $this->_form; $mform->addElement('hidden', 'id'); $mform->addElement('hidden', 'contextid'); $mform->addElement('text', 'name', get_string('accountname', 'payment'), 'maxlength="255"'); $mform->addHelpButton('name', 'accountname', 'payment'); $mform->setType('name', PARAM_TEXT); $mform->addRule('name', get_string('required'), 'required', null, 'client'); $mform->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255, 'server'); $mform->addElement('text', 'idnumber', get_string('accountidnumber', 'payment'), 'maxlength="100"'); $mform->addHelpButton('idnumber', 'accountidnumber', 'payment'); $mform->setType('idnumber', PARAM_RAW_TRIMMED); $mform->addRule('idnumber', get_string('maximumchars', '', 100), 'maxlength', 100, 'server'); $mform->addElement('static', 'staticinfo', '', get_string('accountconfignote', 'payment')); $mform->addElement('advcheckbox', 'enabled', get_string('enable')); $this->add_action_buttons(); } } PKD|\ Z|+classes/external/get_available_gateways.phpnu[. /** * This is the external API for this component. * * @package core_payment * @copyright 2019 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_payment\external; use core_payment\helper; use core_external\external_api; use core_external\external_value; use core_external\external_single_structure; use core_external\external_multiple_structure; use core_external\external_function_parameters; class get_available_gateways extends external_api { /** * Returns description of method parameters. * * @return external_function_parameters */ public static function execute_parameters(): external_function_parameters { return new external_function_parameters([ 'component' => new external_value(PARAM_COMPONENT, 'Component'), 'paymentarea' => new external_value(PARAM_AREA, 'Payment area in the component'), 'itemid' => new external_value(PARAM_INT, 'An identifier for payment area in the component') ]); } /** * Returns the list of gateways that can process payments in the given currency. * * @param string $component * @param string $paymentarea * @param int $itemid * @return \stdClass[] */ public static function execute(string $component, string $paymentarea, int $itemid): array { $params = external_api::validate_parameters(self::execute_parameters(), [ 'component' => $component, 'paymentarea' => $paymentarea, 'itemid' => $itemid, ]); $list = []; $gateways = helper::get_available_gateways($params['component'], $params['paymentarea'], $params['itemid']); $payable = helper::get_payable($params['component'], $params['paymentarea'], $params['itemid']); $amount = $payable->get_amount(); $currency = $payable->get_currency(); foreach ($gateways as $gateway) { $surcharge = helper::get_gateway_surcharge($gateway); $list[] = (object)[ 'shortname' => $gateway, 'name' => get_string('gatewayname', 'paygw_' . $gateway), 'description' => get_string('gatewaydescription', 'paygw_' . $gateway), 'surcharge' => $surcharge, 'cost' => helper::get_cost_as_string($amount, $currency, $surcharge), ]; } return $list; } /** * Returns description of method result value. * * @return external_multiple_structure */ public static function execute_returns(): external_multiple_structure { return new external_multiple_structure( new external_single_structure([ 'shortname' => new external_value(PARAM_PLUGIN, 'Name of the plugin'), 'name' => new external_value(PARAM_TEXT, 'Human readable name of the gateway'), 'description' => new external_value(PARAM_RAW, 'description of the gateway'), 'surcharge' => new external_value(PARAM_INT, 'percentage of surcharge when using the gateway'), 'cost' => new external_value(PARAM_TEXT, 'Cost in human-readable form (amount plus surcharge with currency sign)'), ]) ); } } PKD|\%i"classes/privacy/paygw_provider.phpnu[. namespace core_payment\privacy; interface paygw_provider { /** * Export all user data for the specified payment record, and the given context. * * @param \context $context Context * @param array $subcontext The location within the current context that the payment data belongs * @param \stdClass $payment The payment record */ public static function export_payment_data(\context $context, array $subcontext, \stdClass $payment); /** * Delete all user data related to the given payments. * * @param string $paymentsql SQL query that selects payment.id field for the payments * @param array $paymentparams Array of parameters for $paymentsql */ public static function delete_data_for_payment_sql(string $paymentsql, array $paymentparams); } PKD|\ߕ22classes/privacy/provider.phpnu[. /** * Privacy Subsystem implementation for core_payment. * * @package core_payment * @category privacy * @copyright 2020 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_payment\privacy; use core_privacy\local\metadata\collection; use core_privacy\local\request\approved_contextlist; use core_privacy\local\request\approved_userlist; use core_privacy\local\request\contextlist; use core_privacy\local\request\transform; use core_privacy\local\request\userlist; use core_privacy\local\request\writer; use core_payment\helper as payment_helper; /** * Privacy Subsystem implementation for core_payment. * * @copyright 2020 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements // This component has data. // We need to return all payment information where the user is // listed in the payment.userid field. // We may also need to fetch this informtion from individual plugins in some cases. // e.g. to fetch the full and other gateway-specific meta-data. \core_privacy\local\metadata\provider, // This is a subsysytem which provides information to core. \core_privacy\local\request\subsystem\provider, // This is a subsysytem which provides information to plugins. \core_privacy\local\request\subsystem\plugin_provider, // This plugin is capable of determining which users have data within it. \core_privacy\local\request\core_userlist_provider, // This plugin is capable of determining which users have data within it for the plugins it provides data to. \core_privacy\local\request\shared_userlist_provider { /** * Returns meta data about this system. * * @param collection $collection The initialised collection to add items to. * @return collection A listing of user data stored through this system. */ public static function get_metadata(collection $collection): collection { // The 'payments' table contains data about payments. $collection->add_database_table('payments', [ 'userid' => 'privacy:metadata:database:payments:userid', 'amount' => 'privacy:metadata:database:payments:amount', 'currency' => 'privacy:metadata:database:payments:currency', 'gateway' => 'privacy:metadata:database:payments:gateway', 'timecreated' => 'privacy:metadata:database:payments:timecreated', 'timemodified' => 'privacy:metadata:database:payments:timemodified', ], 'privacy:metadata:database:payments'); return $collection; } /** * Get the list of users who have data within a context. * * @param int $userid The user to search. * @return contextlist The contextlist containing the list of contexts used in this plugin. */ public static function get_contexts_for_userid(int $userid): contextlist { global $DB; $contextids = []; $payments = $DB->get_recordset('payments', ['userid' => $userid]); foreach ($payments as $payment) { $contextids[] = \core_privacy\manager::component_class_callback( $payment->component, consumer_provider::class, 'get_contextid_for_payment', [$payment->paymentarea, $payment->itemid] ) ?: SYSCONTEXTID; } $payments->close(); $contextlist = new contextlist(); if (!empty($contextids)) { [$insql, $inparams] = $DB->get_in_or_equal(array_unique($contextids), SQL_PARAMS_NAMED); $contextlist->add_from_sql("SELECT id FROM {context} WHERE id {$insql}", $inparams); } return $contextlist; } /** * Get the list of users who have data within a context. * * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. */ public static function get_users_in_context(userlist $userlist) { global $DB; $providers = static::get_consumer_providers(); foreach ($providers as $provider) { $provider::get_users_in_context($userlist); } // Orphaned payments. $context = $userlist->get_context(); if ($context instanceof \context_system) { [$notinsql, $notinparams] = $DB->get_in_or_equal($providers, SQL_PARAMS_NAMED, 'param', false); $sql = "SELECT p.userid FROM {payments} p WHERE component $notinsql"; $userlist->add_from_sql('userid', $sql, $notinparams); } } /** * Export all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts to export information for. */ public static function export_user_data(approved_contextlist $contextlist) { global $DB; $providers = static::get_consumer_providers(); foreach ($providers as $provider) { $provider::export_user_data($contextlist); } // Orphaned payments. if (in_array(SYSCONTEXTID, $contextlist->get_contextids())) { [$notinsql, $notinparams] = $DB->get_in_or_equal($providers, SQL_PARAMS_NAMED, 'param', false); $params = ['userid' => $contextlist->get_user()->id] + $notinparams; $orphanedpayments = $DB->get_records_sql( "SELECT * FROM {payments} WHERE userid = :userid AND component $notinsql", $params ); foreach ($orphanedpayments as $payment) { static::export_payment_data_for_user_in_context( \context_system::instance(), [''], $payment->userid, $payment->component, $payment->paymentarea, $payment->itemid ); } } } /** * Delete all data for all users in the specified context. * * @param context $context The specific context to delete data for. */ public static function delete_data_for_all_users_in_context(\context $context) { global $DB; $providers = static::get_consumer_providers(); foreach ($providers as $provider) { $provider::delete_data_for_all_users_in_context($context); } // Orphaned payments. if ($context instanceof \context_system) { [$notinsql, $params] = $DB->get_in_or_equal($providers, SQL_PARAMS_NAMED, 'param', false); $paymentsql = "SELECT id FROM {payments} WHERE component $notinsql"; static::delete_data_for_payment_sql($paymentsql, $params); } } /** * Delete all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. */ public static function delete_data_for_user(approved_contextlist $contextlist) { global $DB; $providers = static::get_consumer_providers(); foreach ($providers as $provider) { $provider::delete_data_for_user($contextlist); } // Orphaned payments. if (in_array(SYSCONTEXTID, $contextlist->get_contextids())) { [$notinsql, $notinparams] = $DB->get_in_or_equal($providers, SQL_PARAMS_NAMED, 'param', false); $paymentsql = "SELECT id FROM {payments} WHERE userid = :userid AND component $notinsql"; $paymentparams = ['userid' => $contextlist->get_user()->id] + $notinparams; static::delete_data_for_payment_sql($paymentsql, $paymentparams); } } /** * Delete multiple users within a single context. * * @param approved_userlist $userlist The approved context and user information to delete information for. */ public static function delete_data_for_users(approved_userlist $userlist) { global $DB; $providers = static::get_consumer_providers(); foreach ($providers as $provider) { $provider::delete_data_for_users($userlist); } // Orphaned payments. if ($userlist->get_context() instanceof \context_system) { [$notinsql, $notinparams] = $DB->get_in_or_equal($providers, SQL_PARAMS_NAMED, 'param', false); [$usersql, $userparams] = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED); $paymentsql = "SELECT id FROM {payments} WHERE component $notinsql AND userid $usersql"; $paymentparams = $notinparams + $userparams; static::delete_data_for_payment_sql($paymentsql, $paymentparams); } } /** * Returns the list of plugins that use the payment subsystem and implement the consumer_provider interface. * * @return string[] provider class names */ private static function get_consumer_providers(): array { $providers = []; foreach (array_keys(\core_component::get_plugin_types()) as $plugintype) { $potentialproviders = \core_component::get_plugin_list_with_class($plugintype, 'privacy\provider'); foreach ($potentialproviders as $potentialprovider) { if (is_a($potentialprovider, consumer_provider::class, true)) { $providers[] = $potentialprovider; } } } return $providers; } /** * Export all user data for the specified user, in the specified context. * * @param \context $context The context that the payment belongs to * @param string[] $subpath Sub-path to be used during export * @param int $userid User id * @param string $component Component name * @param string $paymentarea Payment area * @param int $itemid An internal identifier that is used by the component */ public static function export_payment_data_for_user_in_context(\context $context, array $subpath, int $userid, string $component, string $paymentarea, int $itemid) { global $DB; $payments = $DB->get_records('payments', [ 'component' => $component, 'paymentarea' => $paymentarea, 'itemid' => $itemid, 'userid' => $userid, ]); foreach ($payments as $payment) { $data = (object) [ 'userid' => transform::user($payment->userid), 'amount' => payment_helper::get_cost_as_string($payment->amount, $payment->currency), 'timecreated' => transform::datetime($payment->timecreated), 'timemodified' => transform::datetime($payment->timemodified), ]; $subcontext = array_merge( [get_string('payments', 'payment')], $subpath, ['payment-' . $payment->id] ); writer::with_context($context)->export_data( $subcontext, $data ); \core_privacy\manager::component_class_callback( 'paygw_' . $payment->gateway, paygw_provider::class, 'export_payment_data', [$context, $subcontext, $payment] ); } } /** * Delete all user data related to the given payments. * * @param string $paymentsql SQL query that selects payment.id field for the payments * @param array $paymentparams Array of parameters for $paymentsql */ public static function delete_data_for_payment_sql(string $paymentsql, array $paymentparams) { global $DB; \core_privacy\manager::plugintype_class_callback( 'paygw', paygw_provider::class, 'delete_data_for_payment_sql', [$paymentsql, $paymentparams] ); $DB->delete_records_subquery('payments', 'id', 'id', $paymentsql, $paymentparams); } } PKD|\ }  %classes/privacy/consumer_provider.phpnu[. namespace core_payment\privacy; use core_privacy\local\request\approved_contextlist; use core_privacy\local\request\approved_userlist; use core_privacy\local\request\userlist; use context; interface consumer_provider { /** * Return contextid for the provided payment data * * @param string $paymentarea Payment area * @param int $itemid The item id * @return int|null */ public static function get_contextid_for_payment(string $paymentarea, int $itemid): ?int; /** * Get the list of users who have data within a context. * * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. */ public static function get_users_in_context(userlist $userlist); /** * Export all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts to export information for. */ public static function export_user_data(approved_contextlist $contextlist); /** * Delete all data for all users in the specified context. * * @param context $context The specific context to delete data for. */ public static function delete_data_for_all_users_in_context(context $context); /** * Delete all user data for the specified user, in the specified contexts. * * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. */ public static function delete_data_for_user(approved_contextlist $contextlist); /** * Delete multiple users within a single context. * * @param approved_userlist $userlist The approved context and user information to delete information for. */ public static function delete_data_for_users(approved_userlist $userlist); } PKD|\see"classes/local/entities/payable.phpnu[. /** * The payable class. * * @package core_payment * @copyright 2020 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_payment\local\entities; /** * The payable class. * * @copyright 2020 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class payable { private $amount; private $currency; private $accountid; public function __construct(float $amount, string $currency, int $accountid) { $this->amount = $amount; $this->currency = $currency; $this->accountid = $accountid; } /** * Get the amount of the payable cost. * * @return float */ public function get_amount(): float { return $this->amount; } /** * Get the currency of the payable cost. * * @return string */ public function get_currency(): string { return $this->currency; } /** * Get the id of the payment account the cost is payable to. * * @return int */ public function get_account_id(): int { return $this->accountid; } } PKD|\r +classes/local/callback/service_provider.phpnu[. /** * This file contains the \core_payment\local\local\callback\service_provider interface. * * Plugins should implement this if they use payment subsystem. * * @package core_payment * @copyright 2020 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_payment\local\callback; /** * The service_provider interface for plugins to provide callbacks which are needed by the payment subsystem. * * @copyright 2020 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ interface service_provider { /** * Callback function that returns the cost of the given item in the specified payment area, * along with the accountid that payments are paid to. * * @param string $paymentarea Payment area * @param int $itemid An identifier that is known to the plugin * @return \core_payment\local\entities\payable */ public static function get_payable(string $paymentarea, int $itemid): \core_payment\local\entities\payable; /** * Callback function that returns the URL of the page the user should be redirected to in the case of a successful payment. * * @param string $paymentarea Payment area * @param int $itemid An identifier that is known to the plugin * @return \moodle_url */ public static function get_success_url(string $paymentarea, int $itemid): \moodle_url; /** * Callback function that delivers what the user paid for to them. * * @param string $paymentarea Payment area * @param int $itemid An identifier that is known to the plugin * @param int $paymentid payment id as inserted into the 'payments' table, if needed for reference * @param int $userid The userid the order is going to deliver to * * @return bool Whether successful or not */ public static function deliver_order(string $paymentarea, int $itemid, int $paymentid, int $userid): bool; } PKD|\??classes/helper.phpnu[. /** * Contains helper class for the payment subsystem. * * @package core_payment * @copyright 2019 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_payment; use core_payment\event\account_created; use core_payment\event\account_deleted; use core_payment\event\account_updated; /** * Helper class for the payment subsystem. * * @copyright 2019 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class helper { /** * Returns an accumulated list of supported currencies by all payment gateways. * * @return string[] An array of the currency codes in the three-character ISO-4217 format */ public static function get_supported_currencies(): array { $currencies = []; $plugins = \core_plugin_manager::instance()->get_enabled_plugins('paygw'); foreach ($plugins as $plugin) { /** @var \paygw_paypal\gateway $classname */ $classname = '\paygw_' . $plugin . '\gateway'; $currencies = array_merge($currencies, component_class_callback($classname, 'get_supported_currencies', [], [])); } $currencies = array_unique($currencies); return $currencies; } /** * Returns the list of gateways that can process payments in the given currency. * * @param string $component Name of the component that the paymentarea and itemid belong to * @param string $paymentarea Payment area * @param int $itemid An identifier that is known to the component * @return string[] */ public static function get_available_gateways(string $component, string $paymentarea, int $itemid): array { $gateways = []; $payable = static::get_payable($component, $paymentarea, $itemid); $account = new account($payable->get_account_id()); if (!$account->get('id') || !$account->get('enabled')) { return $gateways; } $currency = $payable->get_currency(); foreach ($account->get_gateways() as $plugin => $gateway) { if (!$gateway->get('enabled')) { continue; } /** @var gateway $classname */ $classname = '\paygw_' . $plugin . '\gateway'; $currencies = component_class_callback($classname, 'get_supported_currencies', [], []); if (in_array($currency, $currencies)) { $gateways[] = $plugin; } } return $gateways; } /** * Rounds the cost based on the currency fractional digits, can also apply surcharge * * @param float $amount amount in the currency units * @param string $currency currency, used for calculating the number of fractional digits * @param float $surcharge surcharge in percents * @return float */ public static function get_rounded_cost(float $amount, string $currency, float $surcharge = 0): float { $amount = $amount * (100 + $surcharge) / 100; $locale = get_string('localecldr', 'langconfig'); $fmt = \NumberFormatter::create($locale, \NumberFormatter::CURRENCY); $localisedcost = numfmt_format_currency($fmt, $amount, $currency); return numfmt_parse_currency($fmt, $localisedcost, $currency); } /** * Returns human-readable amount with correct number of fractional digits and currency indicator, can also apply surcharge * * @param float $amount amount in the currency units * @param string $currency The currency * @param float $surcharge surcharge in percents * @return string */ public static function get_cost_as_string(float $amount, string $currency, float $surcharge = 0): string { $amount = $amount * (100 + $surcharge) / 100; $locale = get_string('localecldr', 'langconfig'); $fmt = \NumberFormatter::create($locale, \NumberFormatter::CURRENCY); $localisedcost = numfmt_format_currency($fmt, $amount, $currency); return $localisedcost; } /** * Returns the percentage of surcharge that is applied when using a gateway * * @param string $gateway Name of the gateway * @return float */ public static function get_gateway_surcharge(string $gateway): float { return (float)get_config('paygw_' . $gateway, 'surcharge'); } /** * Returns the attributes to place on a pay button. * * @param string $component Name of the component that the paymentarea and itemid belong to * @param string $paymentarea Payment area * @param int $itemid An internal identifier that is used by the component * @param string $description Description of the payment * @return array */ public static function gateways_modal_link_params(string $component, string $paymentarea, int $itemid, string $description): array { $payable = static::get_payable($component, $paymentarea, $itemid); $successurl = static::get_success_url($component, $paymentarea, $itemid); return [ 'id' => 'gateways-modal-trigger', 'role' => 'button', 'data-action' => 'core_payment/triggerPayment', 'data-component' => $component, 'data-paymentarea' => $paymentarea, 'data-itemid' => $itemid, 'data-cost' => static::get_cost_as_string($payable->get_amount(), $payable->get_currency()), 'data-description' => $description, 'data-successurl' => $successurl->out(false), ]; } /** * Get the name of the service provider class * * @param string $component The component * @return string * @throws \coding_exception */ private static function get_service_provider_classname(string $component) { $providerclass = "$component\\payment\\service_provider"; if (class_exists($providerclass)) { $rc = new \ReflectionClass($providerclass); if ($rc->implementsInterface(local\callback\service_provider::class)) { return $providerclass; } } throw new \coding_exception("$component does not have an eligible implementation of payment service_provider."); } /** * Asks the payable from the related component. * * @param string $component Name of the component that the paymentarea and itemid belong to * @param string $paymentarea Payment area * @param int $itemid An internal identifier that is used by the component * @return local\entities\payable */ public static function get_payable(string $component, string $paymentarea, int $itemid): local\entities\payable { $providerclass = static::get_service_provider_classname($component); return component_class_callback($providerclass, 'get_payable', [$paymentarea, $itemid]); } /** * Fetches the URL of the page the user should be redirected to from the related component * * @param string $component Name of the component that the paymentarea and itemid belong to * @param string $paymentarea Payment area * @param int $itemid An identifier that is known to the component * @return \moodle_url */ public static function get_success_url(string $component, string $paymentarea, int $itemid): \moodle_url { $providerclass = static::get_service_provider_classname($component); return component_class_callback($providerclass, 'get_success_url', [$paymentarea, $itemid]); } /** * Returns the gateway configuration for given component and gateway * * @param string $component Name of the component that the paymentarea and itemid belong to * @param string $paymentarea Payment area * @param int $itemid An identifier that is known to the component * @param string $gatewayname The gateway name * @return array * @throws \moodle_exception */ public static function get_gateway_configuration(string $component, string $paymentarea, int $itemid, string $gatewayname): array { $payable = self::get_payable($component, $paymentarea, $itemid); $gateway = null; $account = new account($payable->get_account_id()); if ($account && $account->get('enabled')) { $gateway = $account->get_gateways()[$gatewayname] ?? null; } if (!$gateway) { throw new \moodle_exception('gatewaynotfound', 'payment'); } return $gateway->get_configuration(); } /** * Delivers what the user paid for. * * @uses \core_payment\local\callback\service_provider::deliver_order() * * @param string $component Name of the component that the paymentarea and itemid belong to * @param string $paymentarea Payment area * @param int $itemid An internal identifier that is used by the component * @param int $paymentid payment id as inserted into the 'payments' table, if needed for reference * @param int $userid The userid the order is going to deliver to * @return bool Whether successful or not */ public static function deliver_order(string $component, string $paymentarea, int $itemid, int $paymentid, int $userid): bool { $providerclass = static::get_service_provider_classname($component); $result = component_class_callback($providerclass, 'deliver_order', [$paymentarea, $itemid, $paymentid, $userid]); return $result; } /** * Stores essential information about the payment and returns the "id" field of the payment record in DB. * Each payment gateway may then store the additional information their way. * * @param int $accountid Account id * @param string $component Name of the component that the paymentarea and itemid belong to * @param string $paymentarea Payment area * @param int $itemid An internal identifier that is used by the component * @param int $userid Id of the user who is paying * @param float $amount Amount of payment * @param string $currency Currency of payment * @param string $gateway The gateway that is used for the payment * @return int */ public static function save_payment(int $accountid, string $component, string $paymentarea, int $itemid, int $userid, float $amount, string $currency, string $gateway): int { global $DB; $record = new \stdClass(); $record->component = $component; $record->paymentarea = $paymentarea; $record->itemid = $itemid; $record->userid = $userid; $record->amount = $amount; $record->currency = $currency; $record->gateway = $gateway; $record->accountid = $accountid; $record->timecreated = $record->timemodified = time(); $id = $DB->insert_record('payments', $record); return $id; } /** * This functions adds the settings that are common for all payment gateways. * * @param \admin_settingpage $settings The settings object * @param string $gateway The gateway name prefixed with paygw_ */ public static function add_common_gateway_settings(\admin_settingpage $settings, string $gateway): void { $settings->add(new \admin_setting_configtext($gateway . '/surcharge', get_string('surcharge', 'core_payment'), get_string('surcharge_desc', 'core_payment'), 0, PARAM_INT)); } /** * Save a new or edited payment account (used in management interface) * * @param \stdClass $data * @return account */ public static function save_payment_account(\stdClass $data): account { if (empty($data->id)) { $account = new account(0, $data); $account->save(); account_created::create_from_account($account)->trigger(); } else { $account = new account($data->id); $account->from_record($data); $account->save(); account_updated::create_from_account($account)->trigger(); } return $account; } /** * Delete a payment account (used in management interface) * * @param account $account */ public static function delete_payment_account(account $account): void { global $DB; if ($DB->record_exists('payments', ['accountid' => $account->get('id')])) { $account->set('archived', 1); $account->save(); account_updated::create_from_account($account, ['archived' => 1])->trigger(); return; } foreach ($account->get_gateways(false) as $gateway) { if ($gateway->get('id')) { $gateway->delete(); } } $event = account_deleted::create_from_account($account); $account->delete(); $event->trigger(); } /** * Restore archived payment account (used in management interface) * * @param account $account */ public static function restore_payment_account(account $account): void { $account->set('archived', 0); $account->save(); account_updated::create_from_account($account, ['restored' => 1])->trigger(); } /** * Save a payment gateway linked to an existing account (used in management interface) * * @param \stdClass $data * @return account_gateway */ public static function save_payment_gateway(\stdClass $data): account_gateway { if (empty($data->id)) { $records = account_gateway::get_records(['accountid' => $data->accountid, 'gateway' => $data->gateway]); if ($records) { $gateway = reset($records); } else { $gateway = new account_gateway(0, $data); } } else { $gateway = new account_gateway($data->id); } unset($data->accountid, $data->gateway, $data->id); $gateway->from_record($data); $account = $gateway->get_account(); $gateway->save(); account_updated::create_from_account($account)->trigger(); return $gateway; } /** * Returns the list of payment accounts in the given context (used in management interface) * * @param \context $context * @return account[] */ public static function get_payment_accounts_to_manage(\context $context, bool $showarchived = false): array { $records = account::get_records(['contextid' => $context->id] + ($showarchived ? [] : ['archived' => 0])); \core_collator::asort_objects_by_method($records, 'get_formatted_name'); return $records; } /** * Get list of accounts available in the given context * * @param \context $context * @return array */ public static function get_payment_accounts_menu(\context $context): array { global $DB; [$sql, $params] = $DB->get_in_or_equal($context->get_parent_context_ids(true)); $accounts = array_filter(account::get_records_select('contextid '.$sql, $params), function($account) { return $account->is_available() && !$account->get('archived'); }); return array_map(function($account) { return $account->get_formatted_name(); }, $accounts); } } PKD|\x$y y !classes/event/account_deleted.phpnu[. namespace core_payment\event; use core\event\base; use core_payment\account; /** * Class account_deleted * * @package core_payment * @copyright 2020 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Class account_deleted * * @package core_payment * @copyright 2020 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class account_deleted extends base { /** * Initialise event parameters. */ protected function init() { $this->data['objecttable'] = 'payment_accounts'; $this->data['crud'] = 'd'; $this->data['edulevel'] = self::LEVEL_OTHER; } /** * Create an instance of the event and add a record snapshot * * @param account $account * @return base * @throws \coding_exception */ public static function create_from_account(account $account) { $eventparams = [ 'objectid' => $account->get('id'), 'context' => $account->get_context(), 'other' => ['name' => $account->get('name')] ]; $event = self::create($eventparams); $event->add_record_snapshot($event->objecttable, $account->to_record()); return $event; } /** * Returns localised event name. * * @return string */ public static function get_name() { return get_string('eventaccountdeleted', 'payment'); } /** * Returns non-localised event description with id's for admin use only. * * @return string */ public function get_description() { $name = s($this->other['name']); return "The user with id '$this->userid' deleted payment account with id '$this->objectid' and the name '{$name}'."; } /** * Returns relevant URL. * * @return \moodle_url */ public function get_url() { return new \moodle_url('/payment/accounts.php'); } } PKD|\; (y y !classes/event/account_created.phpnu[. namespace core_payment\event; use core\event\base; use core_payment\account; /** * Class account_created * * @package core_payment * @copyright 2020 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Class account_created * * @package core_payment * @copyright 2020 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class account_created extends base { /** * Initialise event parameters. */ protected function init() { $this->data['objecttable'] = 'payment_accounts'; $this->data['crud'] = 'c'; $this->data['edulevel'] = self::LEVEL_OTHER; } /** * Create an instance of the event and add a record snapshot * * @param account $account * @return base * @throws \coding_exception */ public static function create_from_account(account $account) { $eventparams = [ 'objectid' => $account->get('id'), 'context' => $account->get_context(), 'other' => ['name' => $account->get('name')] ]; $event = self::create($eventparams); $event->add_record_snapshot($event->objecttable, $account->to_record()); return $event; } /** * Returns localised event name. * * @return string */ public static function get_name() { return get_string('eventaccountcreated', 'payment'); } /** * Returns non-localised event description with id's for admin use only. * * @return string */ public function get_description() { $name = s($this->other['name']); return "The user with id '$this->userid' created payment account with id '$this->objectid' and the name '{$name}'."; } /** * Returns relevant URL. * * @return \moodle_url */ public function get_url() { return new \moodle_url('/payment/accounts.php'); } } PKD|\~Xtn n !classes/event/account_updated.phpnu[. namespace core_payment\event; use core\event\base; use core_payment\account; /** * Class account_updated * * @package core_payment * @copyright 2020 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Class account_updated * * @package core_payment * @copyright 2020 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class account_updated extends base { /** * Initialise event parameters. */ protected function init() { $this->data['objecttable'] = 'payment_accounts'; $this->data['crud'] = 'u'; $this->data['edulevel'] = self::LEVEL_OTHER; } /** * Create an instance of the event and add a record snapshot * * @param account $account * @param array $other * @return base */ public static function create_from_account(account $account, array $other = []) { $eventparams = [ 'objectid' => $account->get('id'), 'context' => $account->get_context(), 'other' => ['name' => $account->get('name')] + $other ]; $event = self::create($eventparams); $event->add_record_snapshot($event->objecttable, $account->to_record()); return $event; } /** * Returns localised event name. * * @return string */ public static function get_name() { return get_string('eventaccountupdated', 'payment'); } /** * Returns non-localised event description with id's for admin use only. * * @return string */ public function get_description() { $name = s($this->other['name']); if (!empty($this->other['archived'])) { $verb = 'archived'; } else if (!empty($this->other['restored'])) { $verb = 'restored'; } else { $verb = 'updated'; } return "The user with id '$this->userid' $verb payment account with id '$this->objectid' and the name '{$name}'."; } /** * Returns relevant URL. * * @return \moodle_url */ public function get_url() { return new \moodle_url('/payment/accounts.php'); } } PKD|\ OOclasses/account.phpnu[. /** * Class account * * @package core_payment * @copyright 2020 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_payment; use core\persistent; /** * Class account * * @package core_payment * @copyright 2020 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class account extends persistent { /** * Database table. */ const TABLE = 'payment_accounts'; /** @var array */ protected $gateways; /** * Return the definition of the properties of this model. * * @return array */ protected static function define_properties(): array { return array( 'name' => [ 'type' => PARAM_TEXT, ], 'idnumber' => [ 'type' => PARAM_RAW_TRIMMED, ], 'contextid' => [ 'type' => PARAM_INT, 'default' => function() { return \context_system::instance()->id; } ], 'enabled' => [ 'type' => PARAM_BOOL, 'default' => true ], 'archived' => [ 'type' => PARAM_BOOL, 'default' => false ], ); } /** * Account context * * @return \context * @throws \coding_exception */ public function get_context(): \context { return \context::instance_by_id($this->get('contextid')); } /** * Account name ready for display * * @return string * @throws \coding_exception */ public function get_formatted_name(): string { return format_string($this->get('name'), true, ['context' => $this->get_context(), 'escape' => false]); } /** * Manage account url * * @param array $extraparams * @return \moodle_url * @throws \coding_exception * @throws \moodle_exception */ public function get_edit_url(array $extraparams = []): \moodle_url { return new \moodle_url('/payment/manage_account.php', ($this->get('id') ? ['id' => $this->get('id')] : []) + $extraparams); } /** * List of gateways configured (or possible) for this account * * @param bool $enabledpluginsonly only return payment plugins that are enabled * @return account_gateway[] * @throws \coding_exception */ public function get_gateways(bool $enabledpluginsonly = true): array { $id = $this->get('id'); if (!$id) { return []; } if ($this->gateways === null) { \core_component::get_plugin_list('paygw'); $this->gateways = []; foreach (\core_component::get_plugin_list('paygw') as $gatewayname => $unused) { $gateway = account_gateway::get_record(['accountid' => $id, 'gateway' => $gatewayname]); if (!$gateway) { $gateway = new account_gateway(0, (object)['accountid' => $id, 'gateway' => $gatewayname, 'enabled' => false, 'config' => null]); } $this->gateways[$gatewayname] = $gateway; } } if ($enabledpluginsonly) { $enabledplugins = \core\plugininfo\paygw::get_enabled_plugins(); return array_intersect_key($this->gateways, $enabledplugins); } return $this->gateways; } /** * Is this account available (used in management interface) * * @return bool * @throws \coding_exception */ public function is_available(): bool { if (!$this->get('id') || !$this->get('enabled')) { return false; } foreach ($this->get_gateways() as $gateway) { if ($gateway->get('id') && $gateway->get('enabled')) { return true; } } return false; } } PKD|\ցN N classes/gateway.phpnu[. /** * Contains base class for payment gateways. * * @package core_payment * @copyright 2019 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_payment; /** * Base class for payment gateways. * * @copyright 2019 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class gateway { /** * Returns the list of currencies that the payment gateway supports. * * @return string[] An array of the currency codes in the three-character ISO-4217 format */ abstract public static function get_supported_currencies(): array; /** * Configuration form for the gateway instance * * Use $form->get_mform() to access the \MoodleQuickForm instance * * @param \core_payment\form\account_gateway $form */ abstract public static function add_configuration_to_gateway_form(\core_payment\form\account_gateway $form): void; /** * Validates the gateway configuration form. * * Needs to be overridden to make sure the incomplete configuration can not be enabled. * * @param \core_payment\form\account_gateway $form * @param \stdClass $data * @param array $files * @param array $errors form errors (passed by reference) */ public static function validate_gateway_form(\core_payment\form\account_gateway $form, \stdClass $data, array $files, array &$errors): void { if ($data->enabled) { $errors['enabled'] = get_string('gatewaycannotbeenabled', 'payment'); } } } PKD|\e!templates/modal_gateways.mustachenu[{{! This file is part of Moodle - http://moodle.org/ Moodle is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Moodle is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Moodle. If not, see . }} {{! @template core_payment/modal_gateways Moodle modal template with save and cancel buttons. The purpose of this template is to render a modal. Classes required for JS: * none Data attributes required for JS: * none Context variables required for this template: * title A cleaned string (use clean_text()) to display. * body HTML content for the boday Example context (json): { "title": "Example gateways modal", "body": "Some example content for the body" } }} {{< core/modal }} {{$footer}} {{/footer}} {{/ core/modal }} PKD|\9templates/gateway.mustachenu[{{! This file is part of Moodle - http://moodle.org/ Moodle is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Moodle is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Moodle. If not, see . }} {{! @template core_payment/gateway This template will render the gateway option in the gateway selector modal. Classes required for JS: * none Data attributes required for JS: * none Context variables required for this template: * shortname * name * description * surcharge * image Example context (json): { "shortname": "paypal", "name": "PayPal", "description": "A description for PayPal.", "surcharge": "3" } }}
PKD|\!templates/gateways_modal.mustachenu[{{! This file is part of Moodle - http://moodle.org/ Moodle is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Moodle is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Moodle. If not, see . }} {{! @template core_payment/gateways_modal This template will render the gateway selector modal. Classes required for JS: * none Data attributes required for JS: * none Context variables required for this template: * none Example context (json): {} }}
{{> core_payment/gateways_placeholder }}
PKD|\}'templates/gateways_placeholder.mustachenu[{{! This file is part of Moodle - http://moodle.org/ Moodle is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Moodle is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Moodle. If not, see . }} {{! @template core_payment/gateways_placeholder Classes required for JS: * none Data attributes required for JS: * none Context variables required for this template: * none Example context (json): {} }}
PKD|\kACKKtemplates/gateways.mustachenu[{{! This file is part of Moodle - http://moodle.org/ Moodle is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Moodle is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Moodle. If not, see . }} {{! @template core_payment/gateways This template will render the list of gateways in the gateway selector modal. Classes required for JS: * none Data attributes required for JS: * none Context variables required for this template: * gateways Example context (json): { "gateways" : [ { "shortname": "paypal", "name": "PayPal", "description": "A description for PayPal.", "image": "../../../pix/help.svg" }, { "shortname": "stripe", "name": "Stripe", "description": "A description for Stripe.", "image": "../../../pix/help.svg" } ] } }} {{#gateways}} {{> core_payment/gateway }} {{/gateways}} {{^gateways}}

{{#str}}nogateway, core_payment{{/str}}

{{/gateways}}PKD|\ templates/fee_breakdown.mustachenu[{{! This file is part of Moodle - http://moodle.org/ Moodle is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Moodle is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Moodle. If not, see . }} {{! @template core_payment/fee_breakdown Classes required for JS: * none Data attributes required for JS: * none Context variables required for this template: * surchargestr Example context (json): { "surchargestr": "Cost: $10.00" } }}
{{surchargestr}}
PKD|\ʡ~^^ upgrade.txtnu[=== 4.5 Onwards === This file has been replaced by UPGRADING.md. See MDL-81125 for further information. === This files describes API changes in /payment/*, information provided here is intended especially for developers. === 3.11 === * Service provider plugins using the payment subsystem are now required to implement the get_success_url method. PKD|\aYNI I amd/src/modal_gateways.jsnu[// This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . /** * Contain the logic for the gateways modal: A modal with proceed and cancel buttons. * * @module core_payment/modal_gateways * @copyright 2020 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import $ from 'jquery'; import CustomEvents from 'core/custom_interaction_events'; import Modal from 'core/modal'; import ModalEvents from 'core/modal_events'; import PaymentEvents from 'core_payment/events'; const SELECTORS = { PROCEED_BUTTON: '[data-action="proceed"]', CANCEL_BUTTON: '[data-action="cancel"]', }; export default class ModalGateways extends Modal { static TYPE = 'core_payment-modal_gateways'; static TEMPLATE = 'core_payment/modal_gateways'; /** * Constructor for the Modal. * * @param {object} root The root jQuery element for the modal */ constructor(root) { super(root); } /** * Set up all of the event handling for the modal. * * @method registerEventListeners */ registerEventListeners() { // Apply parent event listeners. super.registerEventListeners(); this.getModal().on(CustomEvents.events.activate, SELECTORS.PROCEED_BUTTON, (e, data) => { var proceedEvent = $.Event(PaymentEvents.proceed); this.getRoot().trigger(proceedEvent, this); if (!proceedEvent.isDefaultPrevented()) { this.hide(); data.originalEvent.preventDefault(); } }); this.getModal().on(CustomEvents.events.activate, SELECTORS.CANCEL_BUTTON, (e, data) => { var cancelEvent = $.Event(ModalEvents.cancel); this.getRoot().trigger(cancelEvent, this); if (!cancelEvent.isDefaultPrevented()) { this.hide(); data.originalEvent.preventDefault(); } }); } } ModalGateways.registerModalType(); PKD|\>;4amd/src/events.jsnu[// This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . /** * Contain the events the payment modal can fire. * * @module core_payment/events * @copyright 2020 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ export default { proceed: 'core_payment-modal_gateways:proceed', }; PKD|\!;;amd/src/gateways_modal.jsnu[// This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . /** * Contain the logic for the gateways modal. * * @module core_payment/gateways_modal * @copyright 2019 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import Templates from 'core/templates'; import {getString} from 'core/str'; import {getAvailableGateways} from './repository'; import Selectors from './selectors'; import ModalEvents from 'core/modal_events'; import PaymentEvents from 'core_payment/events'; import {add as addToast, addToastRegion} from 'core/toast'; import Notification from 'core/notification'; import ModalGateways from './modal_gateways'; /** * Register event listeners for the module. */ const registerEventListeners = () => { document.addEventListener('click', e => { const gatewayTrigger = e.target.closest('[data-action="core_payment/triggerPayment"]'); if (gatewayTrigger) { e.preventDefault(); show(gatewayTrigger, {focusOnClose: e.target}); } }); }; /** * Shows the gateway selector modal. * * @param {HTMLElement} rootNode * @param {Object} options - Additional options * @param {HTMLElement} options.focusOnClose The element to focus on when the modal is closed. */ const show = async(rootNode, { focusOnClose = null, } = {}) => { // Load upfront, so we don't try to inject the internal content into a possibly-not-yet-resolved promise. const body = await Templates.render('core_payment/gateways_modal', {}); const modal = await ModalGateways.create({ title: getString('selectpaymenttype', 'core_payment'), body: body, show: true, removeOnClose: true, }); const rootElement = modal.getRoot()[0]; addToastRegion(rootElement); modal.getRoot().on(ModalEvents.hidden, () => { focusOnClose?.focus(); }); modal.getRoot().on(PaymentEvents.proceed, async(e) => { e.preventDefault(); const gateway = (rootElement.querySelector(Selectors.values.gateway) || {value: ''}).value; if (gateway) { processPayment( gateway, rootNode.dataset.component, rootNode.dataset.paymentarea, rootNode.dataset.itemid, rootNode.dataset.description ).then((message) => { modal.hide(); Notification.addNotification({ message, type: 'success', }); location.href = rootNode.dataset.successurl; return; }).catch(message => Notification.alert('', message)); } else { // We cannot use await in the following line. // The reason is that we are preventing the default action of the save event being triggered, // therefore we cannot define the event handler function asynchronous. addToast(getString('nogatewayselected', 'core_payment'), { type: 'warning', }); } }); // Re-calculate the cost when gateway is changed. rootElement.addEventListener('change', e => { if (e.target.matches(Selectors.elements.gateways)) { updateCostRegion(rootElement, rootNode.dataset.cost); } }); const gateways = await getAvailableGateways(rootNode.dataset.component, rootNode.dataset.paymentarea, rootNode.dataset.itemid); const context = { gateways }; const {html, js} = await Templates.renderForPromise('core_payment/gateways', context); Templates.replaceNodeContents(rootElement.querySelector(Selectors.regions.gatewaysContainer), html, js); selectSingleGateway(rootElement); await updateCostRegion(rootElement, rootNode.dataset.cost); }; /** * Auto-select the gateway if there is only one gateway. * * @param {HTMLElement} root An HTMLElement that contains the cost region */ const selectSingleGateway = root => { const gateways = root.querySelectorAll(Selectors.elements.gateways); if (gateways.length == 1) { gateways[0].checked = true; } }; /** * Shows the cost of the item the user is purchasing in the cost region. * * @param {HTMLElement} root An HTMLElement that contains the cost region * @param {string} defaultCost The default cost that is going to be displayed if no gateway is selected * @returns {Promise} */ const updateCostRegion = async(root, defaultCost = '') => { const gatewayElement = root.querySelector(Selectors.values.gateway); const surcharge = parseInt((gatewayElement || {dataset: {surcharge: 0}}).dataset.surcharge); const cost = (gatewayElement || {dataset: {cost: defaultCost}}).dataset.cost; const valueStr = surcharge ? await getString('feeincludesurcharge', 'core_payment', {fee: cost, surcharge: surcharge}) : cost; const surchargeStr = await getString('labelvalue', 'core', { label: await getString('cost', 'core'), value: valueStr } ); const {html, js} = await Templates.renderForPromise('core_payment/fee_breakdown', {surchargestr: surchargeStr}); Templates.replaceNodeContents(root.querySelector(Selectors.regions.costContainer), html, js); }; /** * Process payment using the selected gateway. * * @param {string} gateway The gateway to be used for payment * @param {string} component Name of the component that the itemId belongs to * @param {string} paymentArea Name of the area in the component that the itemId belongs to * @param {number} itemId An internal identifier that is used by the component * @param {string} description Description of the payment * @returns {Promise} */ const processPayment = async(gateway, component, paymentArea, itemId, description) => { const paymentMethod = await import(`paygw_${gateway}/gateways_modal`); return paymentMethod.process(component, paymentArea, itemId, description); }; /** * Set up the payment actions. */ export const init = () => { if (!init.initialised) { // Event listeners should only be registered once. init.initialised = true; registerEventListeners(); } }; /** * Whether the init function was called before. * * @static * @type {boolean} */ init.initialised = false; PKD|\pssamd/src/repository.jsnu[// This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . /** * Repository for payment subsystem. * * @module core_payment/repository * @copyright 2020 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import Ajax from 'core/ajax'; /** * @typedef {Object} PaymentGateway A Payment Gateway * @property {string} shortname * @property {string} name * @property {string} description */ /** * Returns the list of gateways that can process payments in the given currency. * * @method getAvailableGateways * @param {string} component * @param {string} paymentArea * @param {number} itemId * @returns {Promise} */ export const getAvailableGateways = (component, paymentArea, itemId) => { const request = { methodname: 'core_payment_get_available_gateways', args: { component, paymentarea: paymentArea, itemid: itemId, } }; return Ajax.call([request])[0]; }; PKD|\/amd/src/selectors.jsnu[// This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . /** * Define all of the selectors we will be using on the payment interface. * * @module core_payment/selectors * @copyright 2019 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ export default { elements: { gateways: '[data-region="gateways-container"] input[type="radio"]', }, regions: { gatewaysContainer: '[data-region="gateways-container"]', costContainer: '[data-region="fee-breakdown-container"]', }, values: { gateway: '[data-region="gateways-container"] input[type="radio"]:checked', }, }; PKD|\[Wqamd/build/repository.min.jsnu[define("core_payment/repository",["exports","core/ajax"],(function(_exports,_ajax){var obj; /** * Repository for payment subsystem. * * @module core_payment/repository * @copyright 2020 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.getAvailableGateways=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};_exports.getAvailableGateways=(component,paymentArea,itemId)=>{const request={methodname:"core_payment_get_available_gateways",args:{component:component,paymentarea:paymentArea,itemid:itemId}};return _ajax.default.call([request])[0]}})); //# sourceMappingURL=repository.min.js.mapPKD|\M-amd/build/gateways_modal.min.jsnu[define("core_payment/gateways_modal",["exports","core/templates","core/str","./repository","./selectors","core/modal_events","core_payment/events","core/toast","core/notification","./modal_gateways"],(function(_exports,_templates,_str,_repository,_selectors,_modal_events,_events,_toast,_notification,_modal_gateways){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_templates=_interopRequireDefault(_templates),_selectors=_interopRequireDefault(_selectors),_modal_events=_interopRequireDefault(_modal_events),_events=_interopRequireDefault(_events),_notification=_interopRequireDefault(_notification),_modal_gateways=_interopRequireDefault(_modal_gateways);var _systemImportTransformerGlobalIdentifier="undefined"!=typeof window?window:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}const show=async function(rootNode){let{focusOnClose:focusOnClose=null}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const body=await _templates.default.render("core_payment/gateways_modal",{}),modal=await _modal_gateways.default.create({title:(0,_str.getString)("selectpaymenttype","core_payment"),body:body,show:!0,removeOnClose:!0}),rootElement=modal.getRoot()[0];(0,_toast.addToastRegion)(rootElement),modal.getRoot().on(_modal_events.default.hidden,(()=>{null==focusOnClose||focusOnClose.focus()})),modal.getRoot().on(_events.default.proceed,(async e=>{e.preventDefault();const gateway=(rootElement.querySelector(_selectors.default.values.gateway)||{value:""}).value;gateway?processPayment(gateway,rootNode.dataset.component,rootNode.dataset.paymentarea,rootNode.dataset.itemid,rootNode.dataset.description).then((message=>{modal.hide(),_notification.default.addNotification({message:message,type:"success"}),location.href=rootNode.dataset.successurl})).catch((message=>_notification.default.alert("",message))):(0,_toast.add)((0,_str.getString)("nogatewayselected","core_payment"),{type:"warning"})})),rootElement.addEventListener("change",(e=>{e.target.matches(_selectors.default.elements.gateways)&&updateCostRegion(rootElement,rootNode.dataset.cost)}));const gateways=await(0,_repository.getAvailableGateways)(rootNode.dataset.component,rootNode.dataset.paymentarea,rootNode.dataset.itemid),context={gateways:gateways},{html:html,js:js}=await _templates.default.renderForPromise("core_payment/gateways",context);_templates.default.replaceNodeContents(rootElement.querySelector(_selectors.default.regions.gatewaysContainer),html,js),selectSingleGateway(rootElement),await updateCostRegion(rootElement,rootNode.dataset.cost)},selectSingleGateway=root=>{const gateways=root.querySelectorAll(_selectors.default.elements.gateways);1==gateways.length&&(gateways[0].checked=!0)},updateCostRegion=async function(root){let defaultCost=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";const gatewayElement=root.querySelector(_selectors.default.values.gateway),surcharge=parseInt((gatewayElement||{dataset:{surcharge:0}}).dataset.surcharge),cost=(gatewayElement||{dataset:{cost:defaultCost}}).dataset.cost,valueStr=surcharge?await(0,_str.getString)("feeincludesurcharge","core_payment",{fee:cost,surcharge:surcharge}):cost,surchargeStr=await(0,_str.getString)("labelvalue","core",{label:await(0,_str.getString)("cost","core"),value:valueStr}),{html:html,js:js}=await _templates.default.renderForPromise("core_payment/fee_breakdown",{surchargestr:surchargeStr});_templates.default.replaceNodeContents(root.querySelector(_selectors.default.regions.costContainer),html,js)},processPayment=async(gateway,component,paymentArea,itemId,description)=>(await("function"==typeof _systemImportTransformerGlobalIdentifier.define&&_systemImportTransformerGlobalIdentifier.define.amd?new Promise((function(resolve,reject){_systemImportTransformerGlobalIdentifier.require(["paygw_".concat(gateway,"/gateways_modal")],resolve,reject)})):"undefined"!=typeof module&&module.exports&&"undefined"!=typeof require||"undefined"!=typeof module&&module.component&&_systemImportTransformerGlobalIdentifier.require&&"component"===_systemImportTransformerGlobalIdentifier.require.loader?Promise.resolve(require("paygw_".concat(gateway,"/gateways_modal"))):Promise.resolve(_systemImportTransformerGlobalIdentifier["paygw_".concat(gateway,"/gateways_modal")]))).process(component,paymentArea,itemId,description),init=()=>{init.initialised||(init.initialised=!0,document.addEventListener("click",(e=>{const gatewayTrigger=e.target.closest('[data-action="core_payment/triggerPayment"]');gatewayTrigger&&(e.preventDefault(),show(gatewayTrigger,{focusOnClose:e.target}))})))};_exports.init=init,init.initialised=!1})); //# sourceMappingURL=gateways_modal.min.js.mapPKD|\9Ȯamd/build/modal_gateways.min.jsnu[define("core_payment/modal_gateways",["exports","jquery","core/custom_interaction_events","core/modal","core/modal_events","core_payment/events"],(function(_exports,_jquery,_custom_interaction_events,_modal,_modal_events,_events){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_jquery=_interopRequireDefault(_jquery),_custom_interaction_events=_interopRequireDefault(_custom_interaction_events),_modal=_interopRequireDefault(_modal),_modal_events=_interopRequireDefault(_modal_events),_events=_interopRequireDefault(_events);const SELECTORS_PROCEED_BUTTON='[data-action="proceed"]',SELECTORS_CANCEL_BUTTON='[data-action="cancel"]';class ModalGateways extends _modal.default{constructor(root){super(root)}registerEventListeners(){super.registerEventListeners(),this.getModal().on(_custom_interaction_events.default.events.activate,SELECTORS_PROCEED_BUTTON,((e,data)=>{var proceedEvent=_jquery.default.Event(_events.default.proceed);this.getRoot().trigger(proceedEvent,this),proceedEvent.isDefaultPrevented()||(this.hide(),data.originalEvent.preventDefault())})),this.getModal().on(_custom_interaction_events.default.events.activate,SELECTORS_CANCEL_BUTTON,((e,data)=>{var cancelEvent=_jquery.default.Event(_modal_events.default.cancel);this.getRoot().trigger(cancelEvent,this),cancelEvent.isDefaultPrevented()||(this.hide(),data.originalEvent.preventDefault())}))}}return _exports.default=ModalGateways,_defineProperty(ModalGateways,"TYPE","core_payment-modal_gateways"),_defineProperty(ModalGateways,"TEMPLATE","core_payment/modal_gateways"),ModalGateways.registerModalType(),_exports.default})); //# sourceMappingURL=modal_gateways.min.js.mapPKD|\  amd/build/repository.min.js.mapnu[{"version":3,"file":"repository.min.js","sources":["../src/repository.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Repository for payment subsystem.\n *\n * @module core_payment/repository\n * @copyright 2020 Shamim Rezaie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\n\n/**\n * @typedef {Object} PaymentGateway A Payment Gateway\n * @property {string} shortname\n * @property {string} name\n * @property {string} description\n */\n\n/**\n * Returns the list of gateways that can process payments in the given currency.\n *\n * @method getAvailableGateways\n * @param {string} component\n * @param {string} paymentArea\n * @param {number} itemId\n * @returns {Promise}\n */\nexport const getAvailableGateways = (component, paymentArea, itemId) => {\n const request = {\n methodname: 'core_payment_get_available_gateways',\n args: {\n component,\n paymentarea: paymentArea,\n itemid: itemId,\n }\n };\n return Ajax.call([request])[0];\n};\n"],"names":["component","paymentArea","itemId","request","methodname","args","paymentarea","itemid","Ajax","call"],"mappings":";;;;;;;oLAyCoC,CAACA,UAAWC,YAAaC,gBACnDC,QAAU,CACZC,WAAY,sCACZC,KAAM,CACFL,UAAAA,UACAM,YAAaL,YACbM,OAAQL,gBAGTM,cAAKC,KAAK,CAACN,UAAU"}PKD|\$yyamd/build/events.min.js.mapnu[{"version":3,"file":"events.min.js","sources":["../src/events.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Contain the events the payment modal can fire.\n *\n * @module core_payment/events\n * @copyright 2020 Shamim Rezaie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n proceed: 'core_payment-modal_gateways:proceed',\n};\n"],"names":["proceed"],"mappings":"qKAuBe,CACXA,QAAS"}PKD|\ ~'~'#amd/build/gateways_modal.min.js.mapnu[{"version":3,"file":"gateways_modal.min.js","sources":["../src/gateways_modal.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Contain the logic for the gateways modal.\n *\n * @module core_payment/gateways_modal\n * @copyright 2019 Shamim Rezaie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Templates from 'core/templates';\nimport {getString} from 'core/str';\nimport {getAvailableGateways} from './repository';\nimport Selectors from './selectors';\nimport ModalEvents from 'core/modal_events';\nimport PaymentEvents from 'core_payment/events';\nimport {add as addToast, addToastRegion} from 'core/toast';\nimport Notification from 'core/notification';\nimport ModalGateways from './modal_gateways';\n\n/**\n * Register event listeners for the module.\n */\nconst registerEventListeners = () => {\n document.addEventListener('click', e => {\n const gatewayTrigger = e.target.closest('[data-action=\"core_payment/triggerPayment\"]');\n if (gatewayTrigger) {\n e.preventDefault();\n\n show(gatewayTrigger, {focusOnClose: e.target});\n }\n });\n};\n\n/**\n * Shows the gateway selector modal.\n *\n * @param {HTMLElement} rootNode\n * @param {Object} options - Additional options\n * @param {HTMLElement} options.focusOnClose The element to focus on when the modal is closed.\n */\nconst show = async(rootNode, {\n focusOnClose = null,\n} = {}) => {\n\n // Load upfront, so we don't try to inject the internal content into a possibly-not-yet-resolved promise.\n const body = await Templates.render('core_payment/gateways_modal', {});\n\n const modal = await ModalGateways.create({\n title: getString('selectpaymenttype', 'core_payment'),\n body: body,\n show: true,\n removeOnClose: true,\n });\n\n const rootElement = modal.getRoot()[0];\n addToastRegion(rootElement);\n\n modal.getRoot().on(ModalEvents.hidden, () => {\n focusOnClose?.focus();\n });\n\n modal.getRoot().on(PaymentEvents.proceed, async(e) => {\n e.preventDefault();\n const gateway = (rootElement.querySelector(Selectors.values.gateway) || {value: ''}).value;\n\n if (gateway) {\n processPayment(\n gateway,\n rootNode.dataset.component,\n rootNode.dataset.paymentarea,\n rootNode.dataset.itemid,\n rootNode.dataset.description\n ).then((message) => {\n modal.hide();\n Notification.addNotification({\n message,\n type: 'success',\n });\n location.href = rootNode.dataset.successurl;\n\n return;\n }).catch(message => Notification.alert('', message));\n } else {\n // We cannot use await in the following line.\n // The reason is that we are preventing the default action of the save event being triggered,\n // therefore we cannot define the event handler function asynchronous.\n addToast(getString('nogatewayselected', 'core_payment'), {\n type: 'warning',\n });\n }\n });\n\n // Re-calculate the cost when gateway is changed.\n rootElement.addEventListener('change', e => {\n if (e.target.matches(Selectors.elements.gateways)) {\n updateCostRegion(rootElement, rootNode.dataset.cost);\n }\n });\n\n const gateways = await getAvailableGateways(rootNode.dataset.component, rootNode.dataset.paymentarea, rootNode.dataset.itemid);\n const context = {\n gateways\n };\n\n const {html, js} = await Templates.renderForPromise('core_payment/gateways', context);\n Templates.replaceNodeContents(rootElement.querySelector(Selectors.regions.gatewaysContainer), html, js);\n selectSingleGateway(rootElement);\n await updateCostRegion(rootElement, rootNode.dataset.cost);\n};\n\n/**\n * Auto-select the gateway if there is only one gateway.\n *\n * @param {HTMLElement} root An HTMLElement that contains the cost region\n */\nconst selectSingleGateway = root => {\n const gateways = root.querySelectorAll(Selectors.elements.gateways);\n\n if (gateways.length == 1) {\n gateways[0].checked = true;\n }\n};\n\n/**\n * Shows the cost of the item the user is purchasing in the cost region.\n *\n * @param {HTMLElement} root An HTMLElement that contains the cost region\n * @param {string} defaultCost The default cost that is going to be displayed if no gateway is selected\n * @returns {Promise}\n */\nconst updateCostRegion = async(root, defaultCost = '') => {\n const gatewayElement = root.querySelector(Selectors.values.gateway);\n const surcharge = parseInt((gatewayElement || {dataset: {surcharge: 0}}).dataset.surcharge);\n const cost = (gatewayElement || {dataset: {cost: defaultCost}}).dataset.cost;\n const valueStr = surcharge ? await getString('feeincludesurcharge', 'core_payment', {fee: cost, surcharge: surcharge}) : cost;\n\n const surchargeStr = await getString('labelvalue', 'core',\n {\n label: await getString('cost', 'core'),\n value: valueStr\n }\n );\n\n const {html, js} = await Templates.renderForPromise('core_payment/fee_breakdown', {surchargestr: surchargeStr});\n Templates.replaceNodeContents(root.querySelector(Selectors.regions.costContainer), html, js);\n};\n\n/**\n * Process payment using the selected gateway.\n *\n * @param {string} gateway The gateway to be used for payment\n * @param {string} component Name of the component that the itemId belongs to\n * @param {string} paymentArea Name of the area in the component that the itemId belongs to\n * @param {number} itemId An internal identifier that is used by the component\n * @param {string} description Description of the payment\n * @returns {Promise}\n */\nconst processPayment = async(gateway, component, paymentArea, itemId, description) => {\n const paymentMethod = await import(`paygw_${gateway}/gateways_modal`);\n return paymentMethod.process(component, paymentArea, itemId, description);\n};\n\n/**\n * Set up the payment actions.\n */\nexport const init = () => {\n if (!init.initialised) {\n // Event listeners should only be registered once.\n init.initialised = true;\n registerEventListeners();\n }\n};\n\n/**\n * Whether the init function was called before.\n *\n * @static\n * @type {boolean}\n */\ninit.initialised = false;\n"],"names":["show","async","rootNode","focusOnClose","body","Templates","render","modal","ModalGateways","create","title","removeOnClose","rootElement","getRoot","on","ModalEvents","hidden","focus","PaymentEvents","proceed","e","preventDefault","gateway","querySelector","Selectors","values","value","processPayment","dataset","component","paymentarea","itemid","description","then","message","hide","addNotification","type","location","href","successurl","catch","Notification","alert","addEventListener","target","matches","elements","gateways","updateCostRegion","cost","context","html","js","renderForPromise","replaceNodeContents","regions","gatewaysContainer","selectSingleGateway","root","querySelectorAll","length","checked","defaultCost","gatewayElement","surcharge","parseInt","valueStr","fee","surchargeStr","label","surchargestr","costContainer","paymentArea","itemId","process","init","initialised","document","gatewayTrigger","closest"],"mappings":"y5BAsDMA,KAAOC,eAAMC,cAAUC,aACzBA,aAAe,6DACf,SAGMC,WAAaC,mBAAUC,OAAO,8BAA+B,IAE7DC,YAAcC,wBAAcC,OAAO,CACrCC,OAAO,kBAAU,oBAAqB,gBACtCN,KAAMA,KACNJ,MAAM,EACNW,eAAe,IAGbC,YAAcL,MAAMM,UAAU,6BACrBD,aAEfL,MAAMM,UAAUC,GAAGC,sBAAYC,QAAQ,KACnCb,MAAAA,cAAAA,aAAcc,WAGlBV,MAAMM,UAAUC,GAAGI,gBAAcC,SAASlB,MAAAA,IACtCmB,EAAEC,uBACIC,SAAWV,YAAYW,cAAcC,mBAAUC,OAAOH,UAAY,CAACI,MAAO,KAAKA,MAEjFJ,QACAK,eACIL,QACApB,SAAS0B,QAAQC,UACjB3B,SAAS0B,QAAQE,YACjB5B,SAAS0B,QAAQG,OACjB7B,SAAS0B,QAAQI,aACnBC,MAAMC,UACJ3B,MAAM4B,6BACOC,gBAAgB,CACzBF,QAAAA,QACAG,KAAM,YAEVC,SAASC,KAAOrC,SAAS0B,QAAQY,cAGlCC,OAAMP,SAAWQ,sBAAaC,MAAM,GAAIT,2BAKlC,kBAAU,oBAAqB,gBAAiB,CACrDG,KAAM,eAMlBzB,YAAYgC,iBAAiB,UAAUxB,IAC/BA,EAAEyB,OAAOC,QAAQtB,mBAAUuB,SAASC,WACpCC,iBAAiBrC,YAAaV,SAAS0B,QAAQsB,eAIjDF,eAAiB,oCAAqB9C,SAAS0B,QAAQC,UAAW3B,SAAS0B,QAAQE,YAAa5B,SAAS0B,QAAQG,QACjHoB,QAAU,CACZH,SAAAA,WAGEI,KAACA,KAADC,GAAOA,UAAYhD,mBAAUiD,iBAAiB,wBAAyBH,4BACnEI,oBAAoB3C,YAAYW,cAAcC,mBAAUgC,QAAQC,mBAAoBL,KAAMC,IACpGK,oBAAoB9C,mBACdqC,iBAAiBrC,YAAaV,SAAS0B,QAAQsB,OAQnDQ,oBAAsBC,aAClBX,SAAWW,KAAKC,iBAAiBpC,mBAAUuB,SAASC,UAEnC,GAAnBA,SAASa,SACTb,SAAS,GAAGc,SAAU,IAWxBb,iBAAmBhD,eAAM0D,UAAMI,mEAAc,SACzCC,eAAiBL,KAAKpC,cAAcC,mBAAUC,OAAOH,SACrD2C,UAAYC,UAAUF,gBAAkB,CAACpC,QAAS,CAACqC,UAAW,KAAKrC,QAAQqC,WAC3Ef,MAAQc,gBAAkB,CAACpC,QAAS,CAACsB,KAAMa,eAAenC,QAAQsB,KAClEiB,SAAWF,gBAAkB,kBAAU,sBAAuB,eAAgB,CAACG,IAAKlB,KAAMe,UAAWA,YAAcf,KAEnHmB,mBAAqB,kBAAU,aAAc,OAC/C,CACIC,YAAa,kBAAU,OAAQ,QAC/B5C,MAAOyC,YAITf,KAACA,KAADC,GAAOA,UAAYhD,mBAAUiD,iBAAiB,6BAA8B,CAACiB,aAAcF,kCACvFd,oBAAoBI,KAAKpC,cAAcC,mBAAUgC,QAAQgB,eAAgBpB,KAAMC,KAavF1B,eAAiB1B,MAAMqB,QAASO,UAAW4C,YAAaC,OAAQ1C,qPACtBV,sUAAAA,sGAAAA,+BACvBqD,QAAQ9C,UAAW4C,YAAaC,OAAQ1C,aAMpD4C,KAAO,KACXA,KAAKC,cAEND,KAAKC,aAAc,EAjJvBC,SAASlC,iBAAiB,SAASxB,UACzB2D,eAAiB3D,EAAEyB,OAAOmC,QAAQ,+CACpCD,iBACA3D,EAAEC,iBAEFrB,KAAK+E,eAAgB,CAAC5E,aAAciB,EAAEyB,kCAuJlD+B,KAAKC,aAAc"}PKD|\*Muuamd/build/selectors.min.js.mapnu[{"version":3,"file":"selectors.min.js","sources":["../src/selectors.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Define all of the selectors we will be using on the payment interface.\n *\n * @module core_payment/selectors\n * @copyright 2019 Shamim Rezaie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n elements: {\n gateways: '[data-region=\"gateways-container\"] input[type=\"radio\"]',\n },\n regions: {\n gatewaysContainer: '[data-region=\"gateways-container\"]',\n costContainer: '[data-region=\"fee-breakdown-container\"]',\n },\n values: {\n gateway: '[data-region=\"gateways-container\"] input[type=\"radio\"]:checked',\n },\n};\n"],"names":["elements","gateways","regions","gatewaysContainer","costContainer","values","gateway"],"mappings":"wKAuBe,CACXA,SAAU,CACNC,SAAU,0DAEdC,QAAS,CACLC,kBAAmB,qCACnBC,cAAe,2CAEnBC,OAAQ,CACJC,QAAS"}PKD|\wwiamd/build/events.min.jsnu[define("core_payment/events",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={proceed:"core_payment-modal_gateways:proceed"},_exports.default})); //# sourceMappingURL=events.min.js.mapPKD|\T?amd/build/selectors.min.jsnu[define("core_payment/selectors",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={elements:{gateways:'[data-region="gateways-container"] input[type="radio"]'},regions:{gatewaysContainer:'[data-region="gateways-container"]',costContainer:'[data-region="fee-breakdown-container"]'},values:{gateway:'[data-region="gateways-container"] input[type="radio"]:checked'}},_exports.default})); //# sourceMappingURL=selectors.min.js.mapPKD|\p#amd/build/modal_gateways.min.js.mapnu[{"version":3,"file":"modal_gateways.min.js","sources":["../src/modal_gateways.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Contain the logic for the gateways modal: A modal with proceed and cancel buttons.\n *\n * @module core_payment/modal_gateways\n * @copyright 2020 Shamim Rezaie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport CustomEvents from 'core/custom_interaction_events';\nimport Modal from 'core/modal';\nimport ModalEvents from 'core/modal_events';\nimport PaymentEvents from 'core_payment/events';\n\nconst SELECTORS = {\n PROCEED_BUTTON: '[data-action=\"proceed\"]',\n CANCEL_BUTTON: '[data-action=\"cancel\"]',\n};\n\nexport default class ModalGateways extends Modal {\n static TYPE = 'core_payment-modal_gateways';\n static TEMPLATE = 'core_payment/modal_gateways';\n\n /**\n * Constructor for the Modal.\n *\n * @param {object} root The root jQuery element for the modal\n */\n constructor(root) {\n super(root);\n }\n\n /**\n * Set up all of the event handling for the modal.\n *\n * @method registerEventListeners\n */\n registerEventListeners() {\n // Apply parent event listeners.\n super.registerEventListeners();\n\n this.getModal().on(CustomEvents.events.activate, SELECTORS.PROCEED_BUTTON, (e, data) => {\n var proceedEvent = $.Event(PaymentEvents.proceed);\n this.getRoot().trigger(proceedEvent, this);\n\n if (!proceedEvent.isDefaultPrevented()) {\n this.hide();\n data.originalEvent.preventDefault();\n }\n });\n\n this.getModal().on(CustomEvents.events.activate, SELECTORS.CANCEL_BUTTON, (e, data) => {\n var cancelEvent = $.Event(ModalEvents.cancel);\n this.getRoot().trigger(cancelEvent, this);\n\n if (!cancelEvent.isDefaultPrevented()) {\n this.hide();\n data.originalEvent.preventDefault();\n }\n });\n }\n}\n\nModalGateways.registerModalType();\n"],"names":["SELECTORS","ModalGateways","Modal","constructor","root","registerEventListeners","getModal","on","CustomEvents","events","activate","e","data","proceedEvent","$","Event","PaymentEvents","proceed","getRoot","trigger","this","isDefaultPrevented","hide","originalEvent","preventDefault","cancelEvent","ModalEvents","cancel","registerModalType"],"mappings":"0yBA6BMA,yBACc,0BADdA,wBAEa,+BAGEC,sBAAsBC,eASvCC,YAAYC,YACFA,MAQVC,+BAEUA,8BAEDC,WAAWC,GAAGC,mCAAaC,OAAOC,SAAUV,0BAA0B,CAACW,EAAGC,YACvEC,aAAeC,gBAAEC,MAAMC,gBAAcC,cACpCC,UAAUC,QAAQN,aAAcO,MAEhCP,aAAaQ,4BACTC,OACLV,KAAKW,cAAcC,0BAItBlB,WAAWC,GAAGC,mCAAaC,OAAOC,SAAUV,yBAAyB,CAACW,EAAGC,YACtEa,YAAcX,gBAAEC,MAAMW,sBAAYC,aACjCT,UAAUC,QAAQM,YAAaL,MAE/BK,YAAYJ,4BACRC,OACLV,KAAKW,cAAcC,4EAtCdvB,qBACH,+CADGA,yBAEC,+BA0CtBA,cAAc2B"}PKD|\^>M!!tests/helper_test.phpnu[. /** * Testing helper class methods in payments API * * @package core_payment * @category test * @copyright 2020 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_payment; use advanced_testcase; use core\plugininfo\paygw; /** * Testing helper class methods in payments API * * @package core_payment * @category test * @copyright 2020 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class helper_test extends advanced_testcase { protected function enable_paypal_gateway(): bool { if (!array_key_exists('paypal', \core_component::get_plugin_list('paygw'))) { return false; } return true; } public function test_create_account(): void { global $DB; $this->resetAfterTest(); $account = helper::save_payment_account((object)['name' => 'Test 1', 'idnumber' => '']); $this->assertNotEmpty($account->get('id')); $this->assertEquals('Test 1', $DB->get_field('payment_accounts', 'name', ['id' => $account->get('id')])); } public function test_update_account_details(): void { global $DB; $this->resetAfterTest(); $account = helper::save_payment_account((object)['name' => 'Test 1', 'idnumber' => '']); $record = $account->to_record(); $record->name = 'Edited name'; $editedaccount = helper::save_payment_account($record); $this->assertEquals($account->get('id'), $editedaccount->get('id')); $this->assertEquals('Edited name', $DB->get_field('payment_accounts', 'name', ['id' => $account->get('id')])); } public function test_update_account_gateways(): void { global $DB; if (!$this->enable_paypal_gateway()) { $this->markTestSkipped('Paypal payment gateway plugin not found'); } $this->resetAfterTest(); $account = helper::save_payment_account((object)['name' => 'Test 1', 'idnumber' => '']); $gateway = helper::save_payment_gateway( (object)['accountid' => $account->get('id'), 'gateway' => 'paypal', 'config' => 'T1']); $this->assertNotEmpty($gateway->get('id')); $this->assertEquals('T1', $DB->get_field('payment_gateways', 'config', ['id' => $gateway->get('id')])); // Update by id. $editedgateway = helper::save_payment_gateway( (object)['id' => $gateway->get('id'), 'accountid' => $account->get('id'), 'gateway' => 'paypal', 'config' => 'T2']); $this->assertEquals($gateway->get('id'), $editedgateway->get('id')); $this->assertEquals('T2', $DB->get_field('payment_gateways', 'config', ['id' => $gateway->get('id')])); // Update by account/gateway. $editedgateway = helper::save_payment_gateway( (object)['accountid' => $account->get('id'), 'gateway' => 'paypal', 'config' => 'T3']); $this->assertEquals($gateway->get('id'), $editedgateway->get('id')); $this->assertEquals('T3', $DB->get_field('payment_gateways', 'config', ['id' => $gateway->get('id')])); } public function test_delete_account(): void { global $DB; if (!$this->enable_paypal_gateway()) { $this->markTestSkipped('Paypal payment gateway plugin not found'); } $this->resetAfterTest(); // Delete account without payments, it will be deleted, gateways will also be deleted. $account = helper::save_payment_account((object)['name' => 'Test 1', 'idnumber' => '']); $gateway = helper::save_payment_gateway( (object)['accountid' => $account->get('id'), 'gateway' => 'paypal', 'config' => 'T1']); helper::delete_payment_account(account::get_record(['id' => $account->get('id')])); $this->assertEmpty($DB->get_records('payment_accounts', ['id' => $account->get('id')])); $this->assertEmpty($DB->get_records('payment_gateways', ['id' => $gateway->get('id')])); } public function test_archive_restore_account(): void { global $DB, $USER; $this->resetAfterTest(); // Delete account with payments - it will be archived. $this->setAdminUser(); $account = helper::save_payment_account((object)['name' => 'Test 1', 'idnumber' => '']); $DB->insert_record('payments', [ 'accountid' => $account->get('id'), 'component' => 'test', 'paymentarea' => 'test', 'itemid' => 1, 'userid' => $USER->id, ]); helper::delete_payment_account(account::get_record(['id' => $account->get('id')])); $this->assertEquals(1, $DB->get_field('payment_accounts', 'archived', ['id' => $account->get('id')])); // Restore account. helper::restore_payment_account(account::get_record(['id' => $account->get('id')])); $this->assertEquals(0, $DB->get_field('payment_accounts', 'archived', ['id' => $account->get('id')])); } /** * Provider for format_cost test * * @return array */ public static function get_rounded_cost_provider(): array { return [ 'IRR 0 surcharge' => [5.345, 'IRR', 0, 5], 'IRR 12% surcharge' => [5.345, 'IRR', 12, 6], 'USD 0 surcharge' => [5.345, 'USD', 0, 5.34], 'USD 1% surcharge' => [5.345, 'USD', 1, 5.4], ]; } /** * Provider for test_get_cost_as_string * * @return array[] */ public static function get_cost_as_string_provider(): array { return [ 'IRR 0 surcharge' => [5.345, 'IRR', 0, 'IRR'."\xc2\xa0".'5'], 'IRR 12% surcharge' => [5.345, 'IRR', 12, 'IRR'."\xc2\xa0".'6'], 'USD 0 surcharge' => [5.345, 'USD', 0, 'USD'."\xc2\xa0".'5.34'], 'USD 1% surcharge' => [5.345, 'USD', 1, 'USD'."\xc2\xa0".'5.40'], ]; } /** * Test for test_format_cost function * * @dataProvider get_rounded_cost_provider * @param float $amount * @param string $currency * @param float $surcharge * @param string $expected */ public function test_get_rounded_cost(float $amount, string $currency, float $surcharge, float $expected): void { $this->assertEquals($expected, helper::get_rounded_cost($amount, $currency, $surcharge)); } /** * Test for get_cost_as_string function * * @dataProvider get_cost_as_string_provider * @param float $amount * @param string $currency * @param float $surcharge * @param string $expected */ public function test_get_cost_as_string(float $amount, string $currency, float $surcharge, string $expected): void { // Some old ICU versions have a bug, where they don't follow the CLDR and they are // missing the non-breaking-space between the currency abbreviation and the value. // i.e. it returns AUD50 instead of AU\xc2\xa050). See the following issues @ ICU: // - https://unicode-org.atlassian.net/browse/ICU-6560 // - https://unicode-org.atlassian.net/browse/ICU-8853 // - https://unicode-org.atlassian.net/browse/ICU-8840 // It has been detected that versions prior to ICU-61.1 / ICU-62.1 come with this // problem. Noticeably some CI images (as of December 2021) use buggy ICU-60.1. // So, here, we are going to dynamically verify the behaviour and skip the // test when buggy one is found. No need to apply this to code as dar as the real // formatting is not critical for the functionality (just small glitch). if ('IRR5' === (new \NumberFormatter('en-AU', \NumberFormatter::CURRENCY))->formatCurrency(5, 'IRR')) { $this->markTestSkipped('Old ICU libraries behavior (ICU < 62), skipping this tests'); } $this->assertEquals($expected, helper::get_cost_as_string($amount, $currency, $surcharge)); } } PKD|\puJ J tests/generator/lib.phpnu[. /** * Payment module test data generator class * * @package core_payment * @category test * @copyright 2020 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class core_payment_generator extends component_generator_base { /** @var int */ protected $accountcounter = 0; /** * Create a payment account * * @param array $data account data (name, idnumber, enabled) and additionally field 'gateways' that can include * a list of gateways that should be mock-enabled for this account. */ public function create_payment_account(array $data = []): \core_payment\account { $this->accountcounter++; $gateways = []; if (!empty($data['gateways'])) { $gateways = preg_split('/,/', $data['gateways']); } unset($data['gateways']); $account = \core_payment\helper::save_payment_account( (object)($data + ['name' => 'Test '.$this->accountcounter, 'idnumber' => '', 'enabled' => 1])); foreach ($gateways as $gateway) { \core_payment\helper::save_payment_gateway( (object)['accountid' => $account->get('id'), 'gateway' => $gateway, 'enabled' => 1]); } return $account; } /** * Create a payment account * * @param array $data */ public function create_payment(array $data): int { global $DB; if (empty($data['accountid']) || !\core_payment\account::get_record(['id' => $data['accountid']])) { throw new coding_exception('Account id is not specified or does not exist'); } if (empty($data['amount'])) { throw new coding_exception('Amount must be specified'); } $gateways = \core\plugininfo\paygw::get_enabled_plugins(); if (empty($data['gateway'])) { $data['gateway'] = reset($gateways); } $id = $DB->insert_record('payments', $data + [ 'component' => 'testcomponent', 'paymentarea' => 'teatarea', 'itemid' => 0, 'currency' => 'AUD', ]); return $id; } } PKD|\EA  0tests/generator/behat_core_payment_generator.phpnu[. /** * Behat data generator for core_payment. * * @package core_payment * @category test * @copyright 2020 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Behat data generator for core_payment. * * @package core_payment * @category test * @copyright 2020 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class behat_core_payment_generator extends behat_generator_base { /** * Get a list of the entities that can be created. * * @return array entity name => information about how to generate. */ protected function get_creatable_entities(): array { return [ 'payment accounts' => [ 'singular' => 'payment account', 'datagenerator' => 'payment_account', 'required' => ['name'], ], 'payments' => [ 'singular' => 'payment', 'datagenerator' => 'payment', 'required' => ['account', 'amount', 'user'], 'switchids' => ['account' => 'accountid', 'user' => 'userid'], ], ]; } /** * Look up the id of a account from its name. * * @param string $accountname * @return int corresponding id. */ protected function get_account_id(string $accountname): int { global $DB; if (!$id = $DB->get_field('payment_accounts', 'id', ['name' => $accountname])) { throw new Exception('There is no account with name "' . $accountname . '".'); } return $id; } } PKD|\0-F F tests/generator_test.phpnu[. /** * Testing generator in payments API * * @package core_payment * @category test * @copyright 2020 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_payment; /** * Testing generator in payments API * * @package core_payment * @category test * @copyright 2020 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class generator_test extends \advanced_testcase { public function test_create_account(): void { global $DB; $this->resetAfterTest(); /** @var \core_payment_generator $generator */ $generator = $this->getDataGenerator()->get_plugin_generator('core_payment'); $this->assertTrue($generator instanceof \core_payment_generator); $account1 = $generator->create_payment_account(); $account2 = $generator->create_payment_account(['name' => 'My name', 'gateways' => 'paypal']); $record1 = $DB->get_record('payment_accounts', ['id' => $account1->get('id')]); $record2 = $DB->get_record('payment_accounts', ['id' => $account2->get('id')]); $this->assertEquals(1, $record1->enabled); $this->assertEquals('My name', $record2->name); // First account does not have gateways configurations. $this->assertEmpty($DB->get_records('payment_gateways', ['accountid' => $account1->get('id')])); // Second account has. $this->assertCount(1, $DB->get_records('payment_gateways', ['accountid' => $account2->get('id')])); } public function test_create_payment(): void { global $DB; $this->resetAfterTest(); /** @var \core_payment_generator $generator */ $generator = $this->getDataGenerator()->get_plugin_generator('core_payment'); $account = $generator->create_payment_account(['gateways' => 'paypal']); $user = $this->getDataGenerator()->create_user(); $paymentid = $generator->create_payment(['accountid' => $account->get('id'), 'amount' => 10, 'userid' => $user->id]); $this->assertEquals('testcomponent', $DB->get_field('payments', 'component', ['id' => $paymentid])); } } PKD|\UR* * tests/behat/accounts.featurenu[@core @core_payment Feature: Manage payment accounts @javascript Scenario: Creating and editing payment account When I log in as "admin" And I navigate to "Payments > Payment accounts" in site administration And I follow "Manage payment gateways" Then "Australian Dollar" "text" should exist in the "PayPal" "table_row" And I follow "Payment accounts" And I press "Create payment account" And I set the field "Account name" to "TestAccount" And I press "Save changes" And I should see "PayPal" in the "TestAccount" "table_row" And I open the action menu in "TestAccount" "table_row" And I choose "Edit" in the open action menu And I set the field "Account name" to "NewName" And I press "Save changes" And I should not see "TestAccount" And I should see "PayPal" in the "NewName" "table_row" @javascript Scenario: Configuring gateways on payment accounts Given the following "core_payment > payment accounts" exist: | name | | Account1 | | Account2 | When I log in as "admin" And I navigate to "Payments > Payment accounts" in site administration Then I should see "Not available" in the "Account1" "table_row" And I click on "PayPal" "link" in the "Account1" "table_row" And I set the field "Brand name" to "Test paypal" And I set the following fields to these values: | Brand name | Test paypal | | Client ID | Test | | Secret | Test | | Enable | 1 | And I press "Save changes" And I should not see "Not available" in the "Account1" "table_row" And I should see "PayPal" in the "Account1" "table_row" @javascript Scenario: Deleting payment accounts Given the following "core_payment > payment accounts" exist: | name | | Account1 | | Account2 | When I log in as "admin" And I navigate to "Payments > Payment accounts" in site administration And I open the action menu in "Account1" "table_row" And I choose "Delete or archive" in the open action menu And I click on "Yes" "button" in the "Confirm" "dialogue" Then I should not see "Account1" And I should see "Account2" @javascript Scenario: Archiving and restoring accounts Given the following "users" exist: | username | | user1 | And the following "core_payment > payment accounts" exist: | name | | Account1 | | Account2 | And the following "core_payment > payments" exist: | account | component | amount | user | | Account1 | test | 10 | user1 | | Account1 | test | 15 | user1 | When I log in as "admin" And I navigate to "Payments > Payment accounts" in site administration And I open the action menu in "Account1" "table_row" And I choose "Delete or archive" in the open action menu And I click on "Yes" "button" in the "Confirm" "dialogue" Then I should not see "Account1" And I should see "Account2" And I follow "Show archived" And I should see "Archived" in the "Account1" "table_row" And I open the action menu in "Account1" "table_row" And I choose "Restore" in the open action menu And I should not see "Archived" in the "Account1" "table_row" And I log out PKD|\2tests/behat/siteadmin_accounts_breadcrumbs.featurenu[@core @core_payment @javascript Feature: Verify the breadcrumbs in payment accounts site administration pages Whenever I navigate to payment account page in site administration As an admin The breadcrumbs should be visible Background: Given I log in as "admin" Scenario: Verify the breadcrumbs in payment account page as an admin Given I navigate to "Payments > Payment accounts" in site administration And I click on "Create payment account" "button" And "Create payment account" "text" should exist in the ".breadcrumb" "css_element" And "Payment accounts" "link" should exist in the ".breadcrumb" "css_element" And "Payments" "link" should exist in the ".breadcrumb" "css_element" And I press "Cancel" When I click on "Show archived" "link" Then "Payment accounts" "text" should exist in the ".breadcrumb" "css_element" And "Payments" "link" should exist in the ".breadcrumb" "css_element" PKD|\DY manage_gateway.phpnu[. /** * Manage one payment gateway * * @package core_payment * @copyright 2020 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once(__DIR__ . '/../config.php'); require_once($CFG->libdir . '/adminlib.php'); $id = optional_param('id', 0, PARAM_INT); $accountid = optional_param('accountid', 0, PARAM_INT); $gatewayname = optional_param('gateway', null, PARAM_COMPONENT); $pageurl = new moodle_url('/payment/manage_gateway.php'); admin_externalpage_setup('paymentaccounts', '', [], $pageurl); $enabledplugins = \core\plugininfo\paygw::get_enabled_plugins(); if ($id) { $gateway = new \core_payment\account_gateway($id); $account = new \core_payment\account($gateway->get('accountid')); } else if ($accountid) { $account = new \core_payment\account($accountid); $gateway = $account->get_gateways()[$gatewayname] ?? null; } if (empty($account) || empty($gateway)) { throw new moodle_exception('gatewaynotfound', 'payment'); } require_capability('moodle/payment:manageaccounts', $account->get_context()); $PAGE->set_secondary_active_tab('siteadminnode'); $PAGE->set_primary_active_tab('siteadminnode'); $PAGE->navbar->add(get_string('createaccount', 'payment'), $PAGE->url); $PAGE->set_heading($id ? format_string($account->get('name')) : get_string('createaccount', 'payment')); $form = new \core_payment\form\account_gateway($pageurl->out(false), ['persistent' => $gateway]); if ($form->is_cancelled()) { redirect(new moodle_url('/payment/accounts.php')); } else if ($data = $form->get_data()) { \core_payment\helper::save_payment_gateway($data); redirect(new moodle_url('/payment/accounts.php')); } echo $OUTPUT->header(); $form->display(); echo $OUTPUT->footer(); PKD|\^ manage_account.phpnu[PKD|\]iVrQQN gateway/paypal/styles.cssnu[PKD|\[{{ gateway/paypal/db/services.phpnu[PKD|\ gateway/paypal/db/install.phpnu[PKD|\ݗvvgateway/paypal/db/upgrade.phpnu[PKD|\㻠gateway/paypal/db/install.xmlnu[PKD|\R^ '#gateway/paypal/lang/en/paygw_paypal.phpnu[PKD|\R7҃҃g-gateway/paypal/pix/img.svgnu[PKD|\<Zgateway/paypal/version.phpnu[PKD|\y8cgateway/paypal/classes/external/transaction_complete.phpnu[PKD|\0 0 5egateway/paypal/classes/external/get_config_for_js.phpnu[PKD|\ȭ  +gateway/paypal/classes/privacy/provider.phpnu[PKD|\?(mgateway/paypal/classes/paypal_helper.phpnu[PKD|\!!"gateway/paypal/classes/gateway.phpnu[PKD|\ ;gateway/paypal/templates/paypal_button_placeholder.mustachenu[PKD|\!(Ygateway/paypal/amd/src/gateways_modal.jsnu[PKD|\8U $,gateway/paypal/amd/src/repository.jsnu[PKD|\\<<*w6gateway/paypal/amd/build/repository.min.jsnu[PKD|\). ;gateway/paypal/amd/build/gateways_modal.min.jsnu[PKD|\铊 .Kgateway/paypal/amd/build/repository.min.js.mapnu[PKD|\:7!!2NWgateway/paypal/amd/build/gateways_modal.min.js.mapnu[PKD|\}Rygateway/paypal/settings.phpnu[PKD|\뽨qq ~accounts.phpnu[PKD|\4Wy y 4classes/account_gateway.phpnu[PKD|\off classes/form/account_gateway.phpnu[PKD|\NLWo o classes/form/account.phpnu[PKD|\ Z|+eclasses/external/get_available_gateways.phpnu[PKD|\%i"classes/privacy/paygw_provider.phpnu[PKD|\ߕ22classes/privacy/provider.phpnu[PKD|\ }  %classes/privacy/consumer_provider.phpnu[PKD|\see"K classes/local/entities/payable.phpnu[PKD|\r +classes/local/callback/service_provider.phpnu[PKD|\??classes/helper.phpnu[PKD|\x$y y !J_classes/event/account_deleted.phpnu[PKD|\; (y y !jclasses/event/account_created.phpnu[PKD|\~Xtn n !tclasses/event/account_updated.phpnu[PKD|\ OOclasses/account.phpnu[PKD|\ցN N /classes/gateway.phpnu[PKD|\e!templates/modal_gateways.mustachenu[PKD|\9templates/gateway.mustachenu[PKD|\!templates/gateways_modal.mustachenu[PKD|\}'^templates/gateways_placeholder.mustachenu[PKD|\kACKKHtemplates/gateways.mustachenu[PKD|\ ޻templates/fee_breakdown.mustachenu[PKD|\ʡ~^^ Kupgrade.txtnu[PKD|\aYNI I amd/src/modal_gateways.jsnu[PKD|\>;4vamd/src/events.jsnu[PKD|\!;;amd/src/gateways_modal.jsnu[PKD|\pssamd/src/repository.jsnu[PKD|\/amd/src/selectors.jsnu[PKD|\[Wq(amd/build/repository.min.jsnu[PKD|\M-Yamd/build/gateways_modal.min.jsnu[PKD|\9ȮHamd/build/modal_gateways.min.jsnu[PKD|\  amd/build/repository.min.js.mapnu[PKD|\$yytamd/build/events.min.js.mapnu[PKD|\ ~'~'#8#amd/build/gateways_modal.min.js.mapnu[PKD|\*Muu Kamd/build/selectors.min.js.mapnu[PKD|\wwiQamd/build/events.min.jsnu[PKD|\T?$Samd/build/selectors.min.jsnu[PKD|\p#oUamd/build/modal_gateways.min.js.mapnu[PKD|\^>M!!ctests/helper_test.phpnu[PKD|\puJ J tests/generator/lib.phpnu[PKD|\EA  0qtests/generator/behat_core_payment_generator.phpnu[PKD|\0-F F tests/generator_test.phpnu[PKD|\UR* * utests/behat/accounts.featurenu[PKD|\2tests/behat/siteadmin_accounts_breadcrumbs.featurenu[PKD|\DY manage_gateway.phpnu[PKCC