Merge pull request #15737 from eileenmcnaughton/php73
[civicrm-core.git] / tests / phpunit / api / v3 / PaymentTest.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 api_v3_PaymentTest extends CiviUnitTestCase {
36
37 protected $_individualId;
38
39 protected $_financialTypeId = 1;
40
41 /**
42 * Setup function.
43 *
44 * @throws \CRM_Core_Exception
45 */
46 public function setUp() {
47 parent::setUp();
48
49 $this->_apiversion = 3;
50 $this->_individualId = $this->individualCreate();
51 CRM_Core_Config::singleton()->userPermissionClass->permissions = [];
52 }
53
54 /**
55 * Clean up after each test.
56 *
57 * @throws \Exception
58 */
59 public function tearDown() {
60 $this->quickCleanUpFinancialEntities();
61 $this->quickCleanup(['civicrm_uf_match']);
62 unset(CRM_Core_Config::singleton()->userPermissionClass->permissions);
63 parent::tearDown();
64 }
65
66 /**
67 * Test Get Payment api.
68 *
69 * @throws \CRM_Core_Exception
70 */
71 public function testGetPayment() {
72 $p = [
73 'contact_id' => $this->_individualId,
74 'receive_date' => '2010-01-20',
75 'total_amount' => 100.00,
76 'financial_type_id' => $this->_financialTypeId,
77 'trxn_id' => 23456,
78 'contribution_status_id' => 1,
79 ];
80 $contribution = $this->callAPISuccess('contribution', 'create', $p);
81
82 $params = [
83 'contribution_id' => $contribution['id'],
84 'check_permissions' => TRUE,
85 ];
86 CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM', 'administer CiviCRM'];
87 $payment = $this->callAPIFailure('payment', 'get', $params, 'API permission check failed for Payment/get call; insufficient permission: require access CiviCRM and access CiviContribute');
88
89 array_push(CRM_Core_Config::singleton()->userPermissionClass->permissions, 'access CiviContribute');
90 $payment = $this->callAPISuccess('payment', 'get', $params);
91
92 $payment = $this->callAPIAndDocument('payment', 'get', $params, __FUNCTION__, __FILE__);
93 $this->assertEquals(1, $payment['count']);
94
95 $expectedResult = [
96 $contribution['id'] => [
97 'total_amount' => 100,
98 'trxn_id' => 23456,
99 'trxn_date' => '2010-01-20 00:00:00',
100 'contribution_id' => $contribution['id'],
101 'is_payment' => 1,
102 ],
103 ];
104 $this->checkPaymentResult($payment, $expectedResult);
105 $this->callAPISuccess('Contribution', 'Delete', [
106 'id' => $contribution['id'],
107 ]);
108 $this->validateAllPayments();
109 }
110
111 /**
112 * Retrieve Payment using trxn_id.
113 *
114 * @throws \CRM_Core_Exception
115 */
116 public function testGetPaymentWithTrxnID() {
117 $this->_individualId2 = $this->individualCreate();
118 $params1 = [
119 'contact_id' => $this->_individualId,
120 'trxn_id' => 111111,
121 'total_amount' => 10,
122 ];
123 $contributionID1 = $this->contributionCreate($params1);
124
125 $params2 = [
126 'contact_id' => $this->_individualId2,
127 'trxn_id' => 222222,
128 'total_amount' => 20,
129 ];
130 $contributionID2 = $this->contributionCreate($params2);
131
132 $paymentParams = ['trxn_id' => 111111];
133 $payment = $this->callAPISuccess('payment', 'get', $paymentParams);
134 $expectedResult = [
135 $payment['id'] => [
136 'total_amount' => 10,
137 'trxn_id' => 111111,
138 'status_id' => 1,
139 'is_payment' => 1,
140 'contribution_id' => $contributionID1,
141 ],
142 ];
143 $this->checkPaymentResult($payment, $expectedResult);
144
145 $paymentParams = ['trxn_id' => 222222];
146 $payment = $this->callAPISuccess('payment', 'get', $paymentParams);
147 $expectedResult = [
148 $payment['id'] => [
149 'total_amount' => 20,
150 'trxn_id' => 222222,
151 'status_id' => 1,
152 'is_payment' => 1,
153 'contribution_id' => $contributionID2,
154 ],
155 ];
156 $this->checkPaymentResult($payment, $expectedResult);
157 $this->validateAllPayments();
158 }
159
160 /**
161 * Test email receipt for partial payment.
162 *
163 * @throws \CRM_Core_Exception
164 */
165 public function testPaymentEmailReceipt() {
166 $mut = new CiviMailUtils($this);
167 $contribution = $this->createPartiallyPaidParticipantOrder();
168 $event = $this->callAPISuccess('Event', 'get', []);
169 $this->addLocationToEvent($event['id']);
170 $params = [
171 'contribution_id' => $contribution['id'],
172 'total_amount' => 50,
173 'check_number' => '345',
174 'trxn_date' => '2018-08-13 17:57:56',
175 ];
176 $payment = $this->callAPISuccess('payment', 'create', $params);
177 $this->checkPaymentResult($payment, [
178 $payment['id'] => [
179 'from_financial_account_id' => 7,
180 'to_financial_account_id' => 6,
181 'total_amount' => 50,
182 'status_id' => 1,
183 'is_payment' => 1,
184 ],
185 ]);
186
187 $this->callAPISuccess('Payment', 'sendconfirmation', ['id' => $payment['id']]);
188 $mut->assertSubjects(['Payment Receipt - Annual CiviCRM meet - Mr. Anthony Anderson II']);
189 $mut->checkMailLog([
190 'From: "FIXME" <info@EXAMPLE.ORG>',
191 'Dear Anthony,',
192 'Total Fees: $ 300.00',
193 'This Payment Amount: $ 50.00',
194 //150 was paid in the 1st payment.
195 'Balance Owed: $ 100.00',
196 'Event Information and Location',
197 'Paid By: Check',
198 'Check Number: 345',
199 'Transaction Date: August 13th, 2018 5:57 PM',
200 'event place',
201 'streety street',
202 ]);
203 $mut->stop();
204 $mut->clearMessages();
205 $this->validateAllPayments();
206 }
207
208 /**
209 * Test email receipt for partial payment.
210 *
211 * @throws \CRM_Core_Exception
212 */
213 public function testPaymentEmailReceiptFullyPaid() {
214 $mut = new CiviMailUtils($this);
215 CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviContribute', 'edit contributions', 'access CiviCRM'];
216 $contribution = $this->createPartiallyPaidParticipantOrder();
217
218 $params = [
219 'contribution_id' => $contribution['id'],
220 'total_amount' => 150,
221 ];
222 $payment = $this->callAPISuccess('payment', 'create', $params);
223
224 // Here we set the email to an invalid email & use check_permissions, domain email should be used.
225 $email = $this->callAPISuccess('Email', 'create', ['contact_id' => 1, 'email' => 'bob@example.com']);
226 $this->callAPISuccess('Payment', 'sendconfirmation', ['id' => $payment['id'], 'from' => $email['id'], 'check_permissions' => 1]);
227 $mut->assertSubjects(['Payment Receipt - Annual CiviCRM meet - Mr. Anthony Anderson II', 'Registration Confirmation - Annual CiviCRM meet - Mr. Anthony Anderson II']);
228 $mut->checkMailLog([
229 'From: "FIXME" <info@EXAMPLE.ORG>',
230 'Dear Anthony,',
231 'A payment has been received.',
232 'Total Fees: $ 300.00',
233 'This Payment Amount: $ 150.00',
234 'Balance Owed: $ 0.00',
235 'Thank you for completing this payment.',
236 ]);
237 $mut->stop();
238 $mut->clearMessages();
239 $this->validateAllPayments();
240 }
241
242 /**
243 * Test email receipt for partial payment.
244 *
245 * @dataProvider getThousandSeparators
246 *
247 * @param string $thousandSeparator
248 *
249 * @throws \CRM_Core_Exception
250 */
251 public function testRefundEmailReceipt($thousandSeparator) {
252 $this->setCurrencySeparators($thousandSeparator);
253 $decimalSeparator = ($thousandSeparator === ',' ? '.' : ',');
254 $mut = new CiviMailUtils($this);
255 $contribution = $this->createPartiallyPaidParticipantOrder();
256 $this->callAPISuccess('payment', 'create', [
257 'contribution_id' => $contribution['id'],
258 'total_amount' => 50,
259 'check_number' => '345',
260 'trxn_date' => '2018-08-13 17:57:56',
261 ]);
262
263 $payment = $this->callAPISuccess('payment', 'create', [
264 'contribution_id' => $contribution['id'],
265 'total_amount' => -30,
266 'trxn_date' => '2018-11-13 12:01:56',
267 'sequential' => TRUE,
268 ])['values'][0];
269
270 $expected = [
271 'from_financial_account_id' => 7,
272 'to_financial_account_id' => 6,
273 'total_amount' => -30,
274 'status_id' => CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_FinancialTrxn', 'status_id', 'Refunded'),
275 'is_payment' => 1,
276 ];
277 foreach ($expected as $key => $value) {
278 $this->assertEquals($expected[$key], $payment[$key], 'mismatch on key ' . $key);
279 }
280
281 $this->callAPISuccess('Payment', 'sendconfirmation', ['id' => $payment['id']]);
282 $mut->assertSubjects(['Refund Notification - Annual CiviCRM meet - Mr. Anthony Anderson II']);
283 $mut->checkMailLog([
284 'Dear Anthony,',
285 'A refund has been issued based on changes in your registration selections.',
286 'Total Fees: $ 300' . $decimalSeparator . '00',
287 'Refund Amount: $ -30' . $decimalSeparator . '00',
288 'Event Information and Location',
289 'Paid By: Check',
290 'Transaction Date: November 13th, 2018 12:01 PM',
291 'You Paid: $ 170' . $decimalSeparator . '00',
292 ]);
293 $mut->stop();
294 $mut->clearMessages();
295 $this->validateAllPayments();
296 }
297
298 /**
299 * Test adding a payment to a pending multi-line order.
300 *
301 * @throws \CRM_Core_Exception
302 */
303 public function testCreatePaymentPendingOrderNoLineItems() {
304 $order = $this->createPendingParticipantOrder();
305 $this->callAPISuccess('Payment', 'create', [
306 'order_id' => $order['id'],
307 'total_amount' => 50,
308 ]);
309 $this->validateAllPayments();
310 }
311
312 /**
313 * Add participant with contribution
314 *
315 * @return array
316 *
317 * @throws \CRM_Core_Exception
318 */
319 protected function createPendingParticipantOrder() {
320 return $this->callAPISuccess('Order', 'create', $this->getParticipantOrderParams());
321 }
322
323 /**
324 * Test create payment api with no line item in params
325 *
326 * @throws \CRM_Core_Exception
327 */
328 public function testCreatePaymentNoLineItems() {
329 $contribution = $this->createPartiallyPaidParticipantOrder();
330
331 //Create partial payment
332 $params = [
333 'contribution_id' => $contribution['id'],
334 'total_amount' => 50,
335 ];
336 $payment = $this->callAPIAndDocument('payment', 'create', $params, __FUNCTION__, __FILE__);
337 $this->checkPaymentIsValid($payment['id'], $contribution['id']);
338
339 $params = [
340 'entity_table' => 'civicrm_financial_item',
341 'financial_trxn_id' => $payment['id'],
342 ];
343 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params);
344 $amounts = [33.33, 16.67];
345 foreach ($eft['values'] as $value) {
346 $this->assertEquals($value['amount'], array_pop($amounts));
347 }
348
349 // Now create payment to complete total amount of contribution
350 $params = [
351 'contribution_id' => $contribution['id'],
352 'total_amount' => 100,
353 ];
354 $payment = $this->callAPISuccess('payment', 'create', $params);
355 $expectedResult = [
356 $payment['id'] => [
357 'from_financial_account_id' => 7,
358 'to_financial_account_id' => 6,
359 'total_amount' => 100,
360 'status_id' => 1,
361 'is_payment' => 1,
362 ],
363 ];
364 $this->checkPaymentResult($payment, $expectedResult);
365 $params = [
366 'entity_table' => 'civicrm_financial_item',
367 'financial_trxn_id' => $payment['id'],
368 ];
369 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params);
370 $amounts = [66.67, 33.33];
371 foreach ($eft['values'] as $value) {
372 $this->assertEquals($value['amount'], array_pop($amounts));
373 }
374 // Check contribution for completed status
375 $contribution = $this->callAPISuccess('contribution', 'get', ['id' => $contribution['id']]);
376
377 $this->assertEquals($contribution['values'][$contribution['id']]['contribution_status'], 'Completed');
378 $this->assertEquals($contribution['values'][$contribution['id']]['total_amount'], 300.00);
379 $paymentParticipant = [
380 'contribution_id' => $contribution['id'],
381 ];
382 $participantPayment = $this->callAPISuccess('ParticipantPayment', 'getsingle', $paymentParticipant);
383 $participant = $this->callAPISuccess('participant', 'get', ['id' => $participantPayment['participant_id']]);
384 $this->assertEquals($participant['values'][$participant['id']]['participant_status'], 'Registered');
385 $this->callAPISuccess('Contribution', 'Delete', [
386 'id' => $contribution['id'],
387 ]);
388 $this->validateAllPayments();
389 }
390
391 /**
392 * Function to assert db values
393 *
394 * @throws \CRM_Core_Exception
395 */
396 public function checkPaymentResult($payment, $expectedResult) {
397 $refreshedPayment = $this->callAPISuccessGetSingle('Payment', ['financial_trxn_id' => $payment['id']]);
398 foreach ($expectedResult[$payment['id']] as $key => $value) {
399 $this->assertEquals($refreshedPayment[$key], $value, 'mismatch on ' . $key); $this->assertEquals($refreshedPayment[$key], $value, 'mismatch on ' . $key);
400 }
401 }
402
403 /**
404 * Test create payment api with line item in params
405 *
406 * @throws \CRM_Core_Exception
407 */
408 public function testCreatePaymentLineItems() {
409 $contribution = $this->createPartiallyPaidParticipantOrder();
410 $lineItems = $this->callAPISuccess('LineItem', 'get', ['contribution_id' => $contribution['id']])['values'];
411
412 // Create partial payment by passing line item array is params.
413 $params = [
414 'contribution_id' => $contribution['id'],
415 'total_amount' => 50,
416 ];
417 $amounts = [40, 10];
418 foreach ($lineItems as $id => $ignore) {
419 $params['line_item'][] = [$id => array_pop($amounts)];
420 }
421 $payment = $this->callAPIAndDocument('Payment', 'create', $params, __FUNCTION__, __FILE__, 'Payment with line item', 'CreatePaymentWithLineItems');
422 $this->checkPaymentIsValid($payment['id'], $contribution['id']);
423
424 $params = [
425 'entity_table' => 'civicrm_financial_item',
426 'financial_trxn_id' => $payment['id'],
427 'return' => ['entity_id.entity_id', 'amount'],
428 ];
429 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params)['values'];
430 $this->assertCount(2, $eft);
431 $amounts = [40, 10];
432 foreach ($eft as $value) {
433 $this->assertEquals($value['amount'], array_pop($amounts));
434 }
435
436 // Now create payment to complete total amount of contribution
437 $params = [
438 'contribution_id' => $contribution['id'],
439 'total_amount' => 100,
440 ];
441 $amounts = [80, 20];
442 foreach ($lineItems as $id => $ignore) {
443 $params['line_item'][] = [$id => array_pop($amounts)];
444 }
445 $payment = $this->callAPISuccess('Payment', 'create', $params);
446 $expectedResult = [
447 $payment['id'] => [
448 'from_financial_account_id' => 7,
449 'to_financial_account_id' => 6,
450 'total_amount' => 100,
451 'status_id' => 1,
452 'is_payment' => 1,
453 ],
454 ];
455 $this->checkPaymentResult($payment, $expectedResult);
456 $params = [
457 'entity_table' => 'civicrm_financial_item',
458 'financial_trxn_id' => $payment['id'],
459 ];
460 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params)['values'];
461 $this->assertCount(2, $eft);
462 $amounts = [80, 20];
463 foreach ($eft as $value) {
464 $this->assertEquals($value['amount'], array_pop($amounts));
465 }
466 // Check contribution for completed status
467 $contribution = $this->callAPISuccess('Contribution', 'get', ['id' => $contribution['id']]);
468
469 $this->assertEquals($contribution['values'][$contribution['id']]['contribution_status'], 'Completed');
470 $this->assertEquals($contribution['values'][$contribution['id']]['total_amount'], 300.00);
471 $paymentParticipant = [
472 'contribution_id' => $contribution['id'],
473 ];
474 $participantPayment = $this->callAPISuccess('ParticipantPayment', 'getsingle', $paymentParticipant);
475 $participant = $this->callAPISuccess('participant', 'get', ['id' => $participantPayment['participant_id']]);
476 $this->assertEquals($participant['values'][$participant['id']]['participant_status'], 'Registered');
477 $this->validateAllPayments();
478 }
479
480 /**
481 * Test cancel payment api
482 *
483 * @throws \CRM_Core_Exception
484 */
485 public function testCancelPayment() {
486 CRM_Core_Config::singleton()->userPermissionClass->permissions = ['administer CiviCRM', 'access CiviContribute'];
487 $contribution = $this->createPartiallyPaidParticipantOrder();
488
489 $params = [
490 'contribution_id' => $contribution['id'],
491 ];
492
493 $payment = $this->callAPISuccess('payment', 'get', $params);
494 $this->assertEquals(1, $payment['count']);
495
496 $cancelParams = [
497 'id' => $payment['id'],
498 'check_permissions' => TRUE,
499 ];
500 $payment = $this->callAPIFailure('payment', 'cancel', $cancelParams, 'API permission check failed for Payment/cancel call; insufficient permission: require access CiviCRM and access CiviContribute and edit contributions');
501
502 array_push(CRM_Core_Config::singleton()->userPermissionClass->permissions, 'access CiviCRM', 'edit contributions');
503
504 $this->callAPIAndDocument('payment', 'cancel', $cancelParams, __FUNCTION__, __FILE__);
505
506 $payment = $this->callAPISuccess('payment', 'get', $params);
507 $this->assertEquals(2, $payment['count']);
508 $amounts = [-150.00, 150.00];
509 foreach ($payment['values'] as $value) {
510 $this->assertEquals($value['total_amount'], array_pop($amounts), 'Mismatch total amount');
511 }
512
513 $this->callAPISuccess('Contribution', 'Delete', [
514 'id' => $contribution['id'],
515 ]);
516 $this->validateAllPayments();
517 }
518
519 /**
520 * Test delete payment api
521 *
522 * @throws \CRM_Core_Exception
523 */
524 public function testDeletePayment() {
525 CRM_Core_Config::singleton()->userPermissionClass->permissions = ['administer CiviCRM', 'access CiviContribute'];
526 $contribution = $this->createPartiallyPaidParticipantOrder();
527
528 $params = [
529 'contribution_id' => $contribution['id'],
530 ];
531
532 $payment = $this->callAPISuccess('payment', 'get', $params);
533 $this->assertEquals(1, $payment['count']);
534
535 $deleteParams = [
536 'id' => $payment['id'],
537 'check_permissions' => TRUE,
538 ];
539 $payment = $this->callAPIFailure('payment', 'delete', $deleteParams, 'API permission check failed for Payment/delete call; insufficient permission: require access CiviCRM and access CiviContribute and delete in CiviContribute');
540
541 array_push(CRM_Core_Config::singleton()->userPermissionClass->permissions, 'access CiviCRM', 'delete in CiviContribute');
542 $this->callAPIAndDocument('payment', 'delete', $deleteParams, __FUNCTION__, __FILE__);
543
544 $payment = $this->callAPISuccess('payment', 'get', $params);
545 $this->assertEquals(0, $payment['count']);
546
547 $this->callAPISuccess('Contribution', 'Delete', [
548 'id' => $contribution['id'],
549 ]);
550 }
551
552 /**
553 * Test update payment api.
554 *
555 * 1) create a contribution for $300 with a partial payment of $150
556 * - this results in 2 financial transactions. The accounts receivable transaction is linked
557 * via entity_financial_trxns to the 2 line items. The $150 payment is not linked to the line items
558 * so the line items are fully allocated even though they are only half paid.
559 *
560 * 2) add a payment of $50 -
561 * This payment transaction IS linked to the line items so $350 of the $300 in line items is allocated
562 * but $200 is paid
563 *
564 * 3) update that payment to be $100
565 * This results in a negative and a positive payment ($50 & $100) - the negative payment results in
566 * financial_items but the positive payment does not.
567 *
568 * The final result is we have
569 * - 1 partly paid contribution of $300
570 * - payment financial_trxns totalling $250
571 * - 1 Accounts receivable financial_trxn totalling $300
572 * - 2 financial items totalling $300 linked to the Accounts receivable financial_trxn
573 * - 6 entries in the civicrm_entity_financial_trxn linked to line items - totalling $450.
574 * - 5 entries in the civicrm_entity_financial_trxn linked to contributions - totalling $550.
575 *
576 * @throws \CRM_Core_Exception
577 */
578 public function testUpdatePayment() {
579 CRM_Core_Config::singleton()->userPermissionClass->permissions = ['administer CiviCRM', 'access CiviContribute', 'edit contributions'];
580 $contribution = $this->createPartiallyPaidParticipantOrder();
581
582 //Create partial payment by passing line item array is params
583 $params = [
584 'contribution_id' => $contribution['id'],
585 'total_amount' => 50,
586 ];
587
588 $payment = $this->callAPISuccess('payment', 'create', $params);
589 $expectedResult = [
590 $payment['id'] => [
591 'from_financial_account_id' => 7,
592 'to_financial_account_id' => 6,
593 'total_amount' => 50,
594 'status_id' => 1,
595 'is_payment' => 1,
596 ],
597 ];
598 $this->checkPaymentResult($payment, $expectedResult);
599
600 $params = [
601 'entity_table' => 'civicrm_financial_item',
602 'financial_trxn_id' => $payment['id'],
603 ];
604 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params);
605 $amounts = [33.33, 16.67];
606 foreach ($eft['values'] as $value) {
607 $this->assertEquals($value['amount'], array_pop($amounts));
608 }
609
610 // update the amount for payment
611 $params = [
612 'contribution_id' => $contribution['id'],
613 'total_amount' => 100,
614 'id' => $payment['id'],
615 'check_permissions' => TRUE,
616 ];
617 // @todo - move this permissions test to it's own test - it just confuses here.
618 CRM_Core_Config::singleton()->userPermissionClass->permissions = ['administer CiviCRM', 'access CiviContribute'];
619 $this->callAPIFailure('payment', 'create', $params, 'API permission check failed for Payment/create call; insufficient permission: require access CiviCRM and access CiviContribute and edit contributions');
620
621 CRM_Core_Config::singleton()->userPermissionClass->permissions = ['administer CiviCRM', 'access CiviContribute', 'access CiviCRM', 'edit contributions'];
622 $payment = $this->callAPIAndDocument('payment', 'create', $params, __FUNCTION__, __FILE__, 'Update Payment', 'UpdatePayment');
623
624 $this->validateAllPayments();
625 // Check for proportional cancelled payment against lineitems.
626 $minParams = [
627 'entity_table' => 'civicrm_financial_item',
628 'financial_trxn_id' => $payment['id'] - 1,
629 ];
630
631 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $minParams)['values'];
632 $this->assertCount(2, $eft);
633 $amounts = [-33.33, -16.67];
634
635 foreach ($eft as $value) {
636 $this->assertEquals($value['amount'], array_pop($amounts));
637 }
638
639 // Check for proportional updated payment against lineitems.
640 $params = [
641 'entity_table' => 'civicrm_financial_item',
642 'financial_trxn_id' => $payment['id'],
643 ];
644 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params)['values'];
645 $amounts = [66.67, 33.33];
646 foreach ($eft as $value) {
647 $this->assertEquals($value['amount'], array_pop($amounts));
648 }
649 $items = $this->callAPISuccess('FinancialItem', 'get', [])['values'];
650 $this->assertCount(2, $items);
651 $itemSum = 0;
652 foreach ($items as $item) {
653 $this->assertEquals('civicrm_line_item', $item['entity_table']);
654 $itemSum += $item['amount'];
655 }
656 $this->assertEquals(300, $itemSum);
657
658 $params = [
659 'contribution_id' => $contribution['id'],
660 ];
661 $payment = $this->callAPISuccess('payment', 'get', $params);
662 $amounts = [100.00, -50.00, 50.00, 150.00];
663 foreach ($payment['values'] as $value) {
664 $amount = array_pop($amounts);
665 $this->assertEquals($value['total_amount'], $amount, 'Mismatch total amount');
666
667 // Check entity financial trxn created properly
668 $params = [
669 'entity_id' => $contribution['id'],
670 'entity_table' => 'civicrm_contribution',
671 'financial_trxn_id' => $value['id'],
672 ];
673 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params);
674 $this->assertEquals($eft['values'][$eft['id']]['amount'], $amount);
675 }
676
677 $this->callAPISuccess('Contribution', 'Delete', [
678 'id' => $contribution['id'],
679 ]);
680 $this->validateAllPayments();
681 }
682
683 /**
684 * Test create payment api for paylater contribution
685 *
686 * @throws \CRM_Core_Exception
687 */
688 public function testCreatePaymentPayLater() {
689 $this->createLoggedInUser();
690 $processorID = $this->paymentProcessorCreate();
691 $contributionParams = [
692 'total_amount' => 100,
693 'currency' => 'USD',
694 'contact_id' => $this->_individualId,
695 'financial_type_id' => 1,
696 'contribution_status_id' => 2,
697 'is_pay_later' => 1,
698 ];
699 $contribution = $this->callAPISuccess('Contribution', 'create', $contributionParams);
700 //add payment for pay later transaction
701 $params = [
702 'contribution_id' => $contribution['id'],
703 'total_amount' => 100,
704 'card_type_id' => 'Visa',
705 'pan_truncation' => '1234',
706 'trxn_result_code' => 'Startling success',
707 'payment_instrument_id' => $processorID,
708 'trxn_id' => 1234,
709 ];
710 $payment = $this->callAPISuccess('Payment', 'create', $params);
711 $expectedResult = [
712 $payment['id'] => [
713 'from_financial_account_id' => 7,
714 'to_financial_account_id' => 6,
715 'total_amount' => 100,
716 'status_id' => 1,
717 'is_payment' => 1,
718 'card_type_id' => 1,
719 'pan_truncation' => '1234',
720 'trxn_result_code' => 'Startling success',
721 'trxn_id' => 1234,
722 'payment_instrument_id' => 1,
723 ],
724 ];
725 $this->checkPaymentResult($payment, $expectedResult);
726 // Check entity financial trxn created properly
727 $params = [
728 'entity_id' => $contribution['id'],
729 'entity_table' => 'civicrm_contribution',
730 'financial_trxn_id' => $payment['id'],
731 ];
732 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params);
733 $this->assertEquals($eft['values'][$eft['id']]['amount'], 100);
734 $params = [
735 'entity_table' => 'civicrm_financial_item',
736 'financial_trxn_id' => $payment['id'],
737 ];
738 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params);
739 $this->assertEquals($eft['values'][$eft['id']]['amount'], 100);
740 // Check contribution for completed status
741 $contribution = $this->callAPISuccess('contribution', 'get', ['id' => $contribution['id']]);
742 $this->assertEquals($contribution['values'][$contribution['id']]['contribution_status'], 'Completed');
743 $this->assertEquals($contribution['values'][$contribution['id']]['total_amount'], 100.00);
744 $this->callAPISuccess('Contribution', 'Delete', [
745 'id' => $contribution['id'],
746 ]);
747 $this->validateAllPayments();
748 }
749
750 /**
751 * Test create payment api for pay later contribution with partial payment.
752 *
753 * https://lab.civicrm.org/dev/financial/issues/69
754 * @throws \CRM_Core_Exception
755 */
756 public function testCreatePaymentIncompletePaymentPartialPayment() {
757 $contributionParams = [
758 'total_amount' => 100,
759 'currency' => 'USD',
760 'contact_id' => $this->_individualId,
761 'financial_type_id' => 1,
762 'contribution_status_id' => 2,
763 ];
764 $contribution = $this->callAPISuccess('Contribution', 'create', $contributionParams);
765 $this->callAPISuccess('Payment', 'create', [
766 'contribution_id' => $contribution['id'],
767 'total_amount' => 50,
768 'payment_instrument_id' => 'Cash',
769 ]);
770 $payments = $this->callAPISuccess('Payment', 'get', ['contribution_id' => $contribution['id']])['values'];
771 $this->assertCount(1, $payments);
772 $this->validateAllPayments();
773 }
774
775 /**
776 * Test create payment api for pay later contribution with partial payment.
777 *
778 * @throws \CRM_Core_Exception
779 */
780 public function testCreatePaymentPayLaterPartialPayment() {
781 $this->createLoggedInUser();
782 $contributionParams = [
783 'total_amount' => 100,
784 'currency' => 'USD',
785 'contact_id' => $this->_individualId,
786 'financial_type_id' => 1,
787 'contribution_status_id' => 2,
788 'is_pay_later' => 1,
789 ];
790 $contribution = $this->callAPISuccess('Order', 'create', $contributionParams);
791 //Create partial payment
792 $params = [
793 'contribution_id' => $contribution['id'],
794 'total_amount' => 60,
795 ];
796 $payment = $this->callAPISuccess('Payment', 'create', $params);
797 $expectedResult = [
798 $payment['id'] => [
799 'total_amount' => 60,
800 'status_id' => 1,
801 'is_payment' => 1,
802 ],
803 ];
804 $this->checkPaymentResult($payment, $expectedResult);
805 // Check entity financial trxn created properly
806 $params = [
807 'entity_id' => $contribution['id'],
808 'entity_table' => 'civicrm_contribution',
809 'financial_trxn_id' => $payment['id'],
810 ];
811 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params);
812 $this->assertEquals($eft['values'][$eft['id']]['amount'], 60);
813 $params = [
814 'entity_table' => 'civicrm_financial_item',
815 'financial_trxn_id' => $payment['id'],
816 ];
817 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params);
818 $this->assertEquals($eft['values'][$eft['id']]['amount'], 60);
819 $contribution = $this->callAPISuccess('contribution', 'get', ['id' => $contribution['id']]);
820 $this->assertEquals($contribution['values'][$contribution['id']]['contribution_status'], 'Partially paid');
821 $this->assertEquals($contribution['values'][$contribution['id']]['total_amount'], 100.00);
822 //Create full payment
823 $params = [
824 'contribution_id' => $contribution['id'],
825 'total_amount' => 40,
826 ];
827 // Rename the 'completed' status label first to check that we are not using the labels!
828 $this->callAPISuccess('OptionValue', 'get', ['name' => 'Completed', 'option_group_id' => 'contribution_status', 'api.OptionValue.create' => ['label' => 'Unicorn']]);
829 $payment = $this->callAPISuccess('Payment', 'create', $params);
830 $expectedResult = [
831 $payment['id'] => [
832 'from_financial_account_id' => 7,
833 'to_financial_account_id' => 6,
834 'total_amount' => 40,
835 'status_id' => 1,
836 'is_payment' => 1,
837 ],
838 ];
839 $this->checkPaymentResult($payment, $expectedResult);
840 // Check entity financial trxn created properly
841 $params = [
842 'entity_id' => $contribution['id'],
843 'entity_table' => 'civicrm_contribution',
844 'financial_trxn_id' => $payment['id'],
845 ];
846 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params);
847 $this->assertEquals($eft['values'][$eft['id']]['amount'], 40);
848 $params = [
849 'entity_table' => 'civicrm_financial_item',
850 'financial_trxn_id' => $payment['id'],
851 ];
852 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params);
853 $this->assertEquals($eft['values'][$eft['id']]['amount'], 40);
854 // Check contribution for completed status
855 $contribution = $this->callAPISuccess('contribution', 'get', ['id' => $contribution['id']]);
856 $this->assertEquals($contribution['values'][$contribution['id']]['contribution_status'], 'Unicorn');
857 $this->assertEquals($contribution['values'][$contribution['id']]['total_amount'], 100.00);
858 $this->callAPISuccess('Contribution', 'Delete', [
859 'id' => $contribution['id'],
860 ]);
861 $this->callAPISuccess('OptionValue', 'get', ['name' => 'Completed', 'option_group_id' => 'contribution_status', 'api.OptionValue.create' => ['label' => 'Completed']]);
862 $this->callAPISuccessGetCount('Activity', ['target_contact_id' => $this->_individualId, 'activity_type_id' => 'Payment'], 2);
863 $this->validateAllPayments();
864 }
865
866 /**
867 * Test that Payment.create uses the to_account of the payment processor.
868 *
869 * @throws \CiviCRM_API3_Exception
870 * @throws \CRM_Core_Exception
871 */
872 public function testPaymentWithProcessorWithOddFinancialAccount() {
873 $processor = $this->dummyProcessorCreate(['financial_account_id' => 'Deposit Bank Account', 'payment_instrument_id' => 'Cash']);
874 $processor2 = $this->dummyProcessorCreate(['financial_account_id' => 'Payment Processor Account', 'name' => 'p2', 'payment_instrument_id' => 'EFT']);
875 $contributionParams = [
876 'total_amount' => 100,
877 'currency' => 'USD',
878 'contact_id' => $this->_individualId,
879 'financial_type_id' => 1,
880 'contribution_status_id' => 'Pending',
881 ];
882 $order = $this->callAPISuccess('Order', 'create', $contributionParams);
883 $this->callAPISuccess('Payment', 'create', ['payment_processor_id' => $processor->getID(), 'total_amount' => 6, 'contribution_id' => $order['id']]);
884 $this->callAPISuccess('Payment', 'create', ['payment_processor_id' => $processor2->getID(), 'total_amount' => 15, 'contribution_id' => $order['id']]);
885 $payments = $this->callAPISuccess('Payment', 'get', ['sequential' => 1, 'contribution_id' => $order['id']])['values'];
886 $this->assertEquals('Deposit Bank Account', CRM_Core_PseudoConstant::getName('CRM_Core_BAO_FinancialTrxn', 'to_financial_account_id', $payments[0]['to_financial_account_id']));
887 $this->assertEquals('Payment Processor Account', CRM_Core_PseudoConstant::getName('CRM_Core_BAO_FinancialTrxn', 'to_financial_account_id', $payments[1]['to_financial_account_id']));
888 $this->assertEquals('Accounts Receivable', CRM_Core_PseudoConstant::getName('CRM_Core_BAO_FinancialTrxn', 'from_financial_account_id', $payments[0]['from_financial_account_id']));
889 $this->assertEquals('Accounts Receivable', CRM_Core_PseudoConstant::getName('CRM_Core_BAO_FinancialTrxn', 'from_financial_account_id', $payments[1]['from_financial_account_id']));
890 $this->assertEquals('Cash', CRM_Core_PseudoConstant::getName('CRM_Core_BAO_FinancialTrxn', 'payment_instrument_id', $payments[0]['payment_instrument_id']));
891 $this->assertEquals('EFT', CRM_Core_PseudoConstant::getName('CRM_Core_BAO_FinancialTrxn', 'payment_instrument_id', $payments[1]['payment_instrument_id']));
892 // $order = $this->callAPISuccessGetSingle('Order', ['id' => $processor->getID()]);
893 // $this->assertEquals('Cash', CRM_Core_PseudoConstant::getName('CRM_Core_BAO_FinancialTrxn', 'payment_instrument_id', $order['payment_instrument_id']));
894 }
895
896 /**
897 * Add a location to our event.
898 *
899 * @param int $eventID
900 *
901 * @throws \CRM_Core_Exception
902 */
903 protected function addLocationToEvent($eventID) {
904 $addressParams = [
905 'name' => 'event place',
906 'street_address' => 'streety street',
907 'location_type_id' => 1,
908 'is_primary' => 1,
909 ];
910 // api requires contact_id - perhaps incorrectly but use add to get past that.
911 $address = CRM_Core_BAO_Address::add($addressParams);
912
913 $location = $this->callAPISuccess('LocBlock', 'create', ['address_id' => $address->id]);
914 $this->callAPISuccess('Event', 'create', [
915 'id' => $eventID,
916 'loc_block_id' => $location['id'],
917 'is_show_location' => TRUE,
918 ]);
919 $this->validateAllPayments();
920 }
921
922 /**
923 * Check the created payment is valid.
924 *
925 * This is probably over-testing really since we are repetitively checking a basic function...
926 *
927 * @param int $paymentID
928 * @param int $contributionID
929 * @param int $amount
930 *
931 * @throws \CRM_Core_Exception
932 */
933 protected function checkPaymentIsValid($paymentID, $contributionID, $amount = 50) {
934 $payment = $this->callAPISuccess('Payment', 'getsingle', ['financial_trxn_id' => $paymentID]);
935 $this->assertEquals(7, $payment['from_financial_account_id']);
936 $this->assertEquals(6, $payment['to_financial_account_id']);
937 $this->assertEquals(1, $payment['status_id']);
938 $this->assertEquals(1, $payment['is_payment']);
939 $this->assertEquals($amount, $payment['total_amount']);
940
941 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', [
942 'entity_id' => $contributionID,
943 'entity_table' => 'civicrm_contribution',
944 'financial_trxn_id' => $payment['id'],
945 ]);
946
947 $this->assertEquals($eft['values'][$eft['id']]['amount'], $amount);
948 $this->validateAllPayments();
949 }
950
951 }