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