3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
12 use Civi\Payment\PropertyBag
;
15 * Class CRM_Core_Payment_AuthorizeNetTest
18 class CRM_Core_Payment_AuthorizeNetTest
extends CiviUnitTestCase
{
20 use CRM_Core_Payment_AuthorizeNetTrait
;
22 public function setUp() {
24 $this->_paymentProcessorID
= $this->paymentProcessorAuthorizeNetCreate();
26 $this->processor
= Civi\Payment\System
::singleton()->getById($this->_paymentProcessorID
);
27 $this->_financialTypeId
= 1;
29 // for some strange unknown reason, in batch mode this value gets set to null
30 // so crude hack here to avoid an exception and hence an error
31 $GLOBALS['_PEAR_ERRORSTACK_OVERRIDE_CALLBACK'] = [];
34 public function tearDown() {
35 $this->quickCleanUpFinancialEntities();
39 * Test doing a one-off payment.
41 * @throws \Civi\Payment\Exception\PaymentProcessorException
42 * @throws \CiviCRM_API3_Exception
44 public function testSinglePayment() {
45 $this->setupMockHandler();
46 $params = $this->getBillingParams();
47 $params['amount'] = 5.24;
48 $this->processor
->doPayment($params);
49 $this->assertEquals($this->getExpectedSinglePaymentRequest(), $this->getRequestBodies()[0]);
53 * Create a single post dated payment as a recurring transaction.
55 * Test works but not both due to some form of caching going on in the SmartySingleton
57 public function testCreateSingleNowDated() {
58 $this->isRecur
= TRUE;
59 $this->setupMockHandler();
61 $lastName = "O\'Connor";
62 $nameParams = ['first_name' => 'John', 'last_name' => $lastName];
63 $contactId = $this->individualCreate($nameParams);
68 $recur = $this->callAPISuccess('ContributionRecur', 'create', [
69 'contact_id' => $contactId,
72 'frequency_unit' => 'week',
73 'frequency_interval' => 1,
75 'start_date' => date('Ymd'),
76 'create_date' => date('Ymd'),
77 'invoice_id' => $invoiceID,
78 'contribution_status_id' => 2,
80 'payment_processor_id' => $this->_paymentProcessorID
,
83 $contribution = $this->callAPISuccess('Contribution', 'create', [
84 'contact_id' => $contactId,
85 'financial_type_id' => 'Donation',
86 'receive_date' => date('Ymd'),
87 'total_amount' => $amount,
88 'invoice_id' => $invoiceID,
90 'contribution_recur_id' => $recur['id'],
92 'contribution_status_id' => 2,
95 $billingParams = $this->getBillingParams();
97 $params = array_merge($billingParams, [
98 'qfKey' => '08ed21c7ca00a1f7d32fff2488596ef7_4454',
99 'hidden_CreditCard' => 1,
101 'frequency_interval' => 1,
102 'frequency_unit' => 'month',
103 'installments' => 12,
104 'financial_type_id' => $this->_financialTypeId
,
105 'is_email_receipt' => 1,
106 'from_email_address' => 'john.smith@example.com',
107 'receive_date' => date('Ymd'),
108 'receipt_date_time' => '',
109 'payment_processor_id' => $this->_paymentProcessorID
,
110 'price_set_id' => '',
111 'total_amount' => $amount,
113 'source' => 'Mordor',
114 'soft_credit_to' => '',
115 'soft_contact_id' => '',
116 'billing_state_province-5' => 'IL',
117 'state_province-5' => 'IL',
118 'billing_country-5' => 'US',
122 'ip_address' => '127.0.0.1',
125 'currencyID' => 'USD',
126 'pcp_display_in_roll' => '',
127 'pcp_roll_nickname' => '',
128 'pcp_personal_note' => '',
129 'non_deductible_amount' => '',
132 'invoiceID' => $invoiceID,
133 'contribution_page_id' => '',
134 'thankyou_date' => NULL,
135 'honor_contact_id' => NULL,
136 'first_name' => $firstName,
138 'last_name' => $lastName,
139 'street_address' => '8 Hobbiton Road',
140 'city' => 'The Shire',
141 'state_province' => 'IL',
142 'postal_code' => 5010,
144 'contributionType_name' => 'My precious',
145 'contributionType_accounting_code' => '',
146 'contributionPageID' => '',
147 'email' => 'john.smith@example.com',
148 'contactID' => $contactId,
149 'contributionID' => $contribution['id'],
150 'contributionTypeID' => $this->_financialTypeId
,
151 'contributionRecurID' => $recur['id'],
154 // turn verifySSL off
155 Civi
::settings()->set('verifySSL', '0');
156 $this->processor
->doPayment($params);
158 Civi
::settings()->set('verifySSL', '0');
160 // if subscription was successful, processor_id / subscription-id must not be null
161 $this->assertDBNotNull('CRM_Contribute_DAO_ContributionRecur', $recur['id'], 'processor_id',
162 'id', 'Failed to create subscription with Authorize.'
165 $requests = $this->getRequestBodies();
166 $this->assertEquals($this->getExpectedRequest($contactId, date('Y-m-d')), $requests[0]);
167 $header = $this->getRequestHeaders()[0];
168 $this->assertEquals(['apitest.authorize.net'], $header['Host']);
169 $this->assertEquals(['text/xml; charset=UTF8'], $header['Content-Type']);
171 $this->assertEquals([
172 CURLOPT_RETURNTRANSFER
=> TRUE,
173 CURLOPT_SSL_VERIFYPEER
=> Civi
::settings()->get('verifySSL'),
174 ], $this->container
[0]['options']['curl']);
178 * Create a single post dated payment as a recurring transaction.
180 public function testCreateSinglePostDated() {
181 $this->isRecur
= TRUE;
182 $this->setupMockHandler();
183 $start_date = date('Ymd', strtotime('+ 1 week'));
186 $lastName = "O'Connor";
187 $nameParams = ['first_name' => $firstName, 'last_name' => $lastName];
188 $contactId = $this->individualCreate($nameParams);
193 $contributionRecurParams = [
194 'contact_id' => $contactId,
197 'frequency_unit' => 'month',
198 'frequency_interval' => 1,
200 'start_date' => $start_date,
201 'create_date' => date('Ymd'),
202 'invoice_id' => $invoiceID,
203 'contribution_status_id' => '',
205 'payment_processor_id' => $this->_paymentProcessorID
,
207 $recur = $this->callAPISuccess('ContributionRecur', 'create', $contributionRecurParams);
209 $contributionParams = [
210 'contact_id' => $contactId,
211 'financial_type_id' => $this->_financialTypeId
,
212 'receive_date' => $start_date,
213 'total_amount' => $amount,
214 'invoice_id' => $invoiceID,
216 'contribution_recur_id' => $recur['id'],
218 'contribution_status_id' => 2,
221 $contribution = $this->callAPISuccess('Contribution', 'create', $contributionParams);
224 'qfKey' => '00ed21c7ca00a1f7d555555596ef7_4454',
225 'hidden_CreditCard' => 1,
226 'billing_first_name' => $firstName,
227 'billing_middle_name' => '',
228 'billing_last_name' => $lastName,
229 'billing_street_address-5' => '8 Hobbitton Road',
230 'billing_city-5' => 'The Shire',
231 'billing_state_province_id-5' => 1012,
232 'billing_postal_code-5' => 5010,
233 'billing_country_id-5' => 1228,
234 'credit_card_number' => '4007000000027',
236 'credit_card_exp_date' => [
240 'credit_card_type' => 'Visa',
242 'frequency_interval' => 1,
243 'frequency_unit' => 'month',
245 'financial_type_id' => $this->_financialTypeId
,
246 'is_email_receipt' => 1,
247 'from_email_address' => "{$firstName}.{$lastName}@example.com",
248 'receive_date' => $start_date,
249 'receipt_date_time' => '',
250 'payment_processor_id' => $this->_paymentProcessorID
,
251 'price_set_id' => '',
252 'total_amount' => $amount,
254 'source' => 'Mordor',
255 'soft_credit_to' => '',
256 'soft_contact_id' => '',
257 'billing_state_province-5' => 'IL',
258 'state_province-5' => 'IL',
259 'billing_country-5' => 'US',
263 'ip_address' => '127.0.0.1',
266 'currencyID' => 'USD',
267 'pcp_display_in_roll' => '',
268 'pcp_roll_nickname' => '',
269 'pcp_personal_note' => '',
270 'non_deductible_amount' => '',
274 'contribution_page_id' => '',
275 'thankyou_date' => NULL,
276 'honor_contact_id' => NULL,
277 'invoiceID' => $invoiceID,
278 'first_name' => $firstName,
279 'middle_name' => 'bob',
280 'last_name' => $lastName,
281 'street_address' => '8 Hobbiton Road',
282 'city' => 'The Shire',
283 'state_province' => 'IL',
284 'postal_code' => 5010,
286 'contributionPageID' => '',
287 'email' => 'john.smith@example.com',
288 'contactID' => $contactId,
289 'contributionID' => $contribution['id'],
290 'contributionRecurID' => $recur['id'],
293 // if cancel-subscription has been called earlier 'subscriptionType' would be set to cancel.
294 // to make a successful call for another trxn, we need to set it to something else.
295 $smarty = CRM_Core_Smarty
::singleton();
296 $smarty->assign('subscriptionType', 'create');
298 $this->processor
->doPayment($params);
300 // if subscription was successful, processor_id / subscription-id must not be null
301 $this->assertDBNotNull('CRM_Contribute_DAO_ContributionRecur', $recur['id'], 'processor_id',
302 'id', 'Failed to create subscription with Authorize.'
305 $response = $this->getResponseBodies();
306 $this->assertEquals($this->getExpectedRecurResponse(), $response[0], 3);
307 $requests = $this->getRequestBodies();
308 $this->assertEquals($this->getExpectedRequest($contactId, date('Y-m-d', strtotime($start_date)), 70.23, 3, 4007000000027, '2022-10'), $requests[0]);
312 * Get the content that we expect to see sent out.
314 * @param int $contactID
315 * @param string $startDate
318 * @param int $occurrences
319 * @param int $cardNumber
320 * @param string $cardExpiry
324 public function getExpectedRequest($contactID, $startDate, $amount = 7, $occurrences = 12, $cardNumber = 4444333322221111, $cardExpiry = '2025-09') {
325 return '<?xml version="1.0" encoding="utf-8"?>
326 <ARBCreateSubscriptionRequest xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd">
327 <merchantAuthentication>
328 <name>4y5BfuW7jm</name>
329 <transactionKey>4cAmW927n8uLf5J8</transactionKey>
330 </merchantAuthentication>
331 <refId>123456</refId>
338 <startDate>' . $startDate . '</startDate>
339 <totalOccurrences>' . $occurrences . '</totalOccurrences>
341 <amount>' . $amount . '</amount>
344 <cardNumber>' . $cardNumber . '</cardNumber>
345 <expirationDate>' . $cardExpiry . '</expirationDate>
349 <invoiceNumber>1</invoiceNumber>
352 <id>' . $contactID . '</id>
353 <email>john.smith@example.com</email>
356 <firstName>John</firstName>
357 <lastName>O\'Connor</lastName>
358 <address>8 Hobbiton Road</address>
359 <city>The Shire</city>
362 <country>US</country>
365 </ARBCreateSubscriptionRequest>
370 * Get some basic billing parameters.
374 protected function getBillingParams(): array {
376 'billing_first_name' => 'John',
377 'billing_middle_name' => '',
378 'billing_last_name' => "O'Connor",
379 'billing_street_address-5' => '8 Hobbitton Road',
380 'billing_city-5' => 'The Shire',
381 'billing_state_province_id-5' => 1012,
382 'billing_postal_code-5' => 5010,
383 'billing_country_id-5' => 1228,
384 'credit_card_number' => '4444333322221111',
386 'credit_card_exp_date' => [
390 'credit_card_type' => 'Visa',
397 * Test the update billing function.
399 public function testUpdateBilling() {
400 $this->setUpClient($this->getExpectedUpdateResponse());
402 'qfKey' => '52e3078a34158a80b18d0e3c690c5b9f_2369',
403 'entryURL' => 'http://dmaster.local/civicrm/contribute/updatebilling?reset=1&crid=2&cid=202&context=contribution',
404 'credit_card_number' => '4444333322221111',
406 'credit_card_exp_date' => ['M' => '3', 'Y' => '2022'],
407 'credit_card_type' => 'Visa',
411 'street_address' => 'y',
413 'state_province_id' => '1587',
414 'postal_code' => '777',
415 'country_id' => '1006',
416 'state_province' => 'Bengo',
417 'country' => 'Angola',
420 'subscriptionId' => 6656444,
424 $result = $this->processor
->updateSubscriptionBillingInfo($message, $params);
425 $requests = $this->getRequestBodies();
426 $this->assertEquals('I00001: Successful.', $message);
427 $this->assertTrue($result);
428 $this->assertEquals($this->getExpectedUpdateRequest(), $requests[0]);
432 * Test change subscription function.
434 * @throws \Civi\Payment\Exception\PaymentProcessorException
436 public function testChangeSubscription() {
437 $this->setUpClient($this->getExpectedUpdateResponse());
439 'hidden_custom' => '1',
440 'hidden_custom_group_count' => ['' => 1],
441 'qfKey' => '38588554ecd5c01d5ecdedf3870d9100_7980',
442 'entryURL' => 'http://dmaster.local/civicrm/contribute/updaterecur?reset=1&action=update&crid=2&cid=202&context=contribution',
445 'installments' => '8',
447 'financial_type_id' => '3',
448 '_qf_default' => 'UpdateSubscription:next',
449 '_qf_UpdateSubscription_next' => 'Save',
451 'subscriptionId' => 1234,
454 $result = $this->processor
->changeSubscriptionAmount($message, $params);
455 $requests = $this->getRequestBodies();
456 $this->assertEquals('I00001: Successful.', $message);
457 $this->assertTrue($result);
458 $this->assertEquals($this->getExpectedChangeSubscriptionRequest(), $requests[0]);
462 * Get the expected request string for updateBilling.
466 public function getExpectedUpdateRequest() {
467 return '<?xml version="1.0" encoding="utf-8"?>
468 <ARBUpdateSubscriptionRequest xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd">
469 <merchantAuthentication>
470 <name>4y5BfuW7jm</name>
471 <transactionKey>4cAmW927n8uLf5J8</transactionKey>
472 </merchantAuthentication>
473 <subscriptionId>6656444</subscriptionId>
477 <cardNumber>4444333322221111</cardNumber>
478 <expirationDate>2022-03</expirationDate>
482 <firstName>q</firstName>
483 <lastName>t</lastName>
488 <country>Angola</country>
491 </ARBUpdateSubscriptionRequest>
496 * Get the expected response string for update billing.
500 public function getExpectedUpdateResponse() {
501 return 'HTTP/1.1 200 OK
502 Cache-Control: no-store
504 Content-Type: application/xml; charset=utf-8
505 X-OPNET-Transaction-Trace: a2_4345e2c4-e273-46be-8517-8e6c8c408f5c-11416-3580701
506 Access-Control-Allow-Credentials: true
507 Access-Control-Allow-Headers: x-requested-with,cache-control,content-type,origin,method,SOAPAction
508 Access-Control-Allow-Methods: PUT,OPTIONS,POST,GET
509 Access-Control-Allow-Origin: *
510 Strict-Transport-Security: max-age=31536000
512 Date: Thu, 11 Jun 2020 23:19:48 GMT
515 <?xml version="1.0" encoding="utf-8"?><resultCode>Ok</resultCode><message><code>I00001</code><text>Successful.</text>';
519 * Get the expected outgoing request for changeSubscription.
523 protected function getExpectedChangeSubscriptionRequest() {
524 return '<?xml version="1.0" encoding="utf-8"?>
525 <ARBUpdateSubscriptionRequest xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd">
526 <merchantAuthentication>
527 <name>4y5BfuW7jm</name>
528 <transactionKey>4cAmW927n8uLf5J8</transactionKey>
529 </merchantAuthentication>
530 <subscriptionId>1234</subscriptionId>
533 <totalOccurrences>8</totalOccurrences>
535 <amount>9.67</amount>
537 </ARBUpdateSubscriptionRequest>
542 * Get the expected incoming response for changeSubscription.
546 protected function getExpectedChangeSubscriptionResponse() {
547 return 'HTTP/1.1 200 OK
548 Cache-Control: no-store
550 Content-Type: application/xml; charset=utf-8
551 X-OPNET-Transaction-Trace: a2_e77aa7be-8f98-4f54-ba8a-e8a7f3d9e5ab-8400-7232961
552 Access-Control-Allow-Credentials: true
553 Access-Control-Allow-Headers: x-requested-with,cache-control,content-type,origin,method,SOAPAction
554 Access-Control-Allow-Methods: PUT,OPTIONS,POST,GET
555 Access-Control-Allow-Origin: *
556 Strict-Transport-Security: max-age=31536000
558 Date: Fri, 12 Jun 2020 00:18:11 GMT
561 <?xml version="1.0" encoding="utf-8"?><ARBUpdateSubscriptionResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"><messages><resultCode>Ok</resultCode><message><code>I00001</code><text>Successful.</text></message></messages><profile><customerProfileId>1512214263</customerProfileId><customerPaymentProfileId>1512250079</customerPaymentProfileId></profile></ARBUpdateSubscriptionResponse>';
565 * Setup the guzzle client, helper.
567 * @param string $response
569 protected function setUpClient($response) {
570 $this->createMockHandler([$response]);
571 $this->setUpClientWithHistoryContainer();
572 $this->processor
->setGuzzleClient($this->getGuzzleClient());
576 * @throws \Civi\Payment\Exception\PaymentProcessorException
578 public function testCancelRecurring() {
579 $this->setUpClient($this->getExpectedCancelResponse());
580 $propertyBag = new PropertyBag();
581 $propertyBag->setContributionRecurID(9);
582 $propertyBag->setIsNotifyProcessorOnCancelRecur(TRUE);
583 $propertyBag->setRecurProcessorID(6656333);
584 $this->processor
->doCancelRecurring($propertyBag);
585 $requests = $this->getRequestBodies();
586 $this->assertEquals($this->getExpectedCancelRequest(), $requests[0]);
590 * Get expected incoming cancel response.
594 protected function getExpectedCancelResponse() {
595 return 'HTTP/1.1 200 OK
596 Cache-Control: no-store
598 Content-Type: application/xml; charset=utf-8
599 X-OPNET-Transaction-Trace: a2_e77aa7be-8f98-4f54-ba8a-e8a7f3d9e5ab-8400-7311552
600 Access-Control-Allow-Credentials: true
601 Access-Control-Allow-Headers: x-requested-with,cache-control,content-type,origin,method,SOAPAction
602 Access-Control-Allow-Methods: PUT,OPTIONS,POST,GET
603 Access-Control-Allow-Origin: *
604 Strict-Transport-Security: max-age=31536000
606 Date: Fri, 12 Jun 2020 00:52:00 GMT
609 <?xml version="1.0" encoding="utf-8"?><ARBCancelSubscriptionResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"><messages><resultCode>Ok</resultCode><message><code>I00001</code><text>Successful.</text></message></messages></ARBCancelSubscriptionResponse>';
613 * Get the expected outgoing cancel request.
617 protected function getExpectedCancelRequest() {
618 return '<?xml version="1.0" encoding="utf-8"?>
619 <ARBCancelSubscriptionRequest xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd">
620 <merchantAuthentication>
621 <name>4y5BfuW7jm</name>
622 <transactionKey>4cAmW927n8uLf5J8</transactionKey>
623 </merchantAuthentication>
624 <subscriptionId>6656333</subscriptionId>
625 </ARBCancelSubscriptionRequest>