Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
192 changes: 192 additions & 0 deletions .github/workflows/moodle-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
name: Moodle Plugin CI

on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-24.04

services:
mariadb:
image: mariadb:10.11.7
env:
MYSQL_ROOT_PASSWORD: ''
MYSQL_ALLOW_EMPTY_PASSWORD: "true"
MYSQL_CHARACTER_SET_SERVER: "utf8mb4"
MYSQL_COLLATION_SERVER: "utf8mb4_unicode_ci"
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping --silent" --health-interval=10s --health-timeout=5s --health-retries=3

strategy:
fail-fast: false
matrix:
php: ['8.4']
moodle-branch: ['MOODLE_500_STABLE']
database: [mariadb]

steps:
- name: Checkout Plugin
uses: actions/checkout@v4
with:
path: authorizedotnet

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: mbstring, intl, soap, curl, gd, xml, json, zip, pdo, pdo_mysql,
ini-values: max_input_vars=5000
coverage: none

- name: Setup Node.js (for Grunt)
uses: actions/setup-node@v3
with:
node-version: '18'

- name: Install Moodle Node dependencies
run: npm install
working-directory: authorizedotnet

- name: Build grunt
run: npm run build
working-directory: authorizedotnet

- name: Install Plugin Composer Dependencies
run: composer install --no-dev --prefer-dist --no-progress
working-directory: authorizedotnet

- name: Clean Extra Files from Plugin
run: |
rm -rf authorizedotnet/.git authorizedotnet/.github authorizedotnet/node_modules authorizedotnet/tests authorizedotnet/phpunit.xml authorizedotnet/vendor
rm -f authorizedotnet/composer.* authorizedotnet/package*.json authorizedotnet/webpack.config.js authorizedotnet/.zipignore

- name: Install moodle-plugin-ci tooling
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
env:
COMPOSER_NO_INTERACTION: 1

- name: Install moodle-plugin-ci
run: moodle-plugin-ci install --plugin ./authorizedotnet --db-host=127.0.0.1
env:
DB: ${{ matrix.database }}
MOODLE_BRANCH: ${{ matrix.moodle-branch }}

- name: PHP Lint
if: ${{ !cancelled() }}
run: moodle-plugin-ci phplint authorizedotnet

- name: Moodle Code Checker
if: ${{ !cancelled() }}
run: moodle-plugin-ci phpcs --max-warnings 0

- name: Moodle PHPDoc Checker
if: ${{ !cancelled() }}
run: moodle-plugin-ci phpdoc --max-warnings 0

- name: PHP Mess Detector
if: ${{ !cancelled() }}
continue-on-error: true
run: moodle-plugin-ci phpmd authorizedotnet


- name: Mustache Lint
if: ${{ !cancelled() }}
run: moodle-plugin-ci mustache

- name: PHP Code Beautifier and Fixer
if: ${{ !cancelled() }}
run: moodle-plugin-ci phpcbf authorizedotnet

- name: Validate Plugin
if: ${{ !cancelled() }}
run: moodle-plugin-ci validate authorizedotnet

- name: Check Upgrade Savepoints
if: ${{ !cancelled() }}
run: moodle-plugin-ci savepoints authorizedotnet

- name: PHPUnit Tests
if: ${{ !cancelled() }}
run: moodle-plugin-ci phpunit --fail-on-warning authorizedotnet

- name: Run Behat Tests
id: behat
if: ${{ !cancelled() }}
run: moodle-plugin-ci behat --profile chrome --scss-deprecations authorizedotnet


- name: Upload Behat Faildump (if failed)
if: ${{ failure() && steps.behat.outcome == 'failure' }}
uses: actions/upload-artifact@v4
with:
name: Behat Faildump (${{ matrix.php }}, ${{ matrix.database }})
path: ${{ github.workspace }}/moodledata/behat_dump
retention-days: 7
if-no-files-found: ignore

- name: Final Cleanup - Remove Vendor
if: ${{ always() }}
run: rm -rf authorizedotnet/vendor

- name: Mark cancelled jobs as failed
if: ${{ cancelled() }}
run: exit 1

create_tag_and_release:
runs-on: ubuntu-24.04
needs: test
if: github.event_name == 'push' && contains(github.event.head_commit.message, 'Merge pull request')
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
path: authorizedotnet

- uses: actions/setup-node@v3
with:
node-version: 18

- name: Configure Git
run: |
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git config --global user.name "github-actions[bot]"

- name: Clear npm cache
run: npm cache clean --force

- name: Install Moodle Node dependencies
run: npm install
working-directory: authorizedotnet

- name: Install Composer Dependencies
run: composer install --no-dev --prefer-dist --no-progress
working-directory: authorizedotnet

- name: Prepare Release Folder
run: mkdir -p release

