9effd2619d6aa52b87601bb46fb4413c85ede2a8
[civicrm-core.git] / tests / phpunit / CRM / Contribute / Form / AdditionalPaymentTest.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28 /**
29 * Test APIv3 civicrm_contribute_* functions
30 *
31 * @package CiviCRM_APIv3
32 * @subpackage API_Contribution
33 * @group headless
34 */
35 class CRM_Contribute_Form_AdditionalPaymentTest extends CiviUnitTestCase {
36
37 /**
38 * Contact ID.
39 *
40 * @var int
41 */
42 protected $_individualId;
43
44 /**
45 * Parameters to create contribution.
46 *
47 * @var array
48 */
49 protected $_params;
50
51 /**
52 * Contribution ID.
53 *
54 * @var int
55 */
56 protected $_contributionId;
57
58 /**
59 * Parameters to create payment processor.
60 *
61 * @var array
62 */
63 protected $_processorParams = [];
64
65 /**
66 * Payment instrument mapping.
67 *
68 * @var array
69 */
70 protected $paymentInstruments = [];
71
72 /**
73 * Dummy payment processor.
74 *
75 * @var CRM_Core_Payment_Dummy
76 */
77 protected $paymentProcessor;
78
79 /**
80 * Payment processor ID.
81 *
82 * @var int
83 */
84 protected $paymentProcessorID;
85
86 /**
87 * Setup function.
88 *
89 * @throws \CRM_Core_Exception
90 * @throws \CiviCRM_API3_Exception
91 */
92 public function setUp() {
93 parent::setUp();
94 $this->createLoggedInUser();
95
96 $this->_individualId = $this->individualCreate();
97 $this->_params = [
98 'total_amount' => 100,
99 'currency' => 'USD',
100 'contact_id' => $this->_individualId,
101 'financial_type_id' => 1,
102 ];
103 $this->_processorParams = [
104 'domain_id' => 1,
105 'name' => 'Dummy',
106 'payment_processor_type_id' => 10,
107 'financial_account_id' => 12,
108 'is_active' => 1,
109 'user_name' => '',
110 'url_site' => 'http://dummy.com',
111 'url_recur' => 'http://dummy.com',
112 'billing_mode' => 1,
113 ];
114
115 $instruments = $this->callAPISuccess('contribution', 'getoptions', ['field' => 'payment_instrument_id']);
116 $this->paymentInstruments = $instruments['values'];
117
118 $this->paymentProcessor = $this->dummyProcessorCreate();
119 $processor = $this->paymentProcessor->getPaymentProcessor();
120 $this->paymentProcessorID = $processor['id'];
121 }
122
123 /**
124 * Clean up after each test.
125 *
126 * @throws \CRM_Core_Exception
127 */
128 public function tearDown() {
129 $this->quickCleanUpFinancialEntities();
130 $this->quickCleanup(['civicrm_mailing_spool']);
131 parent::tearDown();
132 }
133
134 /**
135 * Test the submit function that completes the partially paid Contribution using Credit Card.
136 *
137 * @throws \CRM_Core_Exception
138 * @throws \CiviCRM_API3_Exception
139 */
140 public function testAddPaymentUsingCreditCardForPartiallyPaidContribution() {
141 $mut = new CiviMailUtils($this, TRUE);
142 $this->createPartiallyPaidOrder();
143
144 // pay additional amount by using Credit Card
145 $this->submitPayment(70, 'live', TRUE);
146 $this->checkResults([30, 70], 2);
147 $mut->assertSubjects(['Payment Receipt - Mr. Anthony Anderson II']);
148 $mut->checkMailLog([
149 'From: site@something.com',
150 'Dear Anthony,',
151 'Payment Details',
152 'Total Amount: $ 100.00',
153 'This Payment Amount: $ 70.00',
154 'Balance Owed: $ 0.00 ',
155 'Billing Name and Address',
156 'Vancouver, AE 1321312',
157 'Visa',
158 '***********1111',
159 'Expires: May 2025',
160 ]);
161
162 $mut->stop();
163 $mut->clearMessages();
164 $this->validateAllPayments();
165 }
166
167 /**
168 * Test the submit function that completes the partially paid Contribution.
169 *
170 * @throws \CRM_Core_Exception
171 * @throws \CiviCRM_API3_Exception
172 */
173 public function testAddPaymentForPartiallyPaidContribution() {
174 $this->createPartiallyPaidOrder();
175
176 // pay additional amount
177 $this->submitPayment(70);
178 $this->checkResults([30, 70], 2);
179 $this->validateAllPayments();
180 }
181
182 /**
183 * Test the submit function that completes the partially paid Contribution with multiple payments.
184 *
185 * @throws \CRM_Core_Exception
186 * @throws \CiviCRM_API3_Exception
187 */
188 public function testMultiplePaymentForPartiallyPaidContribution() {
189 $this->createPartiallyPaidOrder();
190
191 // pay additional amount
192 $this->submitPayment(50);
193 $contribution = $this->callAPISuccessGetSingle('Contribution', ['id' => $this->_contributionId]);
194 $this->assertEquals('Partially paid', $contribution['contribution_status']);
195
196 // pay additional amount
197 $this->submitPayment(20);
198 $this->checkResults([30, 50, 20], 3);
199 $activities = $this->callAPISuccess('Activity', 'get', [
200 'source_record_id' => $this->_contributionId,
201 'activity_type_id' => 'Payment',
202 'options' => ['sort' => 'id'],
203 'sequential' => 1,
204 'return' => ['target_contact_id', 'assignee_contact_id', 'subject'],
205 ])['values'];
206 $this->assertCount(3, $activities);
207 $this->assertEquals('$ 50.00 - Offline Payment for Contribution', $activities[1]['subject']);
208 $this->assertEquals('$ 20.00 - Offline Payment for Contribution', $activities[2]['subject']);
209 $this->assertEquals(CRM_Core_Session::singleton()->getLoggedInContactID(), $activities[0]['source_contact_id']);
210 $this->assertEquals([$this->_individualId], $activities[0]['target_contact_id']);
211 $this->assertEquals([], $activities[0]['assignee_contact_id']);
212 $this->validateAllPayments();
213 }
214
215 /**
216 * Test the submit function that completes the partially paid Contribution with multiple payments.
217 *
218 * @throws \CRM_Core_Exception
219 * @throws \CiviCRM_API3_Exception
220 */
221 public function testMultiplePaymentForPartiallyPaidContributionWithOneCreditCardPayment() {
222 $mut = new CiviMailUtils($this, TRUE);
223 $this->createPartiallyPaidOrder();
224 // In general when there is tpl leakage we try to fix. At the moment, however,
225 // the tpl leakage on credit card related things is kind of 'by-design' - or
226 // at least we haven't found a way to replace the way in with Payment.send_confirmation
227 // picks them up from the form process so we will just clear templates here to stop leakage
228 // from previous tests causing a fail.
229 // The reason this is hard to fix is that we save a billing address per contribution not
230 // per payment so it's a problem with the data model
231 CRM_Core_Smarty::singleton()->clearTemplateVars();
232
233 // pay additional amount
234 $this->submitPayment(50, NULL, TRUE);
235 $contribution = $this->callAPISuccessGetSingle('Contribution', ['id' => $this->_contributionId]);
236 $this->assertEquals('Partially paid', $contribution['contribution_status']);
237
238 // pay additional amount by using credit card
239 $this->submitPayment(20, 'live');
240 $this->checkResults([30, 50, 20], 3);
241 $mut->assertSubjects(['Payment Receipt - Mr. Anthony Anderson II']);
242 $mut->checkMailLog([
243 'Dear Anthony,',
244 'Below you will find a receipt for this payment.',
245 'Total Amount: $ 100.00',
246 'This Payment Amount: $ 50.00',
247 'Balance Owed: $ 20.00 ',
248 'Paid By: Check',
249 'Check Number: check-12345',
250 ],
251 [
252 'Billing Name and Address',
253 'Visa',
254 ]);
255 $mut->stop();
256 $mut->clearMessages();
257 $this->validateAllPayments();
258 }
259
260 /**
261 * Test the submit function that completes the pending pay later Contribution using Credit Card.
262 *
263 * @throws \CRM_Core_Exception
264 * @throws \CiviCRM_API3_Exception
265 */
266 public function testAddPaymentUsingCreditCardForPendingPayLaterContribution() {
267 $mut = new CiviMailUtils($this, TRUE);
268 $this->createPendingOrder();
269
270 // pay additional amount by using Credit Card
271 $this->submitPayment(100, 'live', TRUE);
272 $this->checkResults([100], 1);
273
274 $mut->checkMailLog([
275 'Below you will find a receipt for this payment.',
276 'Total Amount: $ 100.00',
277 'This Payment Amount: $ 100.00',
278 'Balance Owed: $ 0.00 ',
279 'Paid By: Credit Card',
280 '***********1111',
281 'Billing Name and Address',
282 'Vancouver, AE 1321312',
283 'Expires: May 2025',
284
285 ]);
286 $mut->stop();
287 $mut->clearMessages();
288 $this->validateAllPayments();
289 }
290
291 /**
292 * Test the submit function that completes the pending pay later Contribution.
293 *
294 * @throws \CRM_Core_Exception
295 * @throws \CiviCRM_API3_Exception
296 */
297 public function testAddPaymentForPendingPayLaterContribution() {
298 $this->createPendingOrder();
299
300 // pay additional amount
301 $this->submitPayment(70);
302 $contribution = $this->callAPISuccessGetSingle('Contribution', ['id' => $this->_contributionId]);
303 $this->assertEquals('Partially paid', $contribution['contribution_status']);
304 $this->assertEquals('2019-04-01 00:00:00', $contribution['receive_date']);
305 $payment = $this->callAPISuccessGetSingle('Payment', ['contribution_id' => $contribution['id']]);
306 $this->assertEquals('2017-04-11 13:05:11', $payment['trxn_date']);
307
308 // pay additional amount
309 $this->submitPayment(30);
310 $this->checkResults([30, 70], 2);
311 $this->validateAllPayments();
312 }
313
314 /**
315 * Test the Membership status after completing the pending pay later Contribution.
316 *
317 * @throws \CRM_Core_Exception
318 * @throws \CiviCRM_API3_Exception
319 */
320 public function testMembershipStatusAfterCompletingPayLaterContribution() {
321 $this->createPendingOrder();
322 $membership = $this->createPendingMembershipAndRecordContribution($this->_contributionId);
323 // pay additional amount
324 $this->submitPayment(100);
325 $this->callAPISuccessGetSingle('Contribution', ['id' => $this->_contributionId]);
326 $contributionMembership = $this->callAPISuccessGetSingle('Membership', ['id' => $membership['id']]);
327 $membershipStatus = $this->callAPISuccessGetSingle('MembershipStatus', ['id' => $contributionMembership['status_id']]);
328 $this->assertEquals('New', $membershipStatus['name']);
329 $this->validateAllPayments();
330 }
331
332 /**
333 * @param $contributionId
334 *
335 * @return array|int
336 *
337 * @throws \CRM_Core_Exception
338 */
339 private function createPendingMembershipAndRecordContribution($contributionId) {
340 $this->_individualId = $this->individualCreate();
341 $membershipTypeAnnualFixed = $this->callAPISuccess('membership_type', 'create', [
342 'domain_id' => 1,
343 'name' => 'AnnualFixed',
344 'member_of_contact_id' => 1,
345 'duration_unit' => 'year',
346 'duration_interval' => 1,
347 'period_type' => 'fixed',
348 'fixed_period_start_day' => '101',
349 'fixed_period_rollover_day' => '1231',
350 'relationship_type_id' => 20,
351 'financial_type_id' => 2,
352 ]);
353 $membershipStatuses = CRM_Member_PseudoConstant::membershipStatus();
354 $pendingStatusId = array_search('Pending', $membershipStatuses, TRUE);
355 $membership = $this->callAPISuccess('Membership', 'create', [
356 'contact_id' => $this->_individualId,
357 'membership_type_id' => $membershipTypeAnnualFixed['id'],
358 ]);
359 // Updating Membership status to Pending
360 $membership = $this->callAPISuccess('Membership', 'create', [
361 'id' => $membership['id'],
362 'status_id' => $pendingStatusId,
363 ]);
364 $this->callAPISuccess('MembershipPayment', 'create', [
365 'membership_id' => $membership['id'],
366 'contribution_id' => $contributionId,
367 ]);
368 return $membership;
369 }
370
371 /**
372 * Test the submit function that completes the pending pay later Contribution with multiple payments.
373 *
374 * @throws \CRM_Core_Exception
375 * @throws \CiviCRM_API3_Exception
376 */
377 public function testMultiplePaymentForPendingPayLaterContribution() {
378 $this->createPendingOrder();
379
380 // pay additional amount
381 $this->submitPayment(40);
382 $contribution = $this->callAPISuccessGetSingle('Contribution', ['id' => $this->_contributionId]);
383 $this->assertEquals('Partially paid', $contribution['contribution_status']);
384
385 $this->submitPayment(20);
386 $contribution = $this->callAPISuccessGetSingle('Contribution', ['id' => $this->_contributionId]);
387 $this->assertEquals('Partially paid', $contribution['contribution_status']);
388
389 $this->submitPayment(30);
390 $contribution = $this->callAPISuccessGetSingle('Contribution', ['id' => $this->_contributionId]);
391 $this->assertEquals('Partially paid', $contribution['contribution_status']);
392
393 $this->submitPayment(10);
394 $this->checkResults([40, 20, 30, 10], 4);
395 $this->validateAllPayments();
396 }
397
398 /**
399 * Test the submit function that completes the pending pay later Contribution with multiple payments.
400 *
401 * @throws \CRM_Core_Exception
402 * @throws \CiviCRM_API3_Exception
403 */
404 public function testMultiplePaymentForPendingPayLaterContributionWithOneCreditCardPayment() {
405 $this->createPendingOrder();
406
407 // pay additional amount
408 $this->submitPayment(50);
409 $contribution = $this->callAPISuccessGetSingle('Contribution', ['id' => $this->_contributionId]);
410 $this->assertEquals('Partially paid', $contribution['contribution_status']);
411
412 $this->submitPayment(20, 'live');
413 $contribution = $this->callAPISuccessGetSingle('Contribution', ['id' => $this->_contributionId]);
414 $this->assertEquals('Partially paid', $contribution['contribution_status']);
415
416 $this->submitPayment(20);
417 $contribution = $this->callAPISuccessGetSingle('Contribution', ['id' => $this->_contributionId]);
418 $this->assertEquals('Partially paid', $contribution['contribution_status']);
419
420 $this->submitPayment(10, 'live');
421 $this->checkResults([50, 20, 20, 10], 4);
422 $this->validateAllPayments();
423 }
424
425 /**
426 * Function to submit payments for contribution.
427 *
428 * @param float $amount
429 * Payment Amount
430 * @param string $mode
431 * Mode of Payment
432 * @param bool $isEmailReceipt
433 *
434 * @throws \CiviCRM_API3_Exception
435 */
436 public function submitPayment($amount, $mode = NULL, $isEmailReceipt = FALSE) {
437 $form = new CRM_Contribute_Form_AdditionalPayment();
438
439 $submitParams = [
440 'contribution_id' => $this->_contributionId,
441 'contact_id' => $this->_individualId,
442 'total_amount' => $amount,
443 'currency' => 'USD',
444 'financial_type_id' => 1,
445 'trxn_date' => '2017-04-11 13:05:11',
446 'payment_processor_id' => 0,
447 'is_email_receipt' => $isEmailReceipt,
448 'from_email_address' => 'site@something.com',
449 ];
450 if ($mode) {
451 $submitParams += [
452 'payment_instrument_id' => array_search('Credit Card', $this->paymentInstruments, TRUE),
453 'payment_processor_id' => $this->paymentProcessorID,
454 'credit_card_exp_date' => ['M' => 5, 'Y' => 2025],
455 'credit_card_number' => '411111111111111',
456 'cvv2' => 234,
457 'credit_card_type' => 'Visa',
458 'billing_city-5' => 'Vancouver',
459 'billing_state_province_id-5' => 1059,
460 'billing_postal_code-5' => 1321312,
461 'billing_country_id-5' => 1228,
462 ];
463 }
464 else {
465 $submitParams += [
466 'payment_instrument_id' => array_search('Check', $this->paymentInstruments, TRUE),
467 'check_number' => 'check-12345',
468 ];
469 }
470 $form->cid = $this->_individualId;
471 $form->testSubmit($submitParams, $mode);
472 }
473
474 /**
475 * Function to check result.
476 *
477 * @param array $amounts
478 * Array of payment amount for contribution
479 * @param int $count
480 * Number payment for contribution
481 *
482 * @throws \CRM_Core_Exception
483 */
484 public function checkResults($amounts, $count) {
485 $contribution = $this->callAPISuccessGetSingle('Contribution', ['id' => $this->_contributionId]);
486 $this->assertNotEmpty($contribution);
487 $this->assertEquals('Completed', $contribution['contribution_status']);
488
489 $this->callAPISuccessGetCount('EntityFinancialTrxn', [
490 'entity_table' => 'civicrm_contribution',
491 'entity_id' => $this->_contributionId,
492 'financial_trxn_id.is_payment' => 1,
493 'financial_trxn_id.total_amount' => ['IN' => $amounts],
494 ], $count);
495 }
496
497 /**
498 * Create a pending order.
499 *
500 * @throws \CRM_Core_Exception
501 */
502 protected function createPendingOrder() {
503 $orderParams = array_merge($this->_params, [
504 'contribution_status_id' => 'Pending',
505 'is_pay_later' => 1,
506 'receive_date' => '2019-04-01',
507 ]);
508 $contribution = $this->callAPISuccess('Order', 'create', $orderParams);
509 $contribution = $this->callAPISuccessGetSingle('Contribution', ['id' => $contribution['id']]);
510 $this->assertEquals('Pending', CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $contribution['contribution_status_id']));
511 $this->_contributionId = $contribution['id'];
512 }
513
514 /**
515 * Create a partially paid order.
516 *
517 * @throws \CRM_Core_Exception
518 */
519 protected function createPartiallyPaidOrder() {
520 $orderParams = array_merge($this->_params, [
521 'total_amount' => 100.00,
522 'contribution_status_id' => 'Pending',
523 'api.Payment.create' => ['total_amount' => 30],
524 ]);
525 $contribution = $this->callAPISuccess('Order', 'create', $orderParams);
526 $contribution = $this->callAPISuccessGetSingle('Contribution', ['id' => $contribution['id']]);
527 $this->assertEquals('Partially paid', CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $contribution['contribution_status_id']));
528 $this->_contributionId = $contribution['id'];
529 }
530
531 }