Skip to content
This repository was archived by the owner on May 29, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
990b4f1
Merge pull request #2 from webong/webhook
webong Aug 28, 2019
0309850
Add files via upload
webong Aug 29, 2019
bf66387
Delete Group.png
webong Aug 29, 2019
ad86a40
Add files via upload
webong Aug 29, 2019
3e51425
Update README.md
webong Aug 29, 2019
2d4a0d3
Update README.md
webong Aug 29, 2019
abb424d
set verify to handle course enrolment too as well as webhook
webong Sep 1, 2019
93bb7e2
update plugin version
webong Sep 1, 2019
dccc4b1
update verify script
webong Sep 1, 2019
5b3a25f
Merge pull request #3 from webong/verify
webong Sep 1, 2019
3f7009d
Merge pull request #1 from webong/master
lukman008 Sep 1, 2019
e92392d
a
webong Sep 1, 2019
6534d66
update version
webong Sep 1, 2019
8a4573c
update version
webong Sep 1, 2019
853f6d6
r
webong Sep 1, 2019
c12041e
paystack.php
webong Sep 1, 2019
a99ec51
Merge pull request #2 from webong/master
lukman008 Sep 2, 2019
b2f3ef4
minor fix
lukman008 Sep 2, 2019
f4764e3
Update paystack.php
lukman008 Sep 2, 2019
933c3ed
update plugin settings to add profile fields to be used as custom fie…
webong May 11, 2020
0fe24f8
update plugin lang
webong May 11, 2020
42f58a2
update lib.php with getting activated custom fields
webong May 11, 2020
107aa12
update enrol.html view
webong May 11, 2020
fc04150
update edit.php
webong May 11, 2020
fde1569
update plugin version
webong May 11, 2020
635786f
Update paystack.php
webong Oct 19, 2020
19f35b9
Merge pull request #3 from webong/patch-1
whales-paystack Oct 19, 2020
fbf1d69
hotfix
webong Jan 14, 2021
1579f15
Fix bugs
whales-paystack Jan 18, 2021
b74eb79
Add South African Rand currency code (ZAR) to lib
Vladdermouse May 10, 2021
db903d4
Merge pull request #4 from Vladdermouse/master
lukman-paystack Jan 5, 2023
20be0c2
Added more currencies
Segtel1 Jan 5, 2023
cff2281
Merge pull request #6 from Segtel1/master
lukman-paystack Jan 5, 2023
5d2ef26
enrol_paystack_charge_exception_handler defined
OgheneTega Jun 3, 2023
0a4e297
get_exception_handler called in verify.php
OgheneTega Jun 5, 2023
2a88bc8
Merge pull request #7 from OgheneTega/set_exception_handler
lukman-paystack Jun 7, 2023
3d325b9
add support for EGP, KES, and XOF
lukman-paystack Jun 8, 2023
f620ac0
add support for EGP, KES, and XOF
lukman-paystack Jun 8, 2023
b1acd85
Merge branch 'master' into patch/currency-support
lukman-paystack Jun 8, 2023
48fb370
Merge pull request #8 from PaystackHQ/patch/currency-support
lukman-paystack Jun 8, 2023
b5ffc2e
added the $a variable where missing
OgheneTega Oct 27, 2023
fe30fc1
Merge pull request #9 from OgheneTega/fix-windows-redirect
lukman-paystack Oct 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions classes/util.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php
// 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/>.

/**
* PayPal enrolment plugin utility class.
*
* @package enrol_paystack
* @copyright 2019 Paystack
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

namespace enrol_paystack;

defined('MOODLE_INTERNAL') || die();

/**
* Paystack enrolment plugin utility class.
*
* @package enrol_paystack
* @copyright 2019 Paystack
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
final class util {

/**
* Send payment error message to the admin.
*
* @param string $subject
* @param stdClass $data
*/
function message_paystack_error_to_admin($subject, $data)
{
$admin = get_admin();
$site = get_site();
$message = "$site->fullname: Transaction failed.\n\n$subject\n\n";
foreach ($data as $key => $value) {
$message .= s($key) . " => " . s($value) . "\n";
}
$eventdata = new \core\message\message();
$eventdata->modulename = 'moodle';
$eventdata->component = 'enrol_paystack';
$eventdata->name = 'paystack_enrolment';
$eventdata->userfrom = $admin;
$eventdata->userto = $admin;
$eventdata->subject = "PAYSTACK PAYMENT ERROR: " . $subject;
$eventdata->fullmessage = $message;
$eventdata->fullmessageformat = FORMAT_PLAIN;
$eventdata->fullmessagehtml = '';
$eventdata->smallmessage = '';
message_send($eventdata);
}

