From 2d39b9c013a18cefbbf1607638b77dcbf40f514d Mon Sep 17 00:00:00 2001 From: Seamus Lee Date: Fri, 12 Oct 2018 11:25:47 +1100 Subject: [PATCH] dev/financial#33 Add in Hook alterIPNData to allow Extensions to do custom processing of the IPN Data Add in unit test of alterIPNData hook and ensure that it is called in all styles of IPN Notifications Shift hook call to be done in handlePaymentMethod function as per Eileen's comment and remove the hook from deprecated pathways and passbyrefeence in hook Change name to be postIPNProcess to be more explicit about the purpose of the hook --- CRM/Core/Payment.php | 3 + CRM/Utils/Hook.php | 13 ++++ .../CRM/Core/Payment/PayPalIPNTest.php | 67 +++++++++++++++++++ 3 files changed, 83 insertions(+) diff --git a/CRM/Core/Payment.php b/CRM/Core/Payment.php index 63ce4d9b5a31..cf31189d384d 100644 --- a/CRM/Core/Payment.php +++ b/CRM/Core/Payment.php @@ -1393,6 +1393,9 @@ public static function handlePaymentMethod($method, $params = array()) { $extension_instance_found = TRUE; } + // Call IPN alterIPNData hook to allow for custom processing of IPN data. + $IPNParams = array_merge($_GET, $_REQUEST); + CRM_Utils_Hook::postIPNProcess($IPNParams); if (!$extension_instance_found) { $message = "No extension instances of the '%1' payment processor were found.
" . "%2 method is unsupported in legacy payment processors."; diff --git a/CRM/Utils/Hook.php b/CRM/Utils/Hook.php index 92989a902534..e00b3f2f93e1 100644 --- a/CRM/Utils/Hook.php +++ b/CRM/Utils/Hook.php @@ -2473,4 +2473,17 @@ public static function alterMailingRecipients(&$mailingObject, &$criteria, $cont ); } + /** + * ALlow Extensions to custom process IPN hook data such as sending Google Analyitcs information based on the IPN + * @param array $IPNData - Array of IPN Data + * @return mixed + */ + public static function postIPNProcess(&$IPNData) { + return self::singleton()->invoke(array('IPNData'), + $IPNData, self::$_nullObject, self::$_nullObject, + self::$_nullObject, self::$_nullObject, self::$_nullObject, + 'civicrm_postIPNProcess' + ); + } + } diff --git a/tests/phpunit/CRM/Core/Payment/PayPalIPNTest.php b/tests/phpunit/CRM/Core/Payment/PayPalIPNTest.php index 9de32d4ea3a8..c9862c05ec5e 100644 --- a/tests/phpunit/CRM/Core/Payment/PayPalIPNTest.php +++ b/tests/phpunit/CRM/Core/Payment/PayPalIPNTest.php @@ -37,6 +37,7 @@ class CRM_Core_Payment_PayPalIPNTest extends CiviUnitTestCase { protected $_contributionRecurID; protected $_contributionPageID; protected $_paymentProcessorID; + protected $_customFieldID; /** * IDs of entities created to support the tests. * @@ -237,6 +238,7 @@ public function getPaypalTransaction() { 'first_name' => 'Robert', 'txn_id' => '8XA571746W2698126', 'residence_country' => 'US', + 'custom' => json_encode(['cgid' => 'test12345']), ); } @@ -249,4 +251,69 @@ public function getPaypalRecurSubsequentTransaction() { return array_merge($this->getPaypalRecurTransaction(), array('txn_id' => 'secondone')); } + /** + * Test IPN response updates contribution and invoice is attached in mail reciept + * Test also AlterIPNData intercepts at the right point and allows for custom processing + * The scenario is that a pending contribution exists and the IPN call will update it to completed. + * And also if Tax and Invoicing is enabled, this unit test ensure that invoice pdf is attached with email recipet + */ + public function testhookAlterIPNDataOnIPNPaymentSuccess() { + + $pendingStatusID = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Pending'); + $completedStatusID = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'); + $params = array( + 'payment_processor_id' => $this->_paymentProcessorID, + 'contact_id' => $this->_contactID, + 'trxn_id' => NULL, + 'invoice_id' => $this->_invoiceID, + 'contribution_status_id' => $pendingStatusID, + 'is_email_receipt' => TRUE, + ); + $this->_contributionID = $this->contributionCreate($params); + $this->createCustomField(); + $contribution = $this->callAPISuccess('contribution', 'get', array('id' => $this->_contributionID, 'sequential' => 1)); + // assert that contribution created before handling payment via paypal standard has no transaction id set and pending status + $this->assertEquals(NULL, $contribution['values'][0]['trxn_id']); + $this->assertEquals($pendingStatusID, $contribution['values'][0]['contribution_status_id']); + $this->hookClass->setHook('civicrm_postIPNProcess', array($this, 'hookCiviCRMAlterIPNData')); + global $_REQUEST; + $_REQUEST = array('q' => CRM_Utils_System::url('civicrm/payment/ipn/' . $this->_paymentProcessorID)) + $this->getPaypalTransaction(); + + $mut = new CiviMailUtils($this, TRUE); + $payment = CRM_Core_Payment::handlePaymentMethod('PaymentNotification', ['processor_id' => $this->_paymentProcessorID]); + + $contribution = $this->callAPISuccess('contribution', 'get', array('id' => $this->_contributionID, 'sequential' => 1)); + // assert that contribution is completed after getting response from paypal standard which has transaction id set and completed status + $this->assertEquals($_REQUEST['txn_id'], $contribution['values'][0]['trxn_id']); + $this->assertEquals($completedStatusID, $contribution['values'][0]['contribution_status_id']); + $this->assertEquals('test12345', $contribution['values'][0]['custom_' . $this->_customFieldID]); + } + + /** + * Store Custom data passed in from the PayPalIPN in a custom field + */ + public function hookCiviCRMAlterIPNData($data) { + if (!empty($data['custom'])) { + $customData = json_decode($data['custom'], TRUE); + $customField = $this->callAPISuccess('custom_field', 'get', ['label' => 'TestCustomFieldIPNHook']); + $this->callAPISuccess('contribution', 'create', ['id' => $this->_contributionID, 'custom_' . $customField['id'] => $customData['cgid']]); + } + } + + /** + * @return array + */ + protected function createCustomField() { + $customGroup = $this->customGroupCreate(array('extends' => 'Contribution')); + $fields = array( + 'label' => 'TestCustomFieldIPNHook', + 'data_type' => 'String', + 'html_type' => 'Text', + 'custom_group_id' => $customGroup['id'], + ); + $field = CRM_Core_BAO_CustomField::create($fields); + $this->_customFieldID = $field->id; + return $customGroup; + } + }