Merge pull request #16877 from agileware/CIVICRM-1457
[civicrm-core.git] / tests / phpunit / CRM / Core / Payment / AuthorizeNetIPNTest.php
1 <?php
2
3 use Civi\Payment\Exception\PaymentProcessorException;
4
5 /**
6 * Class CRM_Core_Payment_PayPalProIPNTest
7 * @group headless
8 */
9 class CRM_Core_Payment_AuthorizeNetIPNTest extends CiviUnitTestCase {
10 use CRMTraits_Financial_OrderTrait;
11
12 protected $_contributionID;
13 protected $_invoiceID = 'c2r9c15f7be20b4f3fef1f77e4c37424';
14 protected $_financialTypeID = 1;
15 protected $_contactID;
16 protected $_contributionRecurID;
17 protected $_contributionPageID;
18 protected $_paymentProcessorID;
19
20 /**
21 *
22 * @throws \CRM_Core_Exception
23 */
24 public function setUp() {
25 parent::setUp();
26 $this->_paymentProcessorID = $this->paymentProcessorAuthorizeNetCreate(['is_test' => 0]);
27 $this->_contactID = $this->individualCreate();
28 $contributionPage = $this->callAPISuccess('contribution_page', 'create', [
29 'title' => 'Test Contribution Page',
30 'financial_type_id' => $this->_financialTypeID,
31 'currency' => 'USD',
32 'payment_processor' => $this->_paymentProcessorID,
33 'max_amount' => 1000,
34 'receipt_from_email' => 'gaia@the.cosmos',
35 'receipt_from_name' => 'Pachamama',
36 'is_email_receipt' => TRUE,
37 ]);
38 $this->_contributionPageID = $contributionPage['id'];
39 }
40
41 public function tearDown() {
42 $this->quickCleanUpFinancialEntities();
43 }
44
45 /**
46 * Ensure recurring contributions from Contribution Pages
47 * with receipt turned off don't send a receipt.
48 *
49 * @throws \CiviCRM_API3_Exception
50 * @throws \CRM_Core_Exception
51 */
52 public function testIPNPaymentRecurNoReceipt() {
53 $mut = new CiviMailUtils($this, TRUE);
54 // Turn off receipts in contribution page.
55 $api_params = [
56 'id' => $this->_contributionPageID,
57 'is_email_receipt' => FALSE,
58 ];
59 $this->callAPISuccess('contributionPage', 'update', $api_params);
60
61 // Create initial recurring payment and initial contribution.
62 // Note - we can't use setupRecurringPaymentProcessorTransaction(), which
63 // would be convenient because it does not fully mimic the real user
64 // experience. Using setupRecurringPaymentProcessorTransaction() doesn't
65 // specify is_email_receipt so it is always set to 1. We need to more
66 // closely mimic what happens with a live transaction to test that
67 // is_email_receipt is not set to 1 if the originating contribution page
68 // has is_email_receipt set to 0.
69 $form = new CRM_Contribute_Form_Contribution();
70 $form->_mode = 'Live';
71 try {
72 $contribution = $form->testSubmit([
73 'total_amount' => 200,
74 'financial_type_id' => 1,
75 'receive_date' => date('m/d/Y'),
76 'receive_date_time' => date('H:i:s'),
77 'contact_id' => $this->_contactID,
78 'contribution_status_id' => 1,
79 'credit_card_number' => 4444333322221111,
80 'cvv2' => 123,
81 'credit_card_exp_date' => [
82 'M' => 9,
83 'Y' => 2025,
84 ],
85 'credit_card_type' => 'Visa',
86 'billing_first_name' => 'Junko',
87 'billing_middle_name' => '',
88 'billing_last_name' => 'Adams',
89 'billing_street_address-5' => time() . ' Lincoln St S',
90 'billing_city-5' => 'Maryknoll',
91 'billing_state_province_id-5' => 1031,
92 'billing_postal_code-5' => 10545,
93 'billing_country_id-5' => 1228,
94 'frequency_interval' => 1,
95 'frequency_unit' => 'month',
96 'installments' => 2,
97 'hidden_AdditionalDetail' => 1,
98 'hidden_Premium' => 1,
99 'payment_processor_id' => $this->_paymentProcessorID,
100 'currency' => 'USD',
101 'source' => 'bob sled race',
102 'contribution_page_id' => $this->_contributionPageID,
103 'is_recur' => TRUE,
104 ], CRM_Core_Action::ADD);
105 }
106 catch (PaymentProcessorException $e) {
107 $this->markTestSkipped('Error from A.net - cannot proceed');
108 }
109 $this->_contributionID = $contribution->id;
110 $this->ids['Contribution'][0] = $contribution->id;
111 $this->_contributionRecurID = $contribution->contribution_recur_id;
112
113 $contributionRecur = $this->callAPISuccessGetSingle('ContributionRecur', ['id' => $this->_contributionRecurID]);
114 $processor_id = $contributionRecur['processor_id'];
115 $this->assertEquals('Pending', CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $contributionRecur['contribution_status_id']));
116 // Process the initial one after a second's break to ensure modified date really is later.
117 sleep(1);
118 $IPN = new CRM_Core_Payment_AuthorizeNetIPN(
119 $this->getRecurTransaction(['x_subscription_id' => $processor_id])
120 );
121 $IPN->main();
122 $updatedContributionRecur = $this->callAPISuccessGetSingle('ContributionRecur', ['id' => $this->_contributionRecurID]);
123 $this->assertEquals('In Progress', CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_ContributionRecur', 'contribution_status_id', $updatedContributionRecur['contribution_status_id']));
124 $this->assertTrue(strtotime($updatedContributionRecur['modified_date']) > strtotime($contributionRecur['modified_date']));
125
126 // Now send a second one (authorize seems to treat first and second contributions
127 // differently.
128 $IPN = new CRM_Core_Payment_AuthorizeNetIPN($this->getRecurSubsequentTransaction([
129 'x_subscription_id' => $processor_id,
130 'x_subscription_paynum' => 2,
131 ]));
132 $IPN->main();
133 $updatedContributionRecurAgain = $this->callAPISuccessGetSingle('ContributionRecur', ['id' => $this->_contributionRecurID]);
134 $this->assertEquals('Completed', CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_ContributionRecur', 'contribution_status_id', $updatedContributionRecurAgain['contribution_status_id']));
135 $this->assertEquals(date('Y-m-d'), substr($updatedContributionRecurAgain['end_date'], 0, 10));
136 // There should not be any email.
137 $mut->assertMailLogEmpty();
138 }
139
140 /**
141 * Test IPN response updates contribution_recur & contribution for first & second contribution
142 *
143 * @throws \CRM_Core_Exception
144 */
145 public function testIPNPaymentRecurSuccess() {
146 $this->setupRecurringPaymentProcessorTransaction();
147 $IPN = new CRM_Core_Payment_AuthorizeNetIPN($this->getRecurTransaction());
148 $IPN->main();
149 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['id' => $this->_contributionID]);
150 $this->assertEquals(1, $contribution['contribution_status_id']);
151 $this->assertEquals('6511143069', $contribution['trxn_id']);
152 // source gets set by processor
153 $this->assertTrue(substr($contribution['contribution_source'], 0, 20) == "Online Contribution:");
154 $contributionRecur = $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $this->_contributionRecurID]);
155 $this->assertEquals(5, $contributionRecur['contribution_status_id']);
156 $IPN = new CRM_Core_Payment_AuthorizeNetIPN($this->getRecurSubsequentTransaction());
157 $IPN->main();
158 $contribution = $this->callAPISuccess('contribution', 'get', [
159 'contribution_recur_id' => $this->_contributionRecurID,
160 'sequential' => 1,
161 ]);
162 $this->assertEquals(2, $contribution['count']);
163 $this->assertEquals('second_one', $contribution['values'][1]['trxn_id']);
164 $this->assertEquals(date('Y-m-d'), date('Y-m-d', strtotime($contribution['values'][1]['receive_date'])));
165 }
166
167 /**
168 * Test payment processor is correctly assigned for the IPN payment.
169 */
170 public function testIPNPaymentRecurSuccessMultiAuthNetProcessor() {
171 //Create and set up recur payment using second instance of AuthNet Processor.
172 $this->_paymentProcessorID2 = $this->paymentProcessorAuthorizeNetCreate(['name' => 'Authorize2', 'is_test' => 0]);
173 $this->setupRecurringPaymentProcessorTransaction(['payment_processor_id' => $this->_paymentProcessorID2]);
174
175 //Call IPN with processor id.
176 $IPN = new CRM_Core_Payment_AuthorizeNetIPN($this->getRecurTransaction(['processor_id' => $this->_paymentProcessorID2]));
177 $IPN->main();
178 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['id' => $this->_contributionID]);
179 $this->assertEquals(1, $contribution['contribution_status_id']);
180 $this->assertEquals('6511143069', $contribution['trxn_id']);
181 // source gets set by processor
182 $this->assertTrue(substr($contribution['contribution_source'], 0, 20) == "Online Contribution:");
183 $contributionRecur = $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $this->_contributionRecurID]);
184 $this->assertEquals(5, $contributionRecur['contribution_status_id']);
185 }
186
187 /**
188 * Test IPN response updates contribution_recur & contribution for first & second contribution
189 *
190 * @throws \CRM_Core_Exception
191 */
192 public function testIPNPaymentRecurSuccessSuppliedReceiveDate() {
193 $this->setupRecurringPaymentProcessorTransaction();
194 $IPN = new CRM_Core_Payment_AuthorizeNetIPN($this->getRecurTransaction());
195 $IPN->main();
196 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['id' => $this->_contributionID]);
197 $this->assertEquals(1, $contribution['contribution_status_id']);
198 $this->assertEquals('6511143069', $contribution['trxn_id']);
199 // source gets set by processor
200 $this->assertTrue(substr($contribution['contribution_source'], 0, 20) == "Online Contribution:");
201 $contributionRecur = $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $this->_contributionRecurID]);
202 $this->assertEquals(5, $contributionRecur['contribution_status_id']);
203 $IPN = new CRM_Core_Payment_AuthorizeNetIPN(array_merge(['receive_date' => '2010-07-01'], $this->getRecurSubsequentTransaction()));
204 $IPN->main();
205 $contribution = $this->callAPISuccess('contribution', 'get', [
206 'contribution_recur_id' => $this->_contributionRecurID,
207 'sequential' => 1,
208 ]);
209 $this->assertEquals(2, $contribution['count']);
210 $this->assertEquals('second_one', $contribution['values'][1]['trxn_id']);
211 $this->assertEquals('2010-07-01', date('Y-m-d', strtotime($contribution['values'][1]['receive_date'])));
212 }
213
214 /**
215 * Test IPN response updates contribution_recur & contribution for first & second contribution
216 *
217 * @throws \CRM_Core_Exception
218 */
219 public function testIPNPaymentMembershipRecurSuccess() {
220 $this->createRepeatMembershipOrder();
221 $IPN = new CRM_Core_Payment_AuthorizeNetIPN($this->getRecurTransaction());
222 $IPN->main();
223 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['id' => $this->ids['Contribution'][0]]);
224 $this->assertEquals(1, $contribution['contribution_status_id']);
225 $this->assertEquals('6511143069', $contribution['trxn_id']);
226
227 // source gets set by processor
228 $this->assertEquals('Online Contribution:', substr($contribution['contribution_source'], 0, 20));
229 $contributionRecur = $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $this->_contributionRecurID]);
230 $this->assertEquals(5, $contributionRecur['contribution_status_id']);
231 $IPN = new CRM_Core_Payment_AuthorizeNetIPN($this->getRecurSubsequentTransaction());
232 $IPN->main();
233 $contribution = $this->callAPISuccess('contribution', 'get', [
234 'contribution_recur_id' => $this->_contributionRecurID,
235 'sequential' => 1,
236 ]);
237 $this->assertEquals(2, $contribution['count']);
238 // Ensure both contributions are coded as credit card contributions.
239 $this->assertEquals(1, $contribution['values'][0]['payment_instrument_id']);
240 $this->assertEquals(1, $contribution['values'][1]['payment_instrument_id']);
241 $this->assertEquals('second_one', $contribution['values'][1]['trxn_id']);
242 $this->callAPISuccessGetSingle('membership_payment', ['contribution_id' => $contribution['values'][1]['id']]);
243 $this->callAPISuccessGetSingle('line_item', [
244 'contribution_id' => $contribution['values'][1]['id'],
245 'entity_table' => 'civicrm_membership',
246 ]);
247 $this->validateAllContributions();
248 $this->validateAllPayments();
249 }
250
251 /**
252 * Test IPN response mails don't leak.
253 *
254 * @throws \CRM_Core_Exception
255 */
256 public function testIPNPaymentMembershipRecurSuccessNoLeakage() {
257 $mut = new CiviMailUtils($this, TRUE);
258 $this->setupMembershipRecurringPaymentProcessorTransaction(['is_email_receipt' => TRUE]);
259 $this->addProfile('supporter_profile', $this->_contributionPageID);
260 $this->addProfile('honoree_individual', $this->_contributionPageID, 'soft_credit');
261
262 $this->callAPISuccess('ContributionSoft', 'create', [
263 'contact_id' => $this->individualCreate(),
264 'contribution_id' => $this->_contributionID,
265 'soft_credit_type_id' => 'in_memory_of',
266 'amount' => 200,
267 ]);
268
269 $IPN = new CRM_Core_Payment_AuthorizeNetIPN($this->getRecurTransaction());
270 $IPN->main();
271 $mut->checkAllMailLog([
272 'Membership Type: General',
273 'Mr. Anthony Anderson II" <anthony_anderson@civicrm.org>',
274 'Amount: $ 200.00',
275 'Membership Start Date:',
276 'Supporter Profile',
277 'First Name: Anthony',
278 'Last Name: Anderson',
279 'Email Address: anthony_anderson@civicrm.org',
280 'Honor',
281 'This membership will be automatically renewed every',
282 'Dear Anthony',
283 'Thanks for your auto renew membership sign-up',
284 'In Memory of',
285 ]);
286 $mut->clearMessages();
287 $this->_contactID = $this->individualCreate(['first_name' => 'Antonia', 'prefix_id' => 'Mrs.', 'email' => 'antonia_anderson@civicrm.org']);
288 $this->_invoiceID = uniqid();
289
290 // Note, the second contribution is not in honor of anyone and the
291 // receipt should not mention honor at all.
292 $this->setupMembershipRecurringPaymentProcessorTransaction(['is_email_receipt' => TRUE]);
293 $IPN = new CRM_Core_Payment_AuthorizeNetIPN($this->getRecurTransaction(['x_trans_id' => 'hers']));
294 $IPN->main();
295
296 $mut->checkAllMailLog([
297 'Membership Type: General',
298 'Mrs. Antonia Anderson II',
299 'antonia_anderson@civicrm.org',
300 'Amount: $ 200.00',
301 'Membership Start Date:',
302 'Transaction #: hers',
303 'Supporter Profile',
304 'First Name: Antonia',
305 'Last Name: Anderson',
306 'Email Address: antonia_anderson@civicrm.org',
307 'This membership will be automatically renewed every',
308 'Dear Antonia',
309 'Thanks for your auto renew membership sign-up',
310 ]);
311
312 $shouldNotBeInMailing = [
313 'Honor',
314 'In Memory of',
315 ];
316 $mails = $mut->getAllMessages('raw');
317 foreach ($mails as $mail) {
318 $mut->checkMailForStrings([], $shouldNotBeInMailing, '', $mail);
319 }
320 $mut->stop();
321 $mut->clearMessages();
322 }
323
324 /**
325 * Test IPN response mails don't leak.
326 */
327 public function testIPNPaymentMembershipRecurSuccessNoLeakageOnlineThenOffline() {
328 $mut = new CiviMailUtils($this, TRUE);
329 $this->setupMembershipRecurringPaymentProcessorTransaction(['is_email_receipt' => TRUE]);
330 $this->addProfile('supporter_profile', $this->_contributionPageID);
331 $IPN = new CRM_Core_Payment_AuthorizeNetIPN($this->getRecurTransaction());
332 $IPN->main();
333 $mut->checkAllMailLog([
334 'Membership Type: General',
335 'Mr. Anthony Anderson II" <anthony_anderson@civicrm.org>',
336 'Amount: $ 200.00',
337 'Membership Start Date:',
338 'Supporter Profile',
339 'First Name: Anthony',
340 'Last Name: Anderson',
341 'Email Address: anthony_anderson@civicrm.org',
342 'This membership will be automatically renewed every',
343 'Dear Anthony',
344 'Thanks for your auto renew membership sign-up',
345 ]);
346
347 $this->_contactID = $this->individualCreate(['first_name' => 'Antonia', 'prefix_id' => 'Mrs.', 'email' => 'antonia_anderson@civicrm.org']);
348 $this->_invoiceID = uniqid();
349 $this->_contributionPageID = NULL;
350
351 $this->setupMembershipRecurringPaymentProcessorTransaction(['is_email_receipt' => TRUE]);
352 $mut->clearMessages();
353 $IPN = new CRM_Core_Payment_AuthorizeNetIPN($this->getRecurTransaction(['x_trans_id' => 'hers']));
354 $IPN->main();
355
356 $mut->checkAllMailLog([
357 'Membership Type: General',
358 'Mrs. Antonia Anderson II',
359 'antonia_anderson@civicrm.org',
360 'Amount: $ 200.00',
361 'Membership Start Date:',
362 'Transaction #: hers',
363 'This membership will be automatically renewed every',
364 'Dear Antonia',
365 'Thanks for your auto renew membership sign-up',
366 ],
367 [
368 'First Name: Anthony',
369 'First Name: Antonia',
370 'Last Name: Anderson',
371 'Supporter Profile',
372 'Email Address: antonia_anderson@civicrm.org',
373 ]);
374
375 $mut->stop();
376 $mut->clearMessages();
377 }
378
379 /**
380 * Get detail for recurring transaction.
381 *
382 * @param array $params
383 * Additional parameters.
384 *
385 * @return array
386 * Parameters like AuthorizeNet silent post paramters.
387 */
388 public function getRecurTransaction($params = []) {
389 return array_merge([
390 'x_amount' => '200.00',
391 "x_country" => 'US',
392 'x_phone' => "",
393 "x_fax" => "",
394 "x_email" => "me@gmail.com",
395 "x_description" => "lots of money",
396 "x_type" => "auth_capture",
397 "x_ship_to_first_name" => "",
398 "x_ship_to_last_name" => "",
399 "x_ship_to_company" => "",
400 "x_ship_to_address" => "",
401 "x_ship_to_city" => "",
402 "x_ship_to_state" => "",
403 "x_ship_to_zip" => "",
404 "x_ship_to_country" => "",
405 "x_tax" => "0.00",
406 "x_duty" => "0.00",
407 "x_freight" => "0.00",
408 "x_tax_exempt" => "FALSE",
409 "x_po_num" => "",
410 "x_MD5_Hash" => "1B7C0C5B4DEDD9CAD0636E35E22FC594",
411 "x_cvv2_resp_code" => "",
412 "x_cavv_response" => "",
413 "x_test_request" => "false",
414 "x_subscription_id" => $this->_contactID,
415 "x_subscription_paynum" => "1",
416 'x_first_name' => 'Robert',
417 'x_zip' => '90210',
418 'x_state' => 'WA',
419 'x_city' => 'Dallas',
420 'x_address' => '41 My ST',
421 'x_invoice_num' => $this->ids['Contribution'][0],
422 'x_cust_id' => $this->_contactID,
423 'x_company' => 'nowhere@civicrm.org',
424 'x_last_name' => 'Roberts',
425 'x_account_number' => 'XXXX5077',
426 'x_card_type' => 'Visa',
427 'x_method' => 'CC',
428 'x_trans_id' => '6511143069',
429 'x_auth_code' => '123456',
430 'x_avs_code' => 'Z',
431 'x_response_reason_text' => 'This transaction has been approved.',
432 'x_response_reason_code' => '1',
433 'x_response_code' => '1',
434 ], $params);
435 }
436
437 /**
438 * @return array
439 */
440 public function getRecurSubsequentTransaction($params = []) {
441 return array_merge($this->getRecurTransaction(), [
442 'x_trans_id' => 'second_one',
443 'x_MD5_Hash' => 'EA7A3CD65A85757827F51212CA1486A8',
444 ], $params);
445 }
446
447 }