From: Seamus Lee Date: Fri, 12 Oct 2018 00:25:47 +0000 (+1100) Subject: dev/financial#33 Add in Hook alterIPNData to allow Extensions to do custom processing... X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=2d39b9c013a18cefbbf1607638b77dcbf40f514d;p=civicrm-core.git 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 --- diff --git a/CRM/Core/Payment.php b/CRM/Core/Payment.php index 63ce4d9b5a..cf31189d38 100644 --- a/CRM/Core/Payment.php +++ b/CRM/Core/Payment.php @@ -1393,6 +1393,9 @@ abstract class CRM_Core_Payment { $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 92989a9025..e00b3f2f93 100644 --- a/CRM/Utils/Hook.php +++ b/CRM/Utils/Hook.php @@ -2473,4 +2473,17 @@ abstract class CRM_Utils_Hook { ); } + /** + * 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 9de32d4ea3..c9862c05ec 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 @@ class CRM_Core_Payment_PayPalIPNTest extends CiviUnitTestCase { 'first_name' => 'Robert', 'txn_id' => '8XA571746W2698126', 'residence_country' => 'US', + 'custom' => json_encode(['cgid' => 'test12345']), ); } @@ -249,4 +251,69 @@ class CRM_Core_Payment_PayPalIPNTest extends CiviUnitTestCase { 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; + } + }