/**
* Silent exception handler.
*
* @return callable exception handler
*/
public static function get_exception_handler() {
return function($ex) {
$info = get_exception_info($ex);

$logerrmsg = "enrol_paystack Webhook exception handler: ".$info->message;
if (debugging('', DEBUG_NORMAL)) {
$logerrmsg .= ' Debug: '.$info->debuginfo."\n".format_backtrace($info->backtrace, true);
}
error_log($logerrmsg);

if (http_response_code() == 200) {
http_response_code(500);
}

exit(0);
};
}
}
6 changes: 5 additions & 1 deletion enrol.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ <h3><?php echo $instancename; ?></h3>
];
?>
<form method="post" action="<?php echo "$CFG->wwwroot/enrol/paystack/verify.php"?>">
<input type="hidden" name="cmd" value="_xclick" />
<?php
foreach ($data as $key => $value) {
echo '<input type="hidden" name="' . $key . '" value="' . $value . '" />';
}
?>
<input type="hidden" name="charset" value="utf-8" />
<input type="hidden" name="custom" value="<?php echo "{$USER->id}-{$course->id}-{$instance->id}" ?>" />

Expand Down
251 changes: 213 additions & 38 deletions verify.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,65 +11,240 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Verify Payment Callback from Paystack
* Listens for Instant Payment Notification from Paystack
*
* This script waits for Payment notification from Paystack,
* then double checks that data by sending it back to Paystack.
* If Paystack verifies this then it sets up the enrolment for that
* user.
*
* @package enrol_paystack
* @copyright 2019 Paystack
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require("../../config.php");
require_once("$CFG->dirroot/enrol/paystack/lib.php");
// Disable moodle specific debug messages and any errors in output,
// comment out when debugging or better look into error log!
define('NO_DEBUG_DISPLAY', true);

require('../../config.php');
require_once('lib.php');
if ($CFG->version < 2018101900) {
require_once($CFG->libdir . '/eventslib.php');
}
require_once($CFG->libdir . '/enrollib.php');
require_once($CFG->libdir . '/filelib.php');

require_login();

$custom = explode('-', optional_param('custom', array(), PARAM_RAW));
$userid = (int)$custom[0];
$courseid = (int)$custom[1];
$instanceid = (int)$custom[2];
// Paystack does not like when we return error messages here,
// the custom handler just logs exceptions and stops.
set_exception_handler('enrol_paystack_charge_exception_handler');