- name: Build Zip using exclude.lst
run: |
zip -r release/authorizedotnet.zip authorizedotnet -x@authorizedotnet/exclude.lst

- name: Get version from package.json
id: version
run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
working-directory: authorizedotnet

- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: v${{ steps.version.outputs.version }}
files: release/authorizedotnet.zip
body: |
This is the official release for version v${{ steps.version.outputs.version }}.
Thank you for using our authorizedotnet plugin!
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
vendor/
composer.lock
node_modules/
package-lock.json
47 changes: 47 additions & 0 deletions Gruntfile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const fs = require('fs');
const path = require('path');
const { rollup } = require('rollup');
const { nodeResolve } = require('@rollup/plugin-node-resolve');
const commonjs = require('@rollup/plugin-commonjs');
const { terser } = require('rollup-plugin-terser');

module.exports = function (grunt) {
grunt.registerTask('amd', 'Build AMD modules', async function () {
const done = this.async();
const srcDir = 'amd/src';
const destDir = 'amd/build';

if (!fs.existsSync(destDir)) {
fs.mkdirSync(destDir, { recursive: true });
}

const files = fs.readdirSync(srcDir).filter(file => file.endsWith('.js'));

try {
for (const file of files) {
const inputPath = path.join(srcDir, file);
const outputPath = path.join(destDir, file.replace('.js', '.min.js'));

const bundle = await rollup({
input: inputPath,
plugins: [
nodeResolve(),
commonjs(),
terser()
]
});

await bundle.write({
file: outputPath,
format: 'amd'
});

grunt.log.writeln(`✔ Built ${file} → ${outputPath}`);
}
done();
} catch (err) {
grunt.log.error(err);
done(false);
}
});
};
1 change: 1 addition & 0 deletions amd/build/payment.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

144 changes: 144 additions & 0 deletions amd/src/payment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// 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 <http://www.gnu.org/licenses/>.

/**
* TODO describe module payment
*
* @module enrol_authorizedotnet/payment
* @copyright 2025 YOUR NAME <your@email.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

import ajax from "core/ajax";
import Templates from "core/templates";
import Modal from "core/modal";
import ModalEvents from "core/modal_events";
import { getString } from "core/str";
import Notification from "core/notification";

const { call: fetchMany } = ajax;

// Repository function
const getConfigForJs = (instanceid) =>
fetchMany([{
methodname: "moodle_authorizedotnet_get_config_for_js",
args: { instanceid },
}])[0];

const processPayment = (instanceid, userid, opaqueData) =>
fetchMany([{
methodname: "moodle_authorizedotnet_process_payment",
args: {
instanceid,
userid,
opaquedata: JSON.stringify(opaqueData),
},
}])[0];

const switchSdk = (environment) => {
const sdkUrl = (environment === 'sandbox')
? 'https://jstest.authorize.net/v3/AcceptUI.js'
: 'https://js.authorize.net/v3/AcceptUI.js';

if (switchSdk.currentlyloaded === sdkUrl) {
return Promise.resolve();
}

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 => {
script.onload = () => resolve();
script.setAttribute('src', sdkUrl);
script.setAttribute('charset', 'utf-8');
document.head.appendChild(script);
switchSdk.currentlyloaded = sdkUrl;
});
};
switchSdk.currentlyloaded = '';

function authorizeNetPayment(instanceid, userid) {
const enrolButton = document.getElementById(`enrolbutton-${instanceid}`);
if (!enrolButton) {
return;
}

enrolButton.addEventListener("click", async () => {
let modal;
try {
const config = await getConfigForJs(instanceid);
const body = await Templates.render(
'enrol_authorizedotnet/authorizedotnet_button',
{
apiloginid: config.apiloginid,
clientkey: config.publicclientkey,
}
);

modal = await Modal.create({
title: getString("pluginname", "enrol_authorizedotnet"),
body: body,
show: true,
removeOnClose: true,
});

await switchSdk(config.environment);
window.responseHandler = function (response) {
// Prevent outside clicks while processing.
modal.getRoot().on(ModalEvents.outsideClick, (e) => e.preventDefault());

if (response.messages.resultCode === "Error") {
let errorMessages = '';
for (let i = 0; i < response.messages.message.length; i++) {
errorMessages += response.messages.message[i].text + '\n';
}
Notification.alert(getString('error', 'moodle'), errorMessages);
modal.hide();
return;
}
modal.setBody(getString('authorising', 'enrol_authorizedotnet'));

processPayment(instanceid, userid, response.opaqueData)
.then((res) => {
modal.hide();
if (res.success) {
window.location.reload();
} else {
Notification.alert(getString('error', 'moodle'), res.message);
}
})
.catch((err) => {
Notification.exception(err);
modal.hide();
});
};

} catch (err) {
Notification.exception(err);
if (modal) {
modal.hide();
}
}
});
}

export default {
authorizeNetPayment,
};
Loading