diff --git a/.github/workflows/moodle-ci.yml b/.github/workflows/moodle-ci.yml new file mode 100644 index 0000000..bafe038 --- /dev/null +++ b/.github/workflows/moodle-ci.yml @@ -0,0 +1,122 @@ +name: Moodle Plugin CI + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-22.04 + + services: + postgres: + image: postgres:13 + env: + POSTGRES_USER: 'postgres' + POSTGRES_HOST_AUTH_METHOD: 'trust' + ports: + - 5432:5432 + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 3 + + mariadb: + image: mariadb:10 + env: + MYSQL_USER: 'root' + MYSQL_ALLOW_EMPTY_PASSWORD: "true" + MYSQL_CHARACTER_SET_SERVER: "utf8mb4" + MYSQL_COLLATION_SERVER: "utf8mb4_unicode_ci" + ports: + - 3306:3306 + options: --health-cmd="mysqladmin ping" --health-interval 10s --health-timeout 5s --health-retries 3 + + strategy: + fail-fast: false + matrix: + php: ['8.0', '8.1'] + moodle-branch: ['MOODLE_403_STABLE'] + database: [pgsql, mariadb] + + steps: + - name: Check out repository code + uses: actions/checkout@v4 + with: + path: plugin + + - name: Setup PHP ${{ matrix.php }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: ${{ matrix.extensions }} + ini-values: max_input_vars=5000 + # If you are not using code coverage, keep "none". Otherwise, use "pcov" (Moodle 3.10 and up) or "xdebug". + # If you try to use code coverage with "none", it will fallback to phpdbg (which has known problems). + coverage: none + + - name: Initialise moodle-plugin-ci + run: | + composer create-project -n --no-dev --prefer-dist moodlehq/moodle-plugin-ci ci ^4 + echo $(cd ci/bin; pwd) >> $GITHUB_PATH + echo $(cd ci/vendor/bin; pwd) >> $GITHUB_PATH + sudo locale-gen en_AU.UTF-8 + echo "NVM_DIR=$HOME/.nvm" >> $GITHUB_ENV + + - name: Install moodle-plugin-ci + run: moodle-plugin-ci install --plugin ./plugin --db-host=127.0.0.1 + env: + DB: ${{ matrix.database }} + MOODLE_BRANCH: ${{ matrix.moodle-branch }} + # Uncomment this to run Behat tests using the Moodle App. + # MOODLE_APP: 'true' + + - name: PHP Lint + if: ${{ !cancelled() }} + run: moodle-plugin-ci phplint + + - name: PHP Mess Detector + continue-on-error: true # This step will show errors but will not fail + if: ${{ !cancelled() }} + run: moodle-plugin-ci phpmd + + - name: Moodle Code Checker + if: ${{ !cancelled() }} + run: moodle-plugin-ci phpcs --max-warnings 1 + + - name: Moodle PHPDoc Checker + if: ${{ !cancelled() }} + run: moodle-plugin-ci phpdoc --max-warnings 0 + + - name: Validating + if: ${{ !cancelled() }} + run: moodle-plugin-ci validate + + - name: Check upgrade savepoints + if: ${{ !cancelled() }} + run: moodle-plugin-ci savepoints + + - name: Mustache Lint + if: ${{ !cancelled() }} + run: moodle-plugin-ci mustache + + - name: Grunt + if: ${{ !cancelled() }} + run: moodle-plugin-ci grunt --max-lint-warnings 0 + + - name: PHPUnit tests + if: ${{ !cancelled() }} + run: moodle-plugin-ci phpunit --fail-on-warning + + - name: Behat features + id: behat + if: ${{ !cancelled() }} + run: moodle-plugin-ci behat --profile chrome + + - name: Upload Behat Faildump + if: ${{ failure() && steps.behat.outcome == 'failure' }} + uses: actions/upload-artifact@v4 + with: + name: Behat Faildump (${{ join(matrix.*, ', ') }}) + path: ${{ github.workspace }}/moodledata/behat_dump + retention-days: 7 + if-no-files-found: ignore + + - name: Mark cancelled jobs as failed. + if: ${{ cancelled() }} + run: exit 1 diff --git a/README.md b/README.md index 6bab41a..f59ea91 100644 --- a/README.md +++ b/README.md @@ -1 +1,46 @@ -# moodle-paygw_payeer \ No newline at end of file +[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://paypal.me/snickser) support me with a donation. + +# Payeer payment gateway plugin for Moodle. + +Version 0.1 + +https://payeer.com + +![alt text](https://raw.githubusercontent.com/Snickser/moodle-paygw_payeer/pix/img.svg) + +## Status + +[![Build Status](https://github.com/Snickser/moodle-paygw_payeer/actions/workflows/moodle-ci.yml/badge.svg)](https://github.com/Snickser/moodle-paygw_payeer/actions/workflows/moodle-ci.yml) + +## Возможности + ++ Можно использовать пароль или кнопку для обхода платежа. ++ Сохраняет в базе номер курса и название группы студента. ++ Можно указать рекомендуемую цену. ++ Можно ограничить максимальную цену. ++ Отображение продолжительности обучения (для enrol_fee и mod_gwpaymets), если она установлена. ++ Поддержка пароля из модуля курса (mod_gwpaymets). ++ Оповещение пользователя при успешном платеже. + + +## Рекомендации + ++ Moodle 4.3+ ++ Для записи в курс подходит стандарный плагин "Зачисление за оплату" (enrol_fee). ++ Для контрольного задания используйте модуль "[Gateway Payments](https://moodle.org/plugins/mod_gwpayments)" (мои правки [mod_gwpayments](https://github.com/Snickser/moodle-mod_gwpayments/tree/dev)), он правда глючный, но других пока нет. ++ Для ограничения доступности используйте модуль "[PaymentS availability condition for paid access](https://moodle.org/plugins/availability_gwpayments)" (мои правки [availability_gwpayments](https://github.com/Snickser/moodle-availability_gwpayments/tree/dev)). + + +## INSTALLATION + +Download the latest **paygw_payeer.zip** and unzip the contents into the **/payment/gateway** directory. Or upload it from Moodle plugins adminnistration interface.
+ +1. Install the plugin +2. Enable the payeer payment gateway +3. Create a new payment account +4. Configure the payment account against the payeer gateway using your pay ID +5. Enable the 'Enrolment on Payment' enrolment method +6. Add the 'Enrolment on Payment' method to your chosen course +7. Set the payment account, enrolment fee, and currency + +This plugin supports only basic functionality, but everything changes someday... diff --git a/amd/build/gateways_modal.min.js b/amd/build/gateways_modal.min.js new file mode 100644 index 0000000..c10a1f6 --- /dev/null +++ b/amd/build/gateways_modal.min.js @@ -0,0 +1,10 @@ +define("paygw_payeer/gateways_modal",["exports","core/templates","core/modal"],(function(_exports,_templates,_modal){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +/** + * This module is responsible for PayNL content in the gateways modal. + * + * @module paygw_payeer/gateways_modal + * @copyright 2024 Alex Orlov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.process=void 0,_templates=_interopRequireDefault(_templates),_modal=_interopRequireDefault(_modal);_exports.process=(component,paymentArea,itemId,description)=>(async()=>{(await _modal.default.create({body:await _templates.default.render("paygw_payeer/button_placeholder",{}),show:!0,removeOnClose:!0})).destroy()})().then((()=>(location.href=M.cfg.wwwroot+"/payment/gateway/payeer/method.php?sesskey="+M.cfg.sesskey+"&component="+component+"&paymentarea="+paymentArea+"&itemid="+itemId+"&description="+description,new Promise((()=>null)))))})); + +//# sourceMappingURL=gateways_modal.min.js.map \ No newline at end of file diff --git a/amd/build/gateways_modal.min.js.map b/amd/build/gateways_modal.min.js.map new file mode 100644 index 0000000..d7b92a8 --- /dev/null +++ b/amd/build/gateways_modal.min.js.map @@ -0,0 +1 @@ +{"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 PayNL content in the gateways modal.\n *\n * @module paygw_payeer/gateways_modal\n * @copyright 2024 Alex Orlov \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Templates from 'core/templates';\nimport Modal from 'core/modal';\n\n/**\n * Show modal with the PayNL placeholder.\n *\n * @returns {Promise}\n */\n\nconst showModalWithPlaceholder = async() => {\n const modal = await Modal.create({\n body: await Templates.render('paygw_payeer/button_placeholder', {}),\n show: true,\n removeOnClose: true,\n });\n modal.destroy();\n};\n\n\n/**\n * Process.\n *\n * @param {String} component\n * @param {String} paymentArea\n * @param {String} itemId\n * @param {String} description\n * @returns {Promise<>}\n */\nexport const process = (component, paymentArea, itemId, description) => {\n return showModalWithPlaceholder()\n .then(() => {\n location.href = M.cfg.wwwroot + '/payment/gateway/payeer/method.php?' +\n 'sesskey=' + M.cfg.sesskey +\n '&component=' + component +\n '&paymentarea=' + paymentArea +\n '&itemid=' + itemId +\n '&description=' + description;\n return new Promise(() => null);\n });\n};\n"],"names":["component","paymentArea","itemId","description","async","Modal","create","body","Templates","render","show","removeOnClose","destroy","showModalWithPlaceholder","then","location","href","M","cfg","wwwroot","sesskey","Promise"],"mappings":";;;;;;;0LAmDuB,CAACA,UAAWC,YAAaC,OAAQC,cAnBvBC,kBACTC,eAAMC,OAAO,CAC7BC,WAAYC,mBAAUC,OAAO,kCAAmC,IAChEC,MAAM,EACNC,eAAe,KAEbC,WAcCC,GACFC,MAAK,KACFC,SAASC,KAAOC,EAAEC,IAAIC,QAANF,8CACCA,EAAEC,IAAIE,QACnB,cAAgBpB,UAChB,gBAAkBC,YAClB,WAAaC,OACb,gBAAkBC,YACf,IAAIkB,SAAQ,IAAM"} \ No newline at end of file diff --git a/amd/src/gateways_modal.js b/amd/src/gateways_modal.js new file mode 100644 index 0000000..c4742d2 --- /dev/null +++ b/amd/src/gateways_modal.js @@ -0,0 +1,63 @@ +// 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 PayNL content in the gateways modal. + * + * @module paygw_payeer/gateways_modal + * @copyright 2024 Alex Orlov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import Templates from 'core/templates'; +import Modal from 'core/modal'; + +/** + * Show modal with the PayNL placeholder. + * + * @returns {Promise} + */ + +const showModalWithPlaceholder = async() => { + const modal = await Modal.create({ + body: await Templates.render('paygw_payeer/button_placeholder', {}), + show: true, + removeOnClose: true, + }); + modal.destroy(); +}; + + +/** + * Process. + * + * @param {String} component + * @param {String} paymentArea + * @param {String} itemId + * @param {String} description + * @returns {Promise<>} + */ +export const process = (component, paymentArea, itemId, description) => { + return showModalWithPlaceholder() + .then(() => { + location.href = M.cfg.wwwroot + '/payment/gateway/payeer/method.php?' + + 'sesskey=' + M.cfg.sesskey + + '&component=' + component + + '&paymentarea=' + paymentArea + + '&itemid=' + itemId + + '&description=' + description; + return new Promise(() => null); + }); +}; diff --git a/callback.php b/callback.php new file mode 100644 index 0000000..1e6fd74 --- /dev/null +++ b/callback.php @@ -0,0 +1,104 @@ +. + +/** + * Plugin administration pages are defined here. + * + * @package paygw_payeer + * @copyright 2024 Alex Orlov + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use core_payment\helper; +use paygw_payeer\notifications; + +require("../../../config.php"); +global $CFG, $USER, $DB; + +require_once($CFG->libdir . '/filelib.php'); + +defined('MOODLE_INTERNAL') || die(); + +$status = required_param('m_status', PARAM_TEXT); +$amount = required_param('m_amount', PARAM_FLOAT); +$currency = required_param('m_curr', PARAM_TEXT); +$orderid = required_param('m_orderid', PARAM_INT); +$signature = required_param('m_sign', PARAM_TEXT); + +$arhash = [ +$_POST['m_operation_id'], +$_POST['m_operation_ps'], +$_POST['m_operation_date'], +$_POST['m_operation_pay_date'], +$_POST['m_shop'], +$orderid, +$amount, +$currency, +$_POST['m_desc'], +$status, +]; + +if ($status !== 'success') { + die('FAIL. Payment not successed.'); +} + +if (!$payeertx = $DB->get_record('paygw_payeer', [ 'paymentid' => $orderid ])) { + die('FAIL. Not a valid transaction.'); +} + +if (!$payment = $DB->get_record('payments', ['id' => $payeertx->paymentid])) { + die('FAIL. Not a valid payment.'); +} +$component = $payment->component; +$paymentarea = $payment->paymentarea; +$itemid = $payment->itemid; +$paymentid = $payment->id; +$userid = $payment->userid; + +// Get apikey and secretkey. +$config = (object) helper::get_gateway_configuration($component, $paymentarea, $itemid, 'payeer'); + +$arhash[] = $config->apikey; + +$signhash = strtoupper(hash('sha256', implode(':', $arhash))); + +if ($signature !== $signhash) { + die('FAIL. Signature error.'); +} + +// Update payment. +$payment->amount = $amount; +$DB->update_record('payments', $payment); + +// Deliver order. +helper::deliver_order($component, $paymentarea, $itemid, $paymentid, $userid); + +// Notify user. +notifications::notify( + $userid, + $payment->amount, + $payment->currency, + $paymentid, + 'Success completed' +); + +// Update paygw. +$payeertx->success = 1; +if (!$DB->update_record('paygw_payeer', $payeertx)) { + die('FAIL. Update db error.'); +} else { + die($paymentid . '|success'); +} diff --git a/classes/gateway.php b/classes/gateway.php new file mode 100644 index 0000000..023594d --- /dev/null +++ b/classes/gateway.php @@ -0,0 +1,138 @@ +. + +/** + * Contains class for payeer payment gateway. + * + * @package paygw_payeer + * @copyright 2024 Alex Orlov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace paygw_payeer; + +/** + * The gateway class for payeer payment gateway. + * + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class gateway extends \core_payment\gateway { + /** + * Configuration form for currency + */ + public static function get_supported_currencies(): array { + // 3-character ISO-4217: https://en.wikipedia.org/wiki/ISO_4217#Active_codes. + return [ + 'RUB', 'USD', 'EUR', + ]; + } + + /** + * 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', 'shopid', get_string('shopid', 'paygw_payeer')); + $mform->setType('shopid', PARAM_TEXT); + + $mform->addElement('text', 'apikey', get_string('apikey', 'paygw_payeer'), ['size' => 48]); + $mform->setType('apikey', PARAM_TEXT); + + $mform->addElement('text', 'secretkey', get_string('secretkey', 'paygw_payeer'), ['size' => 48]); + $mform->setType('secretkey', PARAM_TEXT); + + $mform->addElement('static'); + + $mform->addElement( + 'advcheckbox', + 'skipmode', + get_string('skipmode', 'paygw_payeer'), + get_string('skipmode', 'paygw_payeer') + ); + $mform->setType('skipmode', PARAM_INT); + $mform->addHelpButton('skipmode', 'skipmode', 'paygw_payeer'); + + $mform->addElement( + 'advcheckbox', + 'passwordmode', + get_string('passwordmode', 'paygw_payeer'), + get_string('passwordmode', 'paygw_payeer') + ); + $mform->setType('passwordmode', PARAM_INT); + $mform->disabledIf('passwordmode', 'skipmode', "neq", 0); + + $mform->addElement('text', 'password', get_string('password', 'paygw_payeer'), ['size' => 20]); + $mform->setType('password', PARAM_TEXT); + $mform->disabledIf('password', 'passwordmode'); + $mform->disabledIf('password', 'skipmode', "neq", 0); + $mform->addHelpButton('password', 'password', 'paygw_payeer'); + + $mform->addElement( + 'advcheckbox', + 'usedetails', + get_string('usedetails', 'paygw_payeer'), + get_string('usedetails', 'paygw_payeer') + ); + $mform->setType('usedetails', PARAM_INT); + $mform->addHelpButton('usedetails', 'usedetails', 'paygw_payeer'); + + $mform->addElement( + 'advcheckbox', + 'showduration', + get_string('showduration', 'paygw_payeer'), + get_string('showduration', 'paygw_payeer') + ); + $mform->setType('showduration', PARAM_INT); + + $mform->addElement('text', 'suggest', get_string('suggest', 'paygw_payeer'), ['size' => 10]); + $mform->setType('suggest', PARAM_TEXT); + + $mform->addElement('text', 'maxcost', get_string('maxcost', 'paygw_payeer'), ['size' => 10]); + $mform->setType('maxcost', PARAM_TEXT); + + global $CFG; + $mform->addElement('html', '
' . + get_string('callback_url', 'paygw_payeer') . '
'); + $mform->addElement('html', $CFG->wwwroot . '/payment/gateway/payeer/callback.php
'); + $mform->addElement('html', get_string('return_url', 'paygw_payeer') . '
'); + $mform->addElement('html', $CFG->wwwroot . '/payment/gateway/payeer/return.php
'); + $mform->addElement('html', get_string('callback_help', 'paygw_payeer') . '

'); + } + + /** + * 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->shopid)) { + $errors['enabled'] = get_string('gatewaycannotbeenabled', 'payment'); + } + } +} diff --git a/classes/notifications.php b/classes/notifications.php new file mode 100644 index 0000000..838f886 --- /dev/null +++ b/classes/notifications.php @@ -0,0 +1,92 @@ +. + +/** Notifications for paygw_payeer. + * + * @package paygw_payeer + * @copyright 2024 Alex Orlov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace paygw_payeer; + +/** Notifications class. + * + * Handle notifications for users about their transactions. + * + * @package paygw_payeer + * @copyright 2024 Alex Orlov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class notifications { + /** + * Function that handle the notifications about transactions using payeer payment gateway + * and all kinds of responses. + * + * this function sending the message to the user and return the id of the message if needed + * or false in case of error. + * + * @param int $userid + * @param float $fee + * @param string $currency + * @param int $orderid + * @param string $type + * @return int|false + */ + public static function notify($userid, $fee, $currency, $orderid, $type = '') { + global $DB; + + // Get the user object for messaging and fullname. + $user = \core_user::get_user($userid); + if (empty($user) || isguestuser($user) || !empty($user->deleted)) { + return false; + } + + // Set the object wiht all informations to notify the user. + $a = (object)[ + 'fee' => $fee, // The original cost. + 'currency' => $currency, + 'orderid' => $orderid, + 'fullname' => fullname($user), + 'firstname' => $user->firstname, + ]; + + $message = new \core\message\message(); + $message->component = 'paygw_payeer'; + $message->name = 'payment_receipt'; // The notification name from message.php. + $message->userfrom = \core_user::get_noreply_user(); // If the message is 'from' a specific user you can set them here. + $message->userto = $user; + $message->subject = get_string('messagesubject', 'paygw_payeer', $type); + switch ($type) { + case 'Success completed': + $messagebody = get_string('message_success_completed', 'paygw_payeer', $a); + break; + } + + $message->fullmessage = $messagebody; + $message->fullmessageformat = FORMAT_MARKDOWN; + $message->fullmessagehtml = "

$messagebody

"; + $message->notification = 1; // Because this is a notification generated from Moodle, not a user-to-user message. + $message->contexturl = ''; // A relevant URL for the notification. + $message->contexturlname = ''; // Link title explaining where users get to for the contexturl. + $content = ['*' => ['header' => '', 'footer' => '']]; // Extra content for specific processor. + $message->set_additional_content('email', $content); + + // Actually send the message. + $messageid = message_send($message); + + return $messageid; + } +} diff --git a/classes/privacy/provider.php b/classes/privacy/provider.php new file mode 100644 index 0000000..3a0b3db --- /dev/null +++ b/classes/privacy/provider.php @@ -0,0 +1,106 @@ +. + +/** + * Privacy Subsystem implementation for paygw_payeer. + * + * @package paygw_payeer + * @category privacy + * @copyright 2024 Alex Orlov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace paygw_payeer\privacy; + +use core_payment\privacy\paygw_provider; +use core_privacy\local\metadata\collection; +use core_privacy\local\request\writer; + +/** + * Privacy Subsystem implementation for paygw_payeer. + * + * @copyright 2024 Alex Orlov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class provider implements \core_privacy\local\request\data_provider, paygw_provider, \core_privacy\local\metadata\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 { + + // Data may be exported to an external location. + $collection->add_external_location_link( + 'payeer.plus', + [ + 'shopid' => 'privacy:metadata:paygw_payeer:shopid', + 'apikey' => 'privacy:metadata:paygw_payeer:apikey', + 'email' => 'privacy:metadata:paygw_payeer:email', + ], + 'privacy:metadata:paygw_payeer:payeer_plus' + ); + + // The paygw_payeer has a database table that contains user data. + $collection->add_database_table( + 'paygw_payeer', + [ + 'invoiceid' => 'privacy:metadata:paygw_payeer:invoiceid', + 'courceid' => 'privacy:metadata:paygw_payeer:courceid', + 'groupnames' => 'privacy:metadata:paygw_payeer:groupnames', + 'success' => 'privacy:metadata:paygw_payeer:success', + ], + 'privacy:metadata:paygw_payeer:paygw_payeer' + ); + return $collection; + } + + + /** + * 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_payeer'); + $record = $DB->get_record('paygw_payeer', ['paymentid' => $payment->id]); + + $data = (object) [ + 'orderid' => $record->invoiceid, + ]; + 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_payeer', "paymentid IN ({$paymentsql})", $paymentparams); + } +} diff --git a/db/install.xml b/db/install.xml new file mode 100644 index 0000000..0df9251 --- /dev/null +++ b/db/install.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + +
+
+
diff --git a/db/messages.php b/db/messages.php new file mode 100644 index 0000000..25e2f01 --- /dev/null +++ b/db/messages.php @@ -0,0 +1,29 @@ +. + +/** + * Defines message providers for payeer payment gateway. + * + * @package paygw_payeer + * @copyright 2024 Alex Orlov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$messageproviders = [ + 'payment_receipt' => [], +]; diff --git a/lang/en/paygw_payeer.php b/lang/en/paygw_payeer.php new file mode 100644 index 0000000..83f5d7f --- /dev/null +++ b/lang/en/paygw_payeer.php @@ -0,0 +1,74 @@ +. + +/** + * Strings for component 'paygw_payeer', language 'en' + * + * @package paygw_payeer + * @copyright 2024 Alex Orlov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +$string['abouttopay'] = 'You are about to pay for'; +$string['apikey'] = 'API Key'; +$string['callback_help'] = 'Copy this lines and paste it into the payeer project settings. Switch off "My project on CMS".'; +$string['callback_url'] = 'Notification URL:'; +$string['fixdesc'] = 'Fixed payment comment'; +$string['fixdesc_help'] = 'This setting sets a fixed comment for all payments.'; +$string['gatewaydescription'] = 'payeer — a reliable way to easily and conveniently accept payments from around the world in the most popular cryptocurrencies.'; +$string['gatewayname'] = 'payeer'; +$string['internalerror'] = 'An internal error has occurred. Please contact us.'; +$string['istestmode'] = 'Test mode'; +$string['maxcost'] = 'Maximium cost'; +$string['message_success_completed'] = 'Hello {$a->firstname}, +You transaction of payment id {$a->orderid} with cost of {$a->fee} {$a->currency} is successfully completed. +If the item is not accessable please contact the administrator.'; +$string['messageprovider:payment_receipt'] = 'Payment receipt'; +$string['messagesubject'] = 'Payment notification'; +$string['password'] = 'Password'; +$string['password_error'] = 'Invalid payment password'; +$string['password_help'] = 'Using this password you can bypass the payback process. It can be useful when it is not possible to make a payment.'; +$string['password_success'] = 'Paymemt password accepted'; +$string['password_text'] = 'If you are unable to make a payment, then ask your curator for a password and enter it.'; +$string['passwordmode'] = 'Password'; +$string['payment'] = 'Donation'; +$string['payment_error'] = 'Payment Error'; +$string['payment_success'] = 'Payment Successful'; +$string['paymore'] = 'If you want to donate more, simply enter your amount instead of the indicated amount.'; +$string['pluginname'] = 'payeer payment'; +$string['pluginname_desc'] = 'The payeer plugin allows you to receive payments via payeer.'; +$string['privacy:metadata'] = 'The payeer plugin store some personal data.'; +$string['privacy:metadata:paygw_payeer:apikey'] = 'ApiKey'; +$string['privacy:metadata:paygw_payeer:courceid'] = 'Cource id'; +$string['privacy:metadata:paygw_payeer:email'] = 'Email'; +$string['privacy:metadata:paygw_payeer:groupnames'] = 'Group names'; +$string['privacy:metadata:paygw_payeer:invoiceid'] = 'Invoice id'; +$string['privacy:metadata:paygw_payeer:payeer_plus'] = 'Send json data'; +$string['privacy:metadata:paygw_payeer:paygw_payeer'] = 'Store some data'; +$string['privacy:metadata:paygw_payeer:shopid'] = 'Shopid'; +$string['privacy:metadata:paygw_payeer:success'] = 'Status'; +$string['return_url'] = 'SuccessURL and FailURL:'; +$string['secretkey'] = 'Secret key'; +$string['sendpaymentbutton'] = 'Send payment via payeer!'; +$string['shopid'] = 'ShopID'; +$string['showduration'] = 'Show duration of training'; +$string['skipmode'] = 'Can skip payment'; +$string['skipmode_help'] = 'This setting allows a payment bypass button, which can be useful in public courses with optional payment.'; +$string['skipmode_text'] = 'If you are not able to make a donation through the payment system, you can click on this button.'; +$string['skippaymentbutton'] = 'Skip payment :('; +$string['suggest'] = 'Suggested cost'; +$string['usedetails'] = 'Make it collapsible'; +$string['usedetails_help'] = 'Display a button or password in a collapsed block.'; +$string['usedetails_text'] = 'Click here if you are unable to donate.'; diff --git a/lang/ru/paygw_payeer.php b/lang/ru/paygw_payeer.php new file mode 100644 index 0000000..5ee4f47 --- /dev/null +++ b/lang/ru/paygw_payeer.php @@ -0,0 +1,64 @@ +. + +/** + * Local language pack from https://study.bhuri.ru + * + * @package paygw_payeer + * @subpackage payeer + * @copyright 2024 Alex Orlov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$string['abouttopay'] = 'Вы собираетесь пожертвовать на'; +$string['apikey'] = 'API Key'; +$string['callback_help'] = 'Скопируйте эти строки и вставьте в настройках проекта в payeer. Отключите настройку "Мой проект на CMS".'; +$string['callback_url'] = 'URL для уведомлений:'; +$string['cost'] = 'Стоимость записи'; +$string['currency'] = 'Валюта'; +$string['fixdesc'] = 'Фиксированный комментарий платежа'; +$string['fixdesc_help'] = 'Эта настройка устанавливает фиксированный комментарий для всех платежей, и отключает отображение описания комментария на странице платежа.'; +$string['gatewaydescription'] = 'payeer — надежный способ легко и удобно принимать оплату со всего мира в самых популярных криптовалютах.'; +$string['istestmode'] = 'Тестовый режим'; +$string['maxcost'] = 'Максимальная цена'; +$string['password'] = 'Резервный пароль'; +$string['password_error'] = 'Введён неверный платёжный пароль'; +$string['password_help'] = 'С помощью этого пароля можно обойти процесс отплаты. Может быть полезен когда нет возможности произвести оплату.'; +$string['password_success'] = 'Платёжный пароль принят'; +$string['password_text'] = 'Если у вас нет возможности сделать пожертвование, то попросите у вашего куратора пароль и введите его.'; +$string['passwordmode'] = 'Разрешить ввод резервного пароля'; +$string['payment'] = 'Пожертвование'; +$string['payment_error'] = 'Ошибка оплаты'; +$string['payment_success'] = 'Оплата успешно произведена'; +$string['paymentserver'] = 'URL сервера оплаты'; +$string['paymore'] = 'Если вы хотите пожертвовать больше, то просто впишите свою сумму вместо указанной.'; +$string['pluginname'] = 'Платежи payeer'; +$string['pluginname_desc'] = 'Плагин payeer позволяет получать платежи через payeer.'; +$string['return_url'] = 'SuccessURL и FailURL:'; +$string['secretkey'] = 'Secret key'; +$string['sendpaymentbutton'] = 'Пожертвовать!'; +$string['shopid'] = 'Идентификатор проекта'; +$string['showduration'] = 'Показывать длительность обучения на странице'; +$string['skipmode'] = 'Показать кнопку обхода платежа'; +$string['skipmode_help'] = 'Эта настройка разрешает кнопку обхода платежа, может быть полезна в публичных курсах с необязательной оплатой.'; +$string['skipmode_text'] = 'Если вы не имеете возможности совершить пожертвование через платёжную систему то можете нажать на эту кнопку.'; +$string['skippaymentbutton'] = 'Не имею :('; +$string['suggest'] = 'Рекомендуемая цена'; +$string['usedetails'] = 'Показывать свёрнутым'; +$string['usedetails_help'] = 'Прячет кнопку или пароль под сворачиваемый блок, если они включены.'; +$string['usedetails_text'] = 'Нажмите тут если у вас нет возможности совершить пожертвование'; diff --git a/method.php b/method.php new file mode 100644 index 0000000..b338f89 --- /dev/null +++ b/method.php @@ -0,0 +1,141 @@ +. + +/** + * Plugin administration pages are defined here. + * + * @package paygw_payeer + * @category admin + * @copyright 2024 Alex Orlov + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use core_payment\helper; + +require_once(__DIR__ . '/../../../config.php'); + +global $CFG, $USER, $DB; + +defined('MOODLE_INTERNAL') || die(); + +require_login(); +require_sesskey(); + +$component = required_param('component', PARAM_COMPONENT); +$paymentarea = required_param('paymentarea', PARAM_AREA); +$itemid = required_param('itemid', PARAM_INT); +$description = required_param('description', PARAM_TEXT); + +$description = json_decode('"' . $description . '"'); + +$params = [ + 'component' => $component, + 'paymentarea' => $paymentarea, + 'itemid' => $itemid, + 'description' => $description, +]; + +$config = (object) helper::get_gateway_configuration($component, $paymentarea, $itemid, 'payeer'); +$payable = helper::get_payable($component, $paymentarea, $itemid);// Get currency and payment amount. +$currency = $payable->get_currency(); +$surcharge = helper::get_gateway_surcharge('payeer');// In case user uses surcharge. +$fee = helper::get_rounded_cost($payable->get_amount(), $currency, $surcharge); + +// Get course info. +$enrolperiod = ''; +$enrolperioddesc = ''; +// Check area. +if ($component == "enrol_fee") { + $cs = $DB->get_record('enrol', ['id' => $itemid, 'enrol' => $paymentarea]); + $enrolperiod = $cs->enrolperiod; +} else if ($component == "mod_gwpayments") { + $cs = $DB->get_record('gwpayments', ['id' => $itemid]); + $enrolperiod = $cs->costduration; +} + +if ($enrolperiod > 0) { + if ($enrolperiod >= 86400 * 7) { + $enrolperioddesc = get_string('weeks'); + $enrolperiod = $enrolperiod / (86400 * 7); + } else if ($enrolperiod >= 86400) { + $enrolperioddesc = get_string('days'); + $enrolperiod = round($enrolperiod / 86400); + } else if ($enrolperiod >= 3600) { + $enrolperioddesc = get_string('hours'); + $enrolperiod = round($enrolperiod / 3600); + } else if ($enrolperiod >= 60) { + $enrolperioddesc = get_string('minutes'); + $enrolperiod = round($enrolperiod / 60); + } else { + $enrolperioddesc = get_string('seconds'); + } +} + + +// Set the context of the page. +$PAGE->set_context(context_system::instance()); + +$PAGE->set_url('/payment/gateway/payeer/method.php', $params); +$string = get_string('payment', 'paygw_payeer'); +$PAGE->set_title(format_string($string)); +$PAGE->set_heading(format_string($string)); + +// Set the appropriate headers for the page. +$PAGE->set_cacheable(false); +$PAGE->set_pagelayout('standard'); + +echo $OUTPUT->header(); + +$templatedata = new stdClass(); +$templatedata->component = $component; +$templatedata->paymentarea = $paymentarea; +$templatedata->itemid = $itemid; +$templatedata->fee = $fee; +$templatedata->currency = $currency; +$templatedata->sesskey = sesskey(); + +if ($config->showduration) { + $templatedata->enrolperiod = $enrolperiod; + $templatedata->enrolperiod_desc = $enrolperioddesc; +} + +$templatedata->passwordmode = $config->passwordmode; + +if ($config->suggest < $fee) { + $templatedata->suggest = $fee; +} else { + $templatedata->suggest = $config->suggest; +} + +$templatedata->maxcost = $config->maxcost; +$templatedata->skipmode = $config->skipmode; + +if ($config->skipmode || $config->passwordmode) { + $templatedata->usedetails = $config->usedetails; +} + +if (!empty($config->fixdesc)) { + $templatedata->description = $config->fixdesc; + $templatedata->fixdesc = 1; +} else { + $templatedata->description = $description; +} + +$templatedata->image = $OUTPUT->image_url('img', 'paygw_payeer'); + +echo $OUTPUT->render_from_template('paygw_payeer/method', $templatedata); + +echo $OUTPUT->footer(); diff --git a/pay.php b/pay.php new file mode 100644 index 0000000..45fd0c3 --- /dev/null +++ b/pay.php @@ -0,0 +1,185 @@ +. + +/** + * Redirects user to the payment page + * + * @package paygw_payeer + * @copyright 2024 Alex Orlov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +use core_payment\helper; + +require_once(__DIR__ . '/../../../config.php'); +global $CFG, $USER, $DB; + +require_once($CFG->libdir . '/filelib.php'); + +require_login(); +require_sesskey(); + +$userid = $USER->id; + +$component = required_param('component', PARAM_COMPONENT); +$paymentarea = required_param('paymentarea', PARAM_AREA); +$itemid = required_param('itemid', PARAM_INT); +$description = required_param('description', PARAM_TEXT); + +$password = optional_param('password', null, PARAM_TEXT); +$skipmode = optional_param('skipmode', 0, PARAM_INT); +$costself = optional_param('costself', null, PARAM_TEXT); + +$config = (object) helper::get_gateway_configuration($component, $paymentarea, $itemid, 'payeer'); +$payable = helper::get_payable($component, $paymentarea, $itemid);// Get currency and payment amount. +$currency = $payable->get_currency(); +$surcharge = helper::get_gateway_surcharge('payeer');// In case user uses surcharge. +// TODO: Check if currency is IDR. If not, then something went really wrong in config. +$cost = helper::get_rounded_cost($payable->get_amount(), $payable->get_currency(), $surcharge); + +// Check self cost. +if (!empty($costself)) { + $cost = $costself; +} +// Check maxcost. +if ($config->maxcost && $cost > $config->maxcost) { + $cost = $config->maxcost; +} +$cost = number_format($cost, 2, '.', ''); + +// Get course and groups for user. +if ($component == "enrol_fee") { + $cs = $DB->get_record('enrol', ['id' => $itemid]); + $cs->course = $cs->courseid; +} else if ($component == "mod_gwpayments") { + $cs = $DB->get_record('gwpayments', ['id' => $itemid]); +} else if ($paymentarea == "cmfee") { + $cs = $DB->get_record('course_modules', ['id' => $itemid]); +} else if ($paymentarea == "sectionfee") { + $cs = $DB->get_record('course_sections', ['id' => $itemid]); +} +$groupnames = ''; +if (!empty($cs->course)) { + $courseid = $cs->course; + if ($gs = groups_get_user_groups($courseid, $userid, true)) { + foreach ($gs as $gr) { + foreach ($gr as $g) { + $groups[] = groups_get_group_name($g); + } + } + if (isset($groups)) { + $groupnames = implode(',', $groups); + } + } +} else { + $courseid = ''; +} + +// Write tx to db. +$paygwdata = new stdClass(); +$paygwdata->courseid = $courseid; +$paygwdata->groupnames = $groupnames; +if (!$transactionid = $DB->insert_record('paygw_payeer', $paygwdata)) { + die(get_string('error_txdatabase', 'paygw_payeer')); +} +$paygwdata->id = $transactionid; + +// Build redirect. +$url = helper::get_success_url($component, $paymentarea, $itemid); + +// Check passwordmode or skipmode. +if (!empty($password) || $skipmode) { + $success = false; + if ($config->skipmode) { + $success = true; + } else if ($config->passwordmode && !empty($config->password)) { + // Check password. + if ($password === $config->password) { + $success = true; + } + } + + if ($success) { + // Make fake pay. + $paymentid = helper::save_payment( + $payable->get_account_id(), + $component, + $paymentarea, + $itemid, + $userid, + 0, + $currency, + 'payeer' + ); + + helper::deliver_order($component, $paymentarea, $itemid, $paymentid, $userid); + + // Write to DB. + $paygwdata->success = 2; + $paygwdata->paymentid = $paymentid; + $DB->update_record('paygw_payeer', $paygwdata); + + redirect($url, get_string('password_success', 'paygw_payeer'), 0, 'success'); + } else { + redirect($url, get_string('password_error', 'paygw_payeer'), 0, 'error'); + } + die; // Never. +} + +// Save payment. +$paymentid = helper::save_payment( + $payable->get_account_id(), + $component, + $paymentarea, + $itemid, + $userid, + 0, + $currency, + 'payeer' +); + +// Write to DB. +$paygwdata->paymentid = $paymentid; +$DB->update_record('paygw_payeer', $paygwdata); + +// Make payment request. +$murl = 'https://payeer.com/merchant/'; +$mshop = $config->shopid; +$morderid = $paymentid; +$mamount = number_format($cost, 2, '.', ''); +$mcurr = $currency == 'RUR' ? 'RUB' : $currency; +$mdesc = base64_encode($description); +$mkey = $config->apikey; + +$arhash = [ + $mshop, + $morderid, + $mamount, + $mcurr, + $mdesc, + $mkey, +]; +$sign = strtoupper(hash('sha256', implode(":", $arhash))); + +redirect($murl . "? +mshop={$mshop}& +morderid={$morderid}& +mamount={$mamount}& +mcurr={$mcurr}& +mdesc={$mdesc}& +m_sign={$sign} +"); diff --git a/pix/icon.svg b/pix/icon.svg new file mode 100644 index 0000000..af00aeb Binary files /dev/null and b/pix/icon.svg differ diff --git a/pix/img.svg b/pix/img.svg new file mode 100644 index 0000000..4ebc1eb --- /dev/null +++ b/pix/img.svg @@ -0,0 +1 @@ +On white \ No newline at end of file diff --git a/return.php b/return.php new file mode 100644 index 0000000..a04d7d0 --- /dev/null +++ b/return.php @@ -0,0 +1,54 @@ +. + +/** + * Redirects user to the original page + * + * @package paygw_payeer + * @copyright 2024 Alex Orlov + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use core_payment\helper; + +require("../../../config.php"); +global $CFG, $DB; + +defined('MOODLE_INTERNAL') || die(); + +require_login(); + +$id = required_param('m_orderid', PARAM_INT); + +if (!$payeertx = $DB->get_record('paygw_payeer', ['paymentid' => $id])) { + die('FAIL. Not a valid transaction id'); +} + +if (!$payment = $DB->get_record('payments', ['id' => $payeertx->paymentid])) { + die('FAIL. Not a valid payment.'); +} + +$paymentarea = $payment->paymentarea; +$component = $payment->component; +$itemid = $payment->itemid; + +$url = helper::get_success_url($component, $paymentarea, $itemid); + +if ($payeertx->success) { + redirect($url, get_string('payment_success', 'paygw_payeer'), 0, 'success'); +} else { + redirect($url, get_string('payment_error', 'paygw_payeer'), 0, 'error'); +} diff --git a/settings.php b/settings.php new file mode 100644 index 0000000..a35b374 --- /dev/null +++ b/settings.php @@ -0,0 +1,31 @@ +. + +/** + * Settings for the payeer payment gateway + * + * @package paygw_payeer + * @copyright 2024 Alex Orlov + * @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_payeer_settings', '', get_string('pluginname_desc', 'paygw_payeer'))); + + \core_payment\helper::add_common_gateway_settings($settings, 'paygw_payeer'); +} diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..44202a8 --- /dev/null +++ b/styles.css @@ -0,0 +1,5 @@ +.core_payment_gateways_modal .payeer .icon { + height: 20px; + width: auto; + max-width: none; +} diff --git a/templates/button_placeholder.mustache b/templates/button_placeholder.mustache new file mode 100644 index 0000000..06b7a8b --- /dev/null +++ b/templates/button_placeholder.mustache @@ -0,0 +1,28 @@ +{{! + 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_payeer/button_placeholder + Classes required for JS: + * none + Data attributes required for JS: + * none + Context variables required for this template: + * none + Example context (json): + {} + +}} +
+
+
\ No newline at end of file diff --git a/templates/method.mustache b/templates/method.mustache new file mode 100644 index 0000000..59bb6a3 --- /dev/null +++ b/templates/method.mustache @@ -0,0 +1,110 @@ +{{! + 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_payeer/method + + Context variables required for this template: + * fee - the amount to pay + * currency - the currency of this payment + * description - what the user paying for + + Example context (json): + { + "image": "http://url", + "sesskey": "abcd", + "fee": "10.05", + "currency": "RUB", + "description": "for enrolment in course", + "enrolperiod": "1", + "enrolperiod_desc": "day", + "passwordmode": "1", + "skipmode": "1", + "suggested": "100", + "maxcost": "1000", + "fixdesc": "1" + } + +}} +
+
+
+ +{{^fixdesc}} +

{{# str }} abouttopay, paygw_payeer {{/ str}} "{{description}}"

+{{/fixdesc}} + +

{{# str }} paymore, paygw_payeer {{/ str}}

+ +

{{# str }} cost {{/ str}}: +  {{currency}} +

+ +{{#enrolperiod}} +

{{# str }} enrolperiod, enrol {{/ str}} ({{enrolperiod_desc}}): {{enrolperiod}}

+{{/enrolperiod}} + +
+payeer + +
+
+ +

+ + +
+ +

{{# str }} paymentinstant {{/ str}}

+ +

+ + +{{#usedetails}} +
+ {{# str }} usedetails_text, paygw_payeer {{/ str }} +{{/usedetails}} + +{{#skipmode}} +

{{# str }} skipmode_text, paygw_payeer {{/ str}}

+ +{{/skipmode}} + +{{^skipmode}} +{{#passwordmode}} +

{{# str }} password_text, paygw_payeer {{/ str}}
+{{# str }} password {{/ str}}: +

+{{/passwordmode}} +{{/skipmode}} + +{{#usedetails}} +
+{{/usedetails}} + +
+ +
+ + + + + +
+ +
diff --git a/version.php b/version.php new file mode 100644 index 0000000..df8ac95 --- /dev/null +++ b/version.php @@ -0,0 +1,31 @@ +. + +/** + * Plugin version and other meta-data are defined here. + * + * @package paygw_payeer + * @copyright 2024 Alex Orlov + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$plugin->version = 2024051700; +$plugin->requires = 2023100900; +$plugin->component = 'paygw_payeer'; +$plugin->release = '0.1'; +$plugin->maturity = MATURITY_STABLE;