protected $_params = [];
+ /**
+ * @var GuzzleHttp\Client
+ */
+ protected $guzzleClient;
+
+ /**
+ * @return \GuzzleHttp\Client
+ */
+ public function getGuzzleClient(): \GuzzleHttp\Client {
+ return $this->guzzleClient ?? new \GuzzleHttp\Client();
+ }
+
+ /**
+ * @param \GuzzleHttp\Client $guzzleClient
+ */
+ public function setGuzzleClient(\GuzzleHttp\Client $guzzleClient) {
+ $this->guzzleClient = $guzzleClient;
+ }
+
/**
* We only need one instance of this object. So we use the singleton
* pattern and cache the instance in this variable
*
* @return array
* the result in a nice formatted array (or an error object)
+ *
+ * @throws \Civi\Payment\Exception\PaymentProcessorException
*/
public function doDirectPayment(&$params) {
if (!defined('CURLOPT_SSLCERT')) {
$template->assign('billingCountry', $this->_getParam('country'));
$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);
- $responseFields = $this->_ParseArbReturn($response);
-
- if ($responseFields['resultCode'] == 'Error') {
- return self::error($responseFields['code'], $responseFields['text']);
+ // Submit to authorize.net
+ $response = $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'),
+ ],
+ ]);
+ $responseFields = $this->_ParseArbReturn((string) $response->getBody());
+
+ if ($responseFields['resultCode'] === 'Error') {
+ throw new PaymentProcessorException($responseFields['code'], $responseFields['text']);
}
// update recur processor_id with subscriptionId
--- /dev/null
+<?php
+
+namespace Civi\Test;
+
+use GuzzleHttp\Handler\MockHandler;
+use GuzzleHttp\HandlerStack;
+use GuzzleHttp\Psr7\Response;
+use GuzzleHttp\Middleware;
+use GuzzleHttp\Client;
+
+/**
+ * Class GuzzleTestTrait
+ *
+ * This trait defines a number of helper functions for testing guzzle.
+ */
+trait GuzzleTestTrait {
+ /**
+ * @var \GuzzleHttp\Client
+ */
+ protected $guzzleClient;
+
+ /**
+ * Array containing guzzle history of requests and responses.
+ *
+ * @var array
+ */
+ protected $container;
+
+ /**
+ * Mockhandler to simulate guzzle requests.
+ *
+ * @var \GuzzleHttp\Handler\MockHandler
+ */
+ protected $mockHandler;
+
+ /**
+ * The url to mock-interact with.
+ *
+ * @var string
+ */
+ protected $baseUri;
+
+ /**
+ * @return \GuzzleHttp\Client
+ */
+ public function getGuzzleClient() {
+ return $this->guzzleClient;
+ }
+
+ /**
+ * @param \GuzzleHttp\Client $guzzleClient
+ */
+ public function setGuzzleClient($guzzleClient) {
+ $this->guzzleClient = $guzzleClient;
+ }
+
+ /**
+ * @return array
+ */
+ public function getContainer() {
+ return $this->container;
+ }
+
+ /**
+ * @param array $container
+ */
+ public function setContainer($container) {
+ $this->container = $container;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getBaseUri() {
+ return $this->baseUri;
+ }
+
+ /**
+ * @param mixed $baseUri
+ */
+ public function setBaseUri($baseUri) {
+ $this->baseUri = $baseUri;
+ }
+
+ /**
+ * @return \GuzzleHttp\Handler\MockHandler
+ */
+ public function getMockHandler() {
+ return $this->mockHandler;
+ }
+
+ /**
+ * @param \GuzzleHttp\Handler\MockHandler $mockHandler
+ */
+ public function setMockHandler($mockHandler) {
+ $this->mockHandler = $mockHandler;
+ }
+
+ /**
+ * @param $responses
+ */
+ protected function createMockHandler($responses) {
+ $mocks = [];
+ foreach ($responses as $response) {
+ $mocks[] = new Response(200, [], $response);
+ }
+ $this->setMockHandler(new MockHandler($mocks));
+ }
+
+ /**
+ * @param $files
+ */
+ protected function createMockHandlerForFiles($files) {
+ $body = [];
+ foreach ($files as $file) {
+ $body[] = trim(file_get_contents(__DIR__ . $file));
+ }
+ $this->createMockHandler($body);
+ }
+
+ /**
+ * Set up a guzzle client with a history container.
+ *
+ * After you have run the requests you can inspect $this->container
+ * for the outgoing requests and incoming responses.
+ *
+ * If $this->mock is defined then no outgoing http calls will be made
+ * and the responses configured on the handler will be returned instead
+ * of replies from a remote provider.
+ */
+ protected function setUpClientWithHistoryContainer() {
+ $this->container = [];
+ $history = Middleware::history($this->container);
+ $handler = HandlerStack::create($this->getMockHandler());
+ $handler->push($history);
+ $this->guzzleClient = new Client(['base_uri' => $this->baseUri, 'handler' => $handler]);
+ }
+
+ /**
+ * Get the bodies of the requests sent via Guzzle.
+ *
+ * @return array
+ */
+ protected function getRequestBodies() {
+ $requests = [];
+ foreach ($this->getContainer() as $guzzle) {
+ $requests[] = (string) $guzzle['request']->getBody();
+ }
+ return $requests;
+ }
+
+ /**
+ * Get the bodies of the requests sent via Guzzle.
+ *
+ * @return array
+ */
+ protected function getRequestHeaders() {
+ $requests = [];
+ foreach ($this->getContainer() as $guzzle) {
+ $requests[] = $guzzle['request']->getHeaders();
+ }
+ return $requests;
+ }
+
+ /**
+ * Get the bodies of the requests sent via Guzzle.
+ *
+ * @return array
+ */
+ protected function getRequestUrls() {
+ $requests = [];
+ foreach ($this->getContainer() as $guzzle) {
+ $requests[] = (string) $guzzle['request']->getUri();
+ }
+ return $requests;
+ }
+
+ /**
+ * Get the bodies of the responses returned via Guzzle.
+ *
+ * @return array
+ */
+ protected function getResponseBodies() {
+ $responses = [];
+ foreach ($this->getContainer() as $guzzle) {
+ $responses[] = (string) $guzzle['response']->getBody();
+ }
+ return $responses;
+ }
+
+}
*/
class CRM_Core_Payment_AuthorizeNetTest extends CiviUnitTestCase {
+ use \Civi\Test\GuzzleTestTrait;
+
+ /**
+ * @var \CRM_Core_Payment_AuthorizeNet
+ */
+ protected $processor;
+
public function setUp() {
parent::setUp();
$this->_paymentProcessorID = $this->paymentProcessorAuthorizeNetCreate();
* Test works but not both due to some form of caching going on in the SmartySingleton
*/
public function testCreateSingleNowDated() {
- $firstName = 'John_' . substr(sha1(rand()), 0, 7) . uniqid();
- $lastName = 'Smith_' . substr(sha1(rand()), 0, 7) . uniqid();
- $nameParams = ['first_name' => $firstName, 'last_name' => $lastName];
+ $this->createMockHandler([$this->getExpectedResponse()]);
+ $this->setUpClientWithHistoryContainer();
+ $this->processor->setGuzzleClient($this->getGuzzleClient());
+ $firstName = 'John';
+ $lastName = 'Smith';
+ $nameParams = ['first_name' => 'John', 'last_name' => $lastName];
$contactId = $this->individualCreate($nameParams);
- $invoiceID = sha1(rand());
+ $invoiceID = 123456;
$amount = rand(100, 1000) . '.00';
$recur = $this->callAPISuccess('ContributionRecur', 'create', [
'qfKey' => '08ed21c7ca00a1f7d32fff2488596ef7_4454',
'hidden_CreditCard' => 1,
'billing_first_name' => $firstName,
- 'billing_middle_name' => "",
+ 'billing_middle_name' => '',
'billing_last_name' => $lastName,
'billing_street_address-5' => '8 Hobbitton Road',
'billing_city-5' => 'The Shire',
'installments' => 12,
'financial_type_id' => $this->_financialTypeId,
'is_email_receipt' => 1,
- 'from_email_address' => "{$firstName}.{$lastName}@example.com",
+ 'from_email_address' => 'john.smith@example.com',
'receive_date' => date('Ymd'),
'receipt_date_time' => '',
'payment_processor_id' => $this->_paymentProcessorID,
'price_set_id' => '',
'total_amount' => $amount,
'currency' => 'USD',
- 'source' => "Mordor",
+ 'source' => 'Mordor',
'soft_credit_to' => '',
'soft_contact_id' => '',
'billing_state_province-5' => 'IL',
'amount' => 7,
'amount_level' => 0,
'currencyID' => 'USD',
- 'pcp_display_in_roll' => "",
- 'pcp_roll_nickname' => "",
- 'pcp_personal_note' => "",
- 'non_deductible_amount' => "",
- 'fee_amount' => "",
- 'net_amount' => "",
+ 'pcp_display_in_roll' => '',
+ 'pcp_roll_nickname' => '',
+ 'pcp_personal_note' => '',
+ 'non_deductible_amount' => '',
+ 'fee_amount' => '',
+ 'net_amount' => '',
'invoiceID' => $invoiceID,
- 'contribution_page_id' => "",
+ 'contribution_page_id' => '',
'thankyou_date' => NULL,
'honor_contact_id' => NULL,
'first_name' => $firstName,
'middle_name' => '',
'last_name' => $lastName,
- 'street_address' => '8 Hobbiton Road' . uniqid(),
+ 'street_address' => '8 Hobbiton Road',
'city' => 'The Shire',
'state_province' => 'IL',
'postal_code' => 5010,
$message = '';
$result = $this->processor->cancelSubscription($message, ['subscriptionId' => $subscriptionID]);
$this->assertTrue($result, 'Failed to cancel subscription with Authorize.');
+
+ $requests = $this->getRequestBodies();
+ $this->assertEquals($this->getExpectedRequest(date('Y-m-d')), $requests[0]);
+ $header = $this->getRequestHeaders()[0];
+ $this->assertEquals(['apitest.authorize.net'], $header['Host']);
+ $this->assertEquals(['text/xml; charset=UTF8'], $header['Content-Type']);
+
+ $this->assertEquals([
+ CURLOPT_RETURNTRANSFER => TRUE,
+ CURLOPT_SSL_VERIFYPEER => Civi::settings()->get('verifySSL'),
+ ], $this->container[0]['options']['curl']);
}
/**
}
}
+ /**
+ * Get the content that we expect to see sent out.
+ *
+ * @param string $startDate
+ *
+ * @return string
+ */
+ public function getExpectedRequest($startDate) {
+ return '<?xml version="1.0" encoding="utf-8"?>
+<ARBCreateSubscriptionRequest xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd">
+ <merchantAuthentication>
+ <name>4y5BfuW7jm</name>
+ <transactionKey>4cAmW927n8uLf5J8</transactionKey>
+ </merchantAuthentication>
+ <refId>123456</refId>
+ <subscription>
+ <paymentSchedule>
+ <interval>
+ <length>1</length>
+ <unit>months</unit>
+ </interval>
+ <startDate>' . $startDate . '</startDate>
+ <totalOccurrences>12</totalOccurrences>
+ </paymentSchedule>
+ <amount>7</amount>
+ <payment>
+ <creditCard>
+ <cardNumber>4444333322221111</cardNumber>
+ <expirationDate>2025-09</expirationDate>
+ </creditCard>
+ </payment>
+ <order>
+ <invoiceNumber>1</invoiceNumber>
+ </order>
+ <customer>
+ <id>3</id>
+ <email>John.Smith@example.com</email>
+ </customer>
+ <billTo>
+ <firstName>John</firstName>
+ <lastName>Smith</lastName>
+ <address>8 Hobbiton Road</address>
+ <city>The Shire</city>
+ <state>IL</state>
+ <zip>5010</zip>
+ <country>US</country>
+ </billTo>
+ </subscription>
+</ARBCreateSubscriptionRequest>
+';
+ }
+
+ /**
+ * Get a successful response to setting up a recurring.
+ *
+ * @return string
+ */
+ public function getExpectedResponse() {
+ return '<?xml version="1.0" encoding="utf-8"?><ARBCreateSubscriptionResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"><refId>8d468ca1b1dd5c2b56c7</refId><messages><resultCode>Ok</resultCode><message><code>I00001</code><text>Successful.</text></message></messages><subscriptionId>6632052</subscriptionId><profile><customerProfileId>1512023280</customerProfileId><customerPaymentProfileId>1512027350</customerPaymentProfileId></profile></ARBCreateSubscriptionResponse>';
+ }
+
}