MDL-61392 enrol_paypal: Improve the IPN notifications handling
authorDavid Mudrák <david@moodle.com>
Thu, 15 Feb 2018 09:28:31 +0000 (10:28 +0100)
committerAndrew Nicols <andrew@nicols.co.uk>
Wed, 14 Mar 2018 00:53:44 +0000 (08:53 +0800)
* Notify administrators once incoming IPN request is verified by PayPal.
* Fix the HTTP status as expected by the IPN protocol.

enrol/paypal/classes/util.php
enrol/paypal/ipn.php
enrol/paypal/lang/en/enrol_paypal.php

index 6f91abd..fcb8f3c 100644 (file)
@@ -81,6 +81,10 @@ final class util {
             }
             error_log($logerrmsg);
 
+            if (http_response_code() == 200) {
+                http_response_code(500);
+            }
+
             exit(0);
         };
     }
index 4c1f713..b3cde16 100644 (file)
@@ -32,6 +32,7 @@
 // comment out when debugging or better look into error log!
 define('NO_DEBUG_DISPLAY', true);
 
+// @codingStandardsIgnoreLine This script does not require login.
 require("../../config.php");
 require_once("lib.php");
 require_once($CFG->libdir.'/eventslib.php');
@@ -42,9 +43,16 @@ require_once($CFG->libdir . '/filelib.php');
 // the custom handler just logs exceptions and stops.
 set_exception_handler(\enrol_paypal\util::get_exception_handler());
 
+// Make sure we are enabled in the first place.
+if (!enrol_is_enabled('paypal')) {
+    http_response_code(503);
+    throw new moodle_exception('errdisabled', 'enrol_paypal');
+}
+
 /// Keep out casual intruders
 if (empty($_POST) or !empty($_GET)) {
-    print_error("Sorry, you can not use the script that way.");
+    http_response_code(400);
+    throw new moodle_exception('invalidrequest', 'core_error');
 }
 
 /// Read all the data from PayPal and get it ready for later;
@@ -69,36 +77,13 @@ $data->payment_gross    = $data->mc_gross;
 $data->payment_currency = $data->mc_currency;
 $data->timeupdated      = time();
 
-// Required for message_send.
-$PAGE->set_context(context_system::instance());
-
-/// get the user and course records
-
-if (! $user = $DB->get_record("user", array("id"=>$data->userid))) {
-    \enrol_paypal\util::message_paypal_error_to_admin("Not a valid user id", $data);
-    die;
-}
-
-if (! $course = $DB->get_record("course", array("id"=>$data->courseid))) {
-    \enrol_paypal\util::message_paypal_error_to_admin("Not a valid course id", $data);
-    die;
-}
-
-if (! $context = context_course::instance($course->id, IGNORE_MISSING)) {
-    \enrol_paypal\util::message_paypal_error_to_admin("Not a valid context id", $data);
-    die;
-}
+$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);
 
-// Now that the course/context has been validated, we can set it. Not that it's wonderful
-// to set contexts more than once but system->course switches are accepted.
-// Required for message_send.
 $PAGE->set_context($context);
 
-if (! $plugin_instance = $DB->get_record("enrol", array("id"=>$data->instanceid, "status"=>0))) {
-    \enrol_paypal\util::message_paypal_error_to_admin("Not a valid instance id", $data);
-    die;
-}
-
+$plugin_instance = $DB->get_record("enrol", array("id" => $data->instanceid, "enrol" => "paypal", "status" => 0), "*", MUST_EXIST);
 $plugin = enrol_get_plugin('paypal');
 
 /// Open a connection back to PayPal to validate the data
@@ -113,10 +98,9 @@ $options = array(
 $location = "https://$paypaladdr/cgi-bin/webscr";
 $result = $c->post($location, $req, $options);
 
-if (!$result) {  /// Could not connect to PayPal - FAIL
-    echo "<p>Error: could not access paypal.com</p>";
-    \enrol_paypal\util::message_paypal_error_to_admin("Could not access paypal.com to verify payment", $data);
-    die;
+if ($c->get_errno()) {
+    throw new moodle_exception('errpaypalconnect', 'enrol_paypal', '', array('url' => $paypaladdr, 'result' => $result),
+        json_encode($data));
 }
 
 /// Connection is OK, so now we post the data to validate it
@@ -318,8 +302,6 @@ if (strlen($result) > 0) {
 
     } else if (strcmp ($result, "INVALID") == 0) { // ERROR
         $DB->insert_record("enrol_paypal", $data, false);
-        \enrol_paypal\util::message_paypal_error_to_admin("Received an invalid payment notification!! (Fake payment?)", $data);
+        throw new moodle_exception('erripninvalid', 'enrol_paypal', '', null, json_encode($data));
     }
 }
-
-exit;
index aa9c7a5..80abf18 100644 (file)
@@ -39,6 +39,9 @@ $string['enrolperiod_desc'] = 'Default length of time that the enrolment is vali
 $string['enrolperiod_help'] = 'Length of time that the enrolment is valid, starting with the moment the user is enrolled. If disabled, the enrolment duration will be unlimited.';
 $string['enrolstartdate'] = 'Start date';
 $string['enrolstartdate_help'] = 'If enabled, users can be enrolled from this date onward only.';
+$string['errdisabled'] = 'PayPal plugin is disabled and does not handle payment notifications.';
+$string['erripninvalid'] = 'Instant payment notification has not been verified by PayPal.';
+$string['errpaypalconnect'] = 'Could not connect to {$a->url} to verify the instant payment notification: {$a->result}';
 $string['expiredaction'] = 'Enrolment expiry action';
 $string['expiredaction_help'] = 'Select action to carry out when user enrolment expires. Please note that some user data and settings are purged from course during course unenrolment.';
 $string['mailadmins'] = 'Notify admin';