From 884a213de3574718f966cbd11254bc38a3dca70e Mon Sep 17 00:00:00 2001 From: eileen Date: Fri, 12 Jun 2020 11:50:33 +1200 Subject: [PATCH] [Ref] Convert Authorize.net to use Guzzle for update subscription, change subscription, add test This is part of the effort to expand our testing & standardise core processors --- CRM/Core/Payment/AuthorizeNet.php | 114 ++++---- .../CRM/Core/Payment/AuthorizeNetTest.php | 248 +++++++++++++++++- 2 files changed, 283 insertions(+), 79 deletions(-) diff --git a/CRM/Core/Payment/AuthorizeNet.php b/CRM/Core/Payment/AuthorizeNet.php index 26b0edc689..452db81259 100644 --- a/CRM/Core/Payment/AuthorizeNet.php +++ b/CRM/Core/Payment/AuthorizeNet.php @@ -326,7 +326,7 @@ class CRM_Core_Payment_AuthorizeNet extends CRM_Core_Payment { $responseFields = $this->_ParseArbReturn((string) $response->getBody()); if ($responseFields['resultCode'] === 'Error') { - throw new PaymentProcessorException($responseFields['code'], $responseFields['text']); + throw new PaymentProcessorException($responseFields['text'], $responseFields['code']); } // update recur processor_id with subscriptionId @@ -615,6 +615,7 @@ class CRM_Core_Payment_AuthorizeNet extends CRM_Core_Payment { * @param array $params * * @return bool|object + * @throws \Civi\Payment\Exception\PaymentProcessorException */ public function cancelSubscription(&$message = '', $params = []) { $template = CRM_Core_Smarty::singleton(); @@ -623,45 +624,39 @@ class CRM_Core_Payment_AuthorizeNet extends CRM_Core_Payment { $template->assign('apiLogin', $this->_getParam('apiLogin')); $template->assign('paymentKey', $this->_getParam('paymentKey')); - $template->assign('subscriptionId', CRM_Utils_Array::value('subscriptionId', $params)); + $template->assign('subscriptionId', $params['subscriptionId']); $arbXML = $template->fetch('CRM/Contribute/Form/Contribution/AuthorizeNetARB.tpl'); - // submit to authorize.net - $submit = curl_init($this->_paymentProcessor['url_recur']); - if (!$submit) { - return self::error(9002, 'Could not initiate connection to payment gateway'); - } - - curl_setopt($submit, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($submit, CURLOPT_HTTPHEADER, ["Content-Type: text/xml"]); - curl_setopt($submit, CURLOPT_HEADER, 1); - curl_setopt($submit, CURLOPT_POSTFIELDS, $arbXML); - curl_setopt($submit, CURLOPT_POST, 1); - curl_setopt($submit, CURLOPT_SSL_VERIFYPEER, Civi::settings()->get('verifySSL')); - - $response = curl_exec($submit); - - if (!$response) { - return self::error(curl_errno($submit), curl_error($submit)); - } - - curl_close($submit); + $response = (string) $this->getGuzzleClient()->post($this->_paymentProcessor['url_recur'], [ + 'headers' => [ + 'Content-Type' => 'text/xml; charset=UTF8', + ], + 'body' => $arbXML, + 'curl' => [ + CURLOPT_RETURNTRANSFER => TRUE, + CURLOPT_SSL_VERIFYPEER => Civi::settings()->get('verifySSL'), + ], + ])->getBody(); $responseFields = $this->_ParseArbReturn($response); $message = "{$responseFields['code']}: {$responseFields['text']}"; - if ($responseFields['resultCode'] == 'Error') { - return self::error($responseFields['code'], $responseFields['text']); + if ($responseFields['resultCode'] === 'Error') { + throw new PaymentProcessorException($responseFields['text'], $responseFields['code']); } return TRUE; } /** + * Update payment details at Authorize.net. + * * @param string $message * @param array $params * * @return bool|object + * + * @throws \Civi\Payment\Exception\PaymentProcessorException */ public function updateSubscriptionBillingInfo(&$message = '', $params = []) { $template = CRM_Core_Smarty::singleton(); @@ -686,32 +681,23 @@ class CRM_Core_Payment_AuthorizeNet extends CRM_Core_Payment { $arbXML = $template->fetch('CRM/Contribute/Form/Contribution/AuthorizeNetARB.tpl'); - // submit to authorize.net - $submit = curl_init($this->_paymentProcessor['url_recur']); - if (!$submit) { - return self::error(9002, 'Could not initiate connection to payment gateway'); - } - - curl_setopt($submit, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($submit, CURLOPT_HTTPHEADER, ["Content-Type: text/xml"]); - curl_setopt($submit, CURLOPT_HEADER, 1); - curl_setopt($submit, CURLOPT_POSTFIELDS, $arbXML); - curl_setopt($submit, CURLOPT_POST, 1); - curl_setopt($submit, CURLOPT_SSL_VERIFYPEER, Civi::settings()->get('verifySSL')); - - $response = curl_exec($submit); - - if (!$response) { - return self::error(curl_errno($submit), curl_error($submit)); - } - - curl_close($submit); + $response = (string) $this->getGuzzleClient()->post($this->_paymentProcessor['url_recur'], [ + 'headers' => [ + 'Content-Type' => 'text/xml; charset=UTF8', + ], + 'body' => $arbXML, + 'curl' => [ + CURLOPT_RETURNTRANSFER => TRUE, + CURLOPT_SSL_VERIFYPEER => Civi::settings()->get('verifySSL'), + ], + ])->getBody(); + // submit to authorize.net $responseFields = $this->_ParseArbReturn($response); $message = "{$responseFields['code']}: {$responseFields['text']}"; - if ($responseFields['resultCode'] == 'Error') { - return self::error($responseFields['code'], $responseFields['text']); + if ($responseFields['resultCode'] === 'Error') { + throw new PaymentProcessorException($responseFields['text'], $responseFields['code']); } return TRUE; } @@ -725,10 +711,14 @@ class CRM_Core_Payment_AuthorizeNet extends CRM_Core_Payment { } /** + * Change the amount of the recurring payment. + * * @param string $message * @param array $params * * @return bool|object + * + * @throws \Civi\Payment\Exception\PaymentProcessorException */ public function changeSubscriptionAmount(&$message = '', $params = []) { $template = CRM_Core_Smarty::singleton(); @@ -748,31 +738,21 @@ class CRM_Core_Payment_AuthorizeNet extends CRM_Core_Payment { $arbXML = $template->fetch('CRM/Contribute/Form/Contribution/AuthorizeNetARB.tpl'); - // submit to authorize.net - $submit = curl_init($this->_paymentProcessor['url_recur']); - if (!$submit) { - throw new PaymentProcessorException('Could not initiate connection to payment gateway', 9002); - } - - curl_setopt($submit, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($submit, CURLOPT_HTTPHEADER, ["Content-Type: text/xml"]); - curl_setopt($submit, CURLOPT_HEADER, 1); - curl_setopt($submit, CURLOPT_POSTFIELDS, $arbXML); - curl_setopt($submit, CURLOPT_POST, 1); - curl_setopt($submit, CURLOPT_SSL_VERIFYPEER, Civi::settings()->get('verifySSL')); - - $response = curl_exec($submit); - - if (!$response) { - return self::error(curl_errno($submit), curl_error($submit)); - } - - curl_close($submit); + $response = (string) $this->getGuzzleClient()->post($this->_paymentProcessor['url_recur'], [ + 'headers' => [ + 'Content-Type' => 'text/xml; charset=UTF8', + ], + 'body' => $arbXML, + 'curl' => [ + CURLOPT_RETURNTRANSFER => TRUE, + CURLOPT_SSL_VERIFYPEER => Civi::settings()->get('verifySSL'), + ], + ])->getBody(); $responseFields = $this->_parseArbReturn($response); $message = "{$responseFields['code']}: {$responseFields['text']}"; - if ($responseFields['resultCode'] == 'Error') { + if ($responseFields['resultCode'] === 'Error') { throw new PaymentProcessorException($responseFields['text'], $responseFields['code']); } return TRUE; diff --git a/tests/phpunit/CRM/Core/Payment/AuthorizeNetTest.php b/tests/phpunit/CRM/Core/Payment/AuthorizeNetTest.php index 8d6d21d6e1..3ef5c7ef9b 100644 --- a/tests/phpunit/CRM/Core/Payment/AuthorizeNetTest.php +++ b/tests/phpunit/CRM/Core/Payment/AuthorizeNetTest.php @@ -9,6 +9,8 @@ +--------------------------------------------------------------------+ */ +use Civi\Payment\PropertyBag; + /** * Class CRM_Core_Payment_AuthorizeNetTest * @group headless @@ -185,12 +187,6 @@ class CRM_Core_Payment_AuthorizeNetTest extends CiviUnitTestCase { 'id', 'Failed to create subscription with Authorize.' ); - // cancel it or the transaction will be rejected by A.net if the test is re-run - $subscriptionID = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_ContributionRecur', $recur['id'], 'processor_id'); - $message = ''; - $result = $this->processor->cancelSubscription($message, ['subscriptionId' => $subscriptionID]); - $this->assertTrue($result, 'Failed to cancel subscription with Authorize.'); - $requests = $this->getRequestBodies(); $this->assertEquals($this->getExpectedRequest($contactId, date('Y-m-d')), $requests[0]); $header = $this->getRequestHeaders()[0]; @@ -332,12 +328,6 @@ class CRM_Core_Payment_AuthorizeNetTest extends CiviUnitTestCase { 'id', 'Failed to create subscription with Authorize.' ); - // cancel it or the transaction will be rejected by A.net if the test is re-run - $subscriptionID = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_ContributionRecur', $recur['id'], 'processor_id'); - $message = ''; - $result = $this->processor->cancelSubscription($message, ['subscriptionId' => $subscriptionID]); - $this->assertTrue($result, 'Failed to cancel subscription with Authorize.'); - $response = $this->getResponseBodies(); $this->assertEquals($this->getExpectedResponse(), $response[0], 3); $requests = $this->getRequestBodies(); @@ -438,4 +428,238 @@ class CRM_Core_Payment_AuthorizeNetTest extends CiviUnitTestCase { ]; } + /** + * Test the update billing function. + */ + public function testUpdateBilling() { + $this->setUpClient($this->getExpectedUpdateResponse()); + $params = [ + 'qfKey' => '52e3078a34158a80b18d0e3c690c5b9f_2369', + 'entryURL' => 'http://dmaster.local/civicrm/contribute/updatebilling?reset=1&crid=2&cid=202&context=contribution', + 'credit_card_number' => '4444333322221111', + 'cvv2' => '123', + 'credit_card_exp_date' => ['M' => '3', 'Y' => '2022'], + 'credit_card_type' => 'Visa', + 'first_name' => 'q', + 'middle_name' => '', + 'last_name' => 't', + 'street_address' => 'y', + 'city' => 'xyz', + 'state_province_id' => '1587', + 'postal_code' => '777', + 'country_id' => '1006', + 'state_province' => 'Bengo', + 'country' => 'Angola', + 'month' => '3', + 'year' => '2022', + 'subscriptionId' => 6656444, + 'amount' => '6.00', + ]; + $message = ''; + $result = $this->processor->updateSubscriptionBillingInfo($message, $params); + $requests = $this->getRequestBodies(); + $this->assertEquals('I00001: Successful.', $message); + $this->assertTrue($result); + $this->assertEquals($this->getExpectedUpdateRequest(), $requests[0]); + } + + /** + * Test change subscription function. + * + * @throws \Civi\Payment\Exception\PaymentProcessorException + */ + public function testChangeSubscription() { + $this->setUpClient($this->getExpectedUpdateResponse()); + $params = [ + 'hidden_custom' => '1', + 'hidden_custom_group_count' => ['' => 1], + 'qfKey' => '38588554ecd5c01d5ecdedf3870d9100_7980', + 'entryURL' => 'http://dmaster.local/civicrm/contribute/updaterecur?reset=1&action=update&crid=2&cid=202&context=contribution', + 'amount' => '9.67', + 'currency' => 'USD', + 'installments' => '8', + 'is_notify' => '1', + 'financial_type_id' => '3', + '_qf_default' => 'UpdateSubscription:next', + '_qf_UpdateSubscription_next' => 'Save', + 'id' => '2', + 'subscriptionId' => 1234, + ]; + $message = ''; + $result = $this->processor->changeSubscriptionAmount($message, $params); + $requests = $this->getRequestBodies(); + $this->assertEquals('I00001: Successful.', $message); + $this->assertTrue($result); + $this->assertEquals($this->getExpectedChangeSubscriptionRequest(), $requests[0]); + } + + /** + * Get the expected request string for updateBilling. + * + * @return string + */ + public function getExpectedUpdateRequest() { + return ' + + + 4y5BfuW7jm + 4cAmW927n8uLf5J8 + + 6656444 + + + + 4444333322221111 + 2022-03 + + + + q + t +
y
+ xyz + Bengo + 777 + Angola +
+
+
+'; + } + + /** + * Get the expected response string for update billing. + * + * @return string + */ + public function getExpectedUpdateResponse() { + return 'HTTP/1.1 200 OK +Cache-Control: no-store +Pragma: no-cache +Content-Type: application/xml; charset=utf-8 +X-OPNET-Transaction-Trace: a2_4345e2c4-e273-46be-8517-8e6c8c408f5c-11416-3580701 +Access-Control-Allow-Credentials: true +Access-Control-Allow-Headers: x-requested-with,cache-control,content-type,origin,method,SOAPAction +Access-Control-Allow-Methods: PUT,OPTIONS,POST,GET +Access-Control-Allow-Origin: * +Strict-Transport-Security: max-age=31536000 +X-Cnection: close +Date: Thu, 11 Jun 2020 23:19:48 GMT +Content-Length: 557 + +OkI00001Successful.'; + } + + /** + * Get the expected outgoing request for changeSubscription. + * + * @return string + */ + protected function getExpectedChangeSubscriptionRequest() { + return ' + + + 4y5BfuW7jm + 4cAmW927n8uLf5J8 + +1234 + + + 8 + + 9.67 + + +'; + } + + /** + * Get the expected incoming response for changeSubscription. + * + * @return string + */ + protected function getExpectedChangeSubscriptionResponse() { + return 'HTTP/1.1 200 OK +Cache-Control: no-store +Pragma: no-cache +Content-Type: application/xml; charset=utf-8 +X-OPNET-Transaction-Trace: a2_e77aa7be-8f98-4f54-ba8a-e8a7f3d9e5ab-8400-7232961 +Access-Control-Allow-Credentials: true +Access-Control-Allow-Headers: x-requested-with,cache-control,content-type,origin,method,SOAPAction +Access-Control-Allow-Methods: PUT,OPTIONS,POST,GET +Access-Control-Allow-Origin: * +Strict-Transport-Security: max-age=31536000 +X-Cnection: close +Date: Fri, 12 Jun 2020 00:18:11 GMT +Content-Length: 492 + +OkI00001Successful.15122142631512250079'; + } + + /** + * Setup the guzzle client, helper. + * + * @param string $response + */ + protected function setUpClient($response) { + $this->createMockHandler([$response]); + $this->setUpClientWithHistoryContainer(); + $this->processor->setGuzzleClient($this->getGuzzleClient()); + } + + /** + * @throws \Civi\Payment\Exception\PaymentProcessorException + */ + public function testCancelRecurring() { + $this->setUpClient($this->getExpectedCancelResponse()); + $propertyBag = new PropertyBag(); + $propertyBag->setContributionRecurID(9); + $propertyBag->setIsNotifyProcessorOnCancelRecur(TRUE); + $propertyBag->setRecurProcessorID(6656333); + $this->processor->doCancelRecurring($propertyBag); + $requests = $this->getRequestBodies(); + $this->assertEquals($this->getExpectedCancelRequest(), $requests[0]); + } + + /** + * Get expected incoming cancel response. + * + * @return string + */ + protected function getExpectedCancelResponse() { + return 'HTTP/1.1 200 OK +Cache-Control: no-store +Pragma: no-cache +Content-Type: application/xml; charset=utf-8 +X-OPNET-Transaction-Trace: a2_e77aa7be-8f98-4f54-ba8a-e8a7f3d9e5ab-8400-7311552 +Access-Control-Allow-Credentials: true +Access-Control-Allow-Headers: x-requested-with,cache-control,content-type,origin,method,SOAPAction +Access-Control-Allow-Methods: PUT,OPTIONS,POST,GET +Access-Control-Allow-Origin: * +Strict-Transport-Security: max-age=31536000 +X-Cnection: close +Date: Fri, 12 Jun 2020 00:52:00 GMT +Content-Length: 361 + +OkI00001Successful.'; + } + + /** + * Get the expected outgoing cancel request. + * + * @return string + */ + protected function getExpectedCancelRequest() { + return ' + + + 4y5BfuW7jm + 4cAmW927n8uLf5J8 + + 6656333 + +'; + + } + } -- 2.25.1