if (!$course = $DB->get_record("course", array("id" => $courseid))) {
redirect($CFG->wwwroot);
// Make sure we are enabled in the first place.
if (!enrol_is_enabled('paystack')) {
http_response_code(503);
throw new moodle_exception('errdisabled', 'enrol_paystack');
}

$context = context_course::instance($course->id, MUST_EXIST);
// Keep out casual intruders.
if (empty($_POST) or !empty($_GET)) {
http_response_code(400);
throw new moodle_exception('invalidrequest', 'core_error');
}
if (empty(required_param('paystack-trxref', PARAM_RAW))) {
print_error(get_string('paystack_sorry', 'enrol_paystack'));
}

$data = new stdClass();

foreach ($_POST as $key => $value) {
if ($key !== clean_param($key, PARAM_ALPHANUMEXT)) {
throw new moodle_exception('invalidrequest', 'core_error', '', null, $key);
}
if (is_array($value)) {
throw new moodle_exception('invalidrequest', 'core_error', '', null, 'Unexpected array param: ' . $key);
}
$data->$key = fix_utf8($value);
}

if (empty($data->custom)) {
throw new moodle_exception('invalidrequest', 'core_error', '', null, 'Missing request param: custom');
}
$custom = explode('-', $data->custom);
unset($data->custom);
if (empty($custom) || count($custom) < 3) {
throw new moodle_exception('invalidrequest', 'core_error', '', null, 'Invalid value of the request param: custom');
}

$data->userid = (int) $custom[0];
$data->courseid = (int) $custom[1];
$data->instanceid = (int) $custom[2];
$data->payment_gross = $data->amount;
$data->payment_currency = $data->currency_code;
$data->timeupdated = time();

// Get the user and course records.
$user = $DB->get_record("user", array("id" => $data->userid), "*", MUST_EXIST);
$course = $DB->get_record("course", array("id" => $data->courseid), "*", MUST_EXIST);
$context = context_course::instance($course->id, MUST_EXIST);
$PAGE->set_context($context);

require_login();
// Use the queried course's full name for the item_name field.
$data->item_name = $course->fullname;

if (!empty($SESSION->wantsurl)) {
$destination = $SESSION->wantsurl;
unset($SESSION->wantsurl);
} else {
$destination = "$CFG->wwwroot/course/view.php?id=$course->id";
$plugin_instance = $DB->get_record("enrol", array("id" => $data->instanceid, "enrol" => "paystack", "status" => 0), "*", MUST_EXIST);
$plugin = enrol_get_plugin('paystack');
$paystack = new \enrol_paystack\paystack('moodle-enrol', $plugin->get_publickey(), $plugin->get_secretkey());

// Set Course and Paystack Url
$courseUrl = "$CFG->wwwroot/course/view.php?id=$course->id";

// Verify Transaction
$res = $paystack->verify_transaction($data->reference);

if (!$res['status']) {
notice($res['message'], $courseUrl);
}

if (empty(required_param('paystack-trxref', PARAM_RAW))) {
notice(get_string('paystack_sorry', 'enrol_paystack'), $destination);
// Send the file, this line will be reached if no error was thrown above.
$data->tax = $res['data']['amount'] / 100;
$data->memo = $res['data']['gateway_response'];
$data->payment_status = $res['data']['status'];
$data->reason_code = $code;
// If currency is incorrectly set then someone maybe trying to cheat the system
if ($data->currency_code != $plugin_instance->currency) {
$message = "Currency does not match course settings, received: " . $data->currency_code;
\enrol_paystack\util::message_paystack_error_to_admin(
$message,
$data
);
notice($message, $courseUrl);
}

$ref = required_param('paystack-trxref', PARAM_RAW);
$fullname = format_string($course->fullname, true, array('context' => $context));
// Check that amount paid is the correct amount
if ((float) $plugin_instance->cost <= 0) {
$cost = (float) $plugin->get_config('cost');
} else {
$cost = (float) $plugin_instance->cost;
}

if (is_enrolled($context, NULL, '', true)) {
// use real paystack check
$plugin = enrol_get_plugin('paystack');
$plugin_instance = $DB->get_record("enrol", array("id" => $instanceid, "enrol" => "paystack", "status" => 0), "*", MUST_EXIST);
$paystack = new \enrol_paystack\Paystack('moodle-enrol', $plugin->get_publickey(), $plugin->secretkey());
$res = $paystack->verify_transaction($ref);
if ($res['data']['status'] != "success") {
$plugin->unenrol_user($plugin_instance, $userid);
message_paystack_error_to_admin(
"Status not successful. User unenrolled from course",
$res
);
redirect($CFG->wwwroot);
// Use the same rounding of floats as on the enrol form.
$cost = format_float($cost, 2, false);

// If cost is greater than payment_gross, then someone maybe trying to cheat the system
if ($data->payment_gross < $cost) {
$message = "Amount paid is not enough ($data->payment_gross < $cost))";
\enrol_paystack\util::message_paystack_error_to_admin(
$message,
$data
);
notice($message, $courseUrl);
}

if ($data->payment_status == 'success') {
// ALL CLEAR !
$paystack->log_transaction_success($data->reference);
$DB->insert_record("enrol_paystack", $data);
if ($plugin_instance->enrolperiod) {
$timestart = time();
$timeend = $timestart + $plugin_instance->enrolperiod;
} else {
$timestart = 0;
$timeend = 0;
}
// Enrol user.
$plugin->enrol_user($plugin_instance, $user->id, $plugin_instance->roleid, $timestart, $timeend);
// Pass $view=true to filter hidden caps if the user cannot see them.
if ($users = get_users_by_capability(
$context,
'moodle/course:update',
'u.*',
'u.id ASC',
'',
'',
'',
'',
false,
true
)) {
$users = sort_by_roleassignment_authority($users, $context);
$teacher = array_shift($users);
} else {
$teacher = false;
}
$mailstudents = $plugin->get_config('mailstudents');
$mailteachers = $plugin->get_config('mailteachers');
$mailadmins = $plugin->get_config('mailadmins');
$shortname = format_string($course->shortname, true, array('context' => $context));
if (!empty($mailstudents)) {
$a = new stdClass();
$a->coursename = format_string($course->fullname, true, array('context' => $coursecontext));
$a->profileurl = "$CFG->wwwroot/user/view.php?id=$user->id";
$eventdata = new \core\message\message();
$eventdata->modulename = 'moodle';
$eventdata->component = 'enrol_paystack';
$eventdata->name = 'paystack_enrolment';
$eventdata->userfrom = empty($teacher) ? core_user::get_support_user() : $teacher;
$eventdata->userto = $user;
$eventdata->subject = get_string("enrolmentnew", 'enrol', $shortname);
$eventdata->fullmessage = get_string('welcometocoursetext', '', $a);
$eventdata->fullmessageformat = FORMAT_PLAIN;
$eventdata->fullmessagehtml = '';
$eventdata->smallmessage = '';
message_send($eventdata);
}
if (!empty($mailteachers) && !empty($teacher)) {
$a->course = format_string($course->fullname, true, array('context' => $coursecontext));
$a->user = fullname($user);
$eventdata = new \core\message\message();
$eventdata->modulename = 'moodle';
$eventdata->component = 'enrol_paystack';
$eventdata->name = 'paystack_enrolment';
$eventdata->userfrom = $user;
$eventdata->userto = $teacher;
$eventdata->subject = get_string("enrolmentnew", 'enrol', $shortname);
$eventdata->fullmessage = get_string('enrolmentnewuser', 'enrol', $a);
$eventdata->fullmessageformat = FORMAT_PLAIN;
$eventdata->fullmessagehtml = '';
$eventdata->smallmessage = '';
message_send($eventdata);
}
if (!empty($mailadmins)) {
$a->course = format_string($course->fullname, true, array('context' => $coursecontext));
$a->user = fullname($user);
$admins = get_admins();
foreach ($admins as $admin) {
$eventdata = new \core\message\message();
$eventdata->modulename = 'moodle';
$eventdata->component = 'enrol_paystack';
$eventdata->name = 'paystack_enrolment';
$eventdata->userfrom = $user;
$eventdata->userto = $admin;
$eventdata->subject = get_string("enrolmentnew", 'enrol', $shortname);
$eventdata->fullmessage = get_string('enrolmentnewuser', 'enrol', $a);
$eventdata->fullmessageformat = FORMAT_PLAIN;
$eventdata->fullmessagehtml = '';
$eventdata->smallmessage = '';
message_send($eventdata);
}
}
redirect($destination, get_string('paymentthanks', '', $fullname));
} else {
// Somehow they aren't enrolled yet! :-(
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After successful payments by users, they were not enrolled for the course,

$PAGE->set_url($destination);
} else {
$message = "Payment status not successful" . $data->memo;
\enrol_paystack\util::message_paystack_error_to_admin(
$message,
$data
);
notice($message, $courseUrl);
}

$fullname = format_string($course->fullname, true, array('context' => $context));
if (is_enrolled($context, null, '', true)) { // TODO: use real paystack check.
redirect($courseUrl, get_string('paymentthanks', '', $fullname));
} else { // Somehow they aren't enrolled yet!
$PAGE->set_url($courseUrl);
echo $OUTPUT->header();
$a = new stdClass();
$a->teacher = get_string('defaultcourseteacher');
$a->fullname = $fullname;
redirect($destination, get_string('paymentsorry', '', $a), 5);
notice(get_string('paymentsorry', '', $a), $courseUrl);
}
2 changes: 1 addition & 1 deletion version.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
defined('MOODLE_INTERNAL') || die();

$plugin->component = 'enrol_paystack';
$plugin->release = '1.2.1';
$plugin->release = '1.3.1';
$plugin->version = 2019082822;
$plugin->requires = 2018120300;
$plugin->maturity = MATURITY_STABLE;
Loading