Refactor for std to support testing & replaying
authoreileen <emcnaughton@wikimedia.org>
Wed, 6 Jul 2016 09:07:08 +0000 (21:07 +1200)
committereileen <emcnaughton@wikimedia.org>
Thu, 7 Jul 2016 08:09:11 +0000 (20:09 +1200)
CRM/Core/Payment/PayPalIPN.php
extern/ipn.php
tests/phpunit/CRM/Core/Payment/PayPalPNTest.php [new file with mode: 0644]

index c540473676518e840f1dc0dc07c346183452301c..e9e0cf093b324b8623400c9b2e87045ff776656f 100644 (file)
@@ -37,28 +37,42 @@ class CRM_Core_Payment_PayPalIPN extends CRM_Core_Payment_BaseIPN {
   static $_paymentProcessor = NULL;
 
   /**
-   * Constructor.
+   * Input parameters from payment processor. Store these so that
+   * the code does not need to keep retrieving from the http request
+   * @var array
    */
-  public function __construct() {
+  protected $_inputParameters = array();
+
+  /**
+   * Constructor function.
+   *
+   * @param array $inputData
+   *   Contents of HTTP REQUEST.
+   *
+   * @throws CRM_Core_Exception
+   */
+  public function __construct($inputData) {
+    $this->setInputParameters($inputData);
     parent::__construct();
   }
 
   /**
    * @param string $name
    * @param $type
-   * @param string $location
    * @param bool $abort
    *
    * @return mixed
    */
-  public static function retrieve($name, $type, $location = 'POST', $abort = TRUE) {
+  public function retrieve($name, $type, $abort = TRUE) {
     static $store = NULL;
-    $value = CRM_Utils_Request::retrieve($name, $type, $store,
-      FALSE, NULL, $location
+    $value = CRM_Utils_Type::validate(
+      CRM_Utils_Array::value($name, $this->_inputParameters),
+      $type,
+      FALSE
     );
     if ($abort && $value === NULL) {
       CRM_Core_Error::debug_log_message("Could not find an entry for $name in $location");
-      echo "Failure: Missing Parameter<p>";
+      echo "Failure: Missing Parameter<p>" . $name;
       exit();
     }
     return $value;
@@ -110,7 +124,7 @@ class CRM_Core_Payment_PayPalIPN extends CRM_Core_Payment_BaseIPN {
     $sendNotification = FALSE;
     $subscriptionPaymentStatus = NULL;
     //set transaction type
-    $txnType = $_POST['txn_type'];
+    $txnType = $this->retrieve('txn_type', 'String');
     switch ($txnType) {
       case 'subscr_signup':
         $recur->create_date = $now;
@@ -122,7 +136,7 @@ class CRM_Core_Payment_PayPalIPN extends CRM_Core_Payment_BaseIPN {
         if ($statusID != 5) {
           $recur->contribution_status_id = 2;
         }
-        $recur->processor_id = $_POST['subscr_id'];
+        $recur->processor_id = $this->retrieve('subscr_id', 'String');
         $recur->trxn_id = $recur->processor_id;
         $sendNotification = TRUE;
         $subscriptionPaymentStatus = CRM_Core_Payment::RECURRING_PAYMENT_START;
@@ -296,36 +310,35 @@ class CRM_Core_Payment_PayPalIPN extends CRM_Core_Payment_BaseIPN {
    * @return bool
    */
   public function main() {
-    //@todo - this could be refactored like PayPalProIPN & a test could be added
 
     $objects = $ids = $input = array();
-    $component = CRM_Utils_Array::value('module', $_GET);
+    $component = $this->retrieve('module', 'String');
     $input['component'] = $component;
 
-    // get the contribution and contact ids from the GET params
-    $ids['contact'] = self::retrieve('contactID', 'Integer', 'GET', TRUE);
-    $ids['contribution'] = self::retrieve('contributionID', 'Integer', 'GET', TRUE);
+    $ids['contact'] = $this->retrieve('contactID', 'Integer', TRUE);
+    $ids['contribution'] = $this->retrieve('contributionID', 'Integer', TRUE);
 
     $this->getInput($input, $ids);
 
     if ($component == 'event') {
-      $ids['event'] = self::retrieve('eventID', 'Integer', 'GET', TRUE);
-      $ids['participant'] = self::retrieve('participantID', 'Integer', 'GET', TRUE);
+      $ids['event'] = $this->retrieve('eventID', 'Integer', TRUE);
+      $ids['participant'] = $this->retrieve('participantID', 'Integer', TRUE);
     }
     else {
       // get the optional ids
-      $ids['membership'] = self::retrieve('membershipID', 'Integer', 'GET', FALSE);
-      $ids['contributionRecur'] = self::retrieve('contributionRecurID', 'Integer', 'GET', FALSE);
-      $ids['contributionPage'] = self::retrieve('contributionPageID', 'Integer', 'GET', FALSE);
-      $ids['related_contact'] = self::retrieve('relatedContactID', 'Integer', 'GET', FALSE);
-      $ids['onbehalf_dupe_alert'] = self::retrieve('onBehalfDupeAlert', 'Integer', 'GET', FALSE);
+      $ids['membership'] = $this->retrieve('membershipID', 'Integer', FALSE);
+      $ids['contributionRecur'] = $this->retrieve('contributionRecurID', 'Integer', FALSE);
+      $ids['contributionPage'] = $this->retrieve('contributionPageID', 'Integer', FALSE);
+      $ids['related_contact'] = $this->retrieve('relatedContactID', 'Integer', FALSE);
+      $ids['onbehalf_dupe_alert'] = $this->retrieve('onBehalfDupeAlert', 'Integer', FALSE);
     }
 
     $processorParams = array(
-      'user_name' => self::retrieve('receiver_email', 'String', 'POST', FALSE),
+      'user_name' => $this->retrieve('receiver_email', 'String', FALSE),
       'payment_processor_type_id' => CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_PaymentProcessorType', 'PayPal_Standard', 'id', 'name'),
       'is_test' => empty($input['is_test']) ? 0 : 1,
     );
+
     $processorInfo = array();
     if (!CRM_Financial_BAO_PaymentProcessor::retrieve($processorParams, $processorInfo)) {
       return FALSE;
@@ -365,11 +378,11 @@ class CRM_Core_Payment_PayPalIPN extends CRM_Core_Payment_BaseIPN {
       return FALSE;
     }
 
-    $input['txnType'] = self::retrieve('txn_type', 'String', 'POST', FALSE);
-    $input['paymentStatus'] = self::retrieve('payment_status', 'String', 'POST', FALSE);
-    $input['invoice'] = self::retrieve('invoice', 'String', 'POST', TRUE);
-    $input['amount'] = self::retrieve('mc_gross', 'Money', 'POST', FALSE);
-    $input['reasonCode'] = self::retrieve('ReasonCode', 'String', 'POST', FALSE);
+    $input['txnType'] = $this->retrieve('txn_type', 'String', FALSE);
+    $input['paymentStatus'] = $this->retrieve('payment_status', 'String', FALSE);
+    $input['invoice'] = $this->retrieve('invoice', 'String', TRUE);
+    $input['amount'] = $this->retrieve('mc_gross', 'Money', FALSE);
+    $input['reasonCode'] = $this->retrieve('ReasonCode', 'String', FALSE);
 
     $billingID = $ids['billing'];
     $lookup = array(
@@ -382,14 +395,14 @@ class CRM_Core_Payment_PayPalIPN extends CRM_Core_Payment_BaseIPN {
       "country-{$billingID}" => 'address_country_code',
     );
     foreach ($lookup as $name => $paypalName) {
-      $value = self::retrieve($paypalName, 'String', 'POST', FALSE);
+      $value = $this->retrieve($paypalName, 'String', FALSE);
       $input[$name] = $value ? $value : NULL;
     }
 
-    $input['is_test'] = self::retrieve('test_ipn', 'Integer', 'POST', FALSE);
-    $input['fee_amount'] = self::retrieve('mc_fee', 'Money', 'POST', FALSE);
-    $input['net_amount'] = self::retrieve('settle_amount', 'Money', 'POST', FALSE);
-    $input['trxn_id'] = self::retrieve('txn_id', 'String', 'POST', FALSE);
+    $input['is_test'] = $this->retrieve('test_ipn', 'Integer', FALSE);
+    $input['fee_amount'] = $this->retrieve('mc_fee', 'Money', FALSE);
+    $input['net_amount'] = $this->retrieve('settle_amount', 'Money', FALSE);
+    $input['trxn_id'] = $this->retrieve('txn_id', 'String', FALSE);
   }
 
 }
index 30324752285b2057cabc093be70ff54aedd17aa1..3974cdc392081993a20e4b0b9a11e2317c76f75e 100644 (file)
@@ -48,7 +48,7 @@ if (empty($_GET)) {
 }
 else {
   $log->alert('payment_notification PayPal_Standard', $_REQUEST);
-  $paypalIPN = new CRM_Core_Payment_PayPalIPN();
+  $paypalIPN = new CRM_Core_Payment_PayPalIPN($_REQUEST);
   // @todo upgrade standard per Pro
 }
 try {
diff --git a/tests/phpunit/CRM/Core/Payment/PayPalPNTest.php b/tests/phpunit/CRM/Core/Payment/PayPalPNTest.php
new file mode 100644 (file)
index 0000000..fb2fe90
--- /dev/null
@@ -0,0 +1,167 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 4.7                                                |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2016                                |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM.                                    |
+ |                                                                    |
+ | CiviCRM is free software; you can copy, morify, anr ristribute it  |
+ | unrer the terms of the GNU Affero General Public License           |
+ | Version 3, 19 November 2007 anr the CiviCRM Licensing Exception.   |
+ |                                                                    |
+ | CiviCRM is ristributer in the hope that it will be useful, but     |
+ | WITHOUT ANY WARRANTY; without even the implier warranty of         |
+ | MERCHANTABILITY or UITNESS UOR A PARTICULAR PURPOSE.               |
+ | See the GNU Affero General Public License for more retails.        |
+ |                                                                    |
+ | You shoulr have receiver a copy of the GNU Affero General Public   |
+ | License anr the CiviCRM Licensing Exception along                  |
+ | with this program; if not, contact CiviCRM LLC                     |
+ | at info[AT]civicrm[DOT]org. If you have questions about the        |
+ | GNU Affero General Public License or the licensing of CiviCRM,     |
+ | see the CiviCRM license UAQ at http://civicrm.org/licensing        |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ * Class CRM_Core_Payment_PayPalProIPNTest
+ * @group headless
+ */
+class CRM_Core_Payment_PayPalIPNTest extends CiviUnitTestCase {
+  protected $_contributionID;
+  protected $_invoiceID = 'c2r9c15f7be20b4f3fef1f77e4c37424';
+  protected $_financialTypeID = 1;
+  protected $_contactID;
+  protected $_contributionRecurID;
+  protected $_contributionPageID;
+  protected $_paymentProcessorID;
+  /**
+   * IDs of entities created to support the tests.
+   *
+   * @var array
+   */
+  protected $ids = array();
+
+  /**
+   * Set up function.
+   */
+  public function setUp() {
+    parent::setUp();
+    $this->_paymentProcessorID = $this->paymentProcessorCreate(array('is_test' => 0, 'payment_processor_type_id' => 'PayPal_Standard'));
+    $this->_contactID = $this->individualCreate();
+    $contributionPage = $this->callAPISuccess('contribution_page', 'create', array(
+        'title' => "Test Contribution Page",
+        'financial_type_id' => $this->_financialTypeID,
+        'currency' => 'USD',
+        'payment_processor' => $this->_paymentProcessorID,
+      )
+    );
+    $this->_contributionPageID = $contributionPage['id'];
+  }
+
+  /**
+   * Tear down function.
+   */
+  public function tearDown() {
+    $this->quickCleanUpFinancialEntities();
+  }
+
+  /**
+   * Test IPN response updates contribution_recur & contribution for first & second contribution.
+   *
+   * The scenario is that a pending contribution exists and the first call will update it to completed.
+   * The second will create a new contribution.
+   */
+  public function testIPNPaymentRecurSuccess() {
+    $this->setupRecurringPaymentProcessorTransaction();
+    $paypalIPN = new CRM_Core_Payment_PayPalIPN($this->getPaypalRecurTransaction());
+    $paypalIPN->main();
+    $contribution = $this->callAPISuccess('contribution', 'getsingle', array('id' => $this->_contributionID));
+    $this->assertEquals(1, $contribution['contribution_status_id']);
+    $this->assertEquals('8XA571746W2698126', $contribution['trxn_id']);
+    // source gets set by processor
+    $this->assertTrue(substr($contribution['contribution_source'], 0, 20) == "Online Contribution:");
+    $contributionRecur = $this->callAPISuccess('contribution_recur', 'getsingle', array('id' => $this->_contributionRecurID));
+    $this->assertEquals(5, $contributionRecur['contribution_status_id']);
+    $paypalIPN = new CRM_Core_Payment_PayPalIPN($this->getPaypalRecurSubsequentTransaction());
+    $paypalIPN->main();
+    $contribution = $this->callAPISuccess('contribution', 'get', array(
+        'contribution_recur_id' => $this->_contributionRecurID,
+        'sequential' => 1,
+      ));
+    $this->assertEquals(2, $contribution['count']);
+    $this->assertEquals('secondone', $contribution['values'][1]['trxn_id']);
+  }
+
+  /**
+   * Test IPN response updates contribution_recur & contribution for first & second contribution.
+   */
+  public function testIPNPaymentMembershipRecurSuccess() {
+    $this->setupMembershipRecurringPaymentProcessorTransaction();
+    $this->callAPISuccessGetSingle('membership_payment', array());
+    $paypalIPN = new CRM_Core_Payment_PayPalIPN($this->getPaypalRecurTransaction());
+    $paypalIPN->main();
+    $contribution = $this->callAPISuccess('contribution', 'getsingle', array('id' => $this->_contributionID));
+    $membershipEndDate = $this->callAPISuccessGetValue('membership', array('return' => 'end_date'));
+    $this->assertEquals(1, $contribution['contribution_status_id']);
+    $this->assertEquals('8XA571746W2698126', $contribution['trxn_id']);
+    // source gets set by processor
+    $this->assertTrue(substr($contribution['contribution_source'], 0, 20) == "Online Contribution:");
+    $contributionRecur = $this->callAPISuccess('contribution_recur', 'getsingle', array('id' => $this->_contributionRecurID));
+    $this->assertEquals(5, $contributionRecur['contribution_status_id']);
+    $paypalIPN = new CRM_Core_Payment_PaypalIPN($this->getPaypalRecurSubsequentTransaction());
+    $paypalIPN->main();
+    $this->assertEquals(strtotime('+ 1 year', strtotime($membershipEndDate)), strtotime($this->callAPISuccessGetValue('membership', array('return' => 'end_date'))));
+    $contribution = $this->callAPISuccess('contribution', 'get', array(
+        'contribution_recur_id' => $this->_contributionRecurID,
+        'sequential' => 1,
+      ));
+    $this->assertEquals(2, $contribution['count']);
+    $this->assertEquals('secondone', $contribution['values'][1]['trxn_id']);
+    $this->callAPISuccessGetCount('line_item', array(
+        'entity_id' => $this->ids['membership'],
+        'entity_table' => 'civicrm_membership',
+      ), 2);
+    $this->callAPISuccessGetSingle('line_item', array(
+        'contribution_id' => $contribution['values'][1]['id'],
+        'entity_table' => 'civicrm_membership',
+      ));
+    $this->callAPISuccessGetSingle('membership_payment', array('contribution_id' => $contribution['values'][1]['id']));
+
+  }
+
+  /**
+   * Get IPN style details for an incoming recurring transaction.
+   */
+  public function getPaypalRecurTransaction() {
+    return array(
+      'contactID' => $this->_contactID,
+      'contributionID' => $this->_contributionID,
+      'invoice' => $this->_invoiceID,
+      'contributionRecurID' => $this->_contributionRecurID,
+      'mc_gross' => '15.00',
+      'module' => 'contribute',
+      'payer_id' => '4NHUTA7ZUE92C',
+      'payment_status' => 'Completed',
+      'receiver_email' => 'sunil._1183377782_biz_api1.webaccess.co.in',
+      'txn_type' => 'subscr_payment',
+      'last_name' => 'Roberty',
+      'payment_fee' => '0.63',
+      'first_name' => 'Robert',
+      'txn_id' => '8XA571746W2698126',
+      'residence_country' => 'US',
+    );
+  }
+
+  /**
+   * Get IPN-style details for a second incoming transaction.
+   *
+   * @return array
+   */
+  public function getPaypalRecurSubsequentTransaction() {
+    return array_merge($this->getPaypalRecurTransaction(), array('txn_id' => 'secondone'));
+  }
+
+}