Follow up fixes to participant payment test
[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 * Should financials be checked after the test but before tear down.
27 *
28 * Ideally all tests (or at least all that call any financial api calls ) should do this but there
29 * are some test data issues and some real bugs currently blocking.
30 *
31 * @var bool
32 */
33 protected $isValidateFinancialsOnPostAssert = TRUE;
34
35 /**
36 * Setup function.
37 *
38 * @throws \CiviCRM_API3_Exception
39 */
40 public function setUp(): void {
41 parent::setUp();
42
43 $this->_apiversion = 3;
44 $this->_individualId = $this->individualCreate();
45 CRM_Core_Config::singleton()->userPermissionClass->permissions = [];
46 }
47
48 /**
49 * Clean up after each test.
50 *
51 * @throws \Exception
52 */
53 public function tearDown(): void {
54 $this->quickCleanUpFinancialEntities();
55 $this->quickCleanup(['civicrm_uf_match']);
56 unset(CRM_Core_Config::singleton()->userPermissionClass->permissions);
57 parent::tearDown();
58 }
59
60 /**
61 * Test Get Payment api.
62 *
63 * @throws \CRM_Core_Exception
64 */
65 public function testGetPayment(): void {
66 $p = [
67 'contact_id' => $this->_individualId,
68 'receive_date' => '2010-01-20',
69 'total_amount' => 100.00,
70 'financial_type_id' => $this->_financialTypeId,
71 'trxn_id' => 23456,
72 'contribution_status_id' => 1,
73 ];
74 $contribution = $this->callAPISuccess('contribution', 'create', $p);
75
76 $params = [
77 'contribution_id' => $contribution['id'],
78 'check_permissions' => TRUE,
79 ];
80 CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviCRM', 'administer CiviCRM'];
81 $this->callAPIFailure('payment', 'get', $params, 'API permission check failed for Payment/get call; insufficient permission: require access CiviCRM and access CiviContribute');
82
83 CRM_Core_Config::singleton()->userPermissionClass->permissions[] = 'access CiviContribute';
84 $this->callAPISuccess('payment', 'get', $params);
85
86 $payment = $this->callAPIAndDocument('payment', 'get', $params, __FUNCTION__, __FILE__);
87 $this->assertEquals(1, $payment['count']);
88
89 $expectedResult = [
90 $contribution['id'] => [
91 'total_amount' => 100,
92 'trxn_id' => 23456,
93 'trxn_date' => '2010-01-20 00:00:00',
94 'contribution_id' => $contribution['id'],
95 'is_payment' => 1,
96 ],
97 ];
98 $this->checkPaymentResult($payment, $expectedResult);
99 $this->callAPISuccess('Contribution', 'Delete', [
100 'id' => $contribution['id'],
101 ]);
102 $this->validateAllPayments();
103 }
104
105 /**
106 * Test multiple payments for contribution and assert if option
107 * and is_payment returns the correct list of payments.
108 *
109 * @throws \CRM_Core_Exception
110 */
111 public function testMultiplePaymentsForContribution(): void {
112 $params = [
113 'contact_id' => $this->_individualId,
114 'total_amount' => 100,
115 'contribution_status_id' => 'Pending',
116 ];
117 $contributionID = $this->contributionCreate($params);
118 $paymentParams = [
119 'contribution_id' => $contributionID,
120 'total_amount' => 20,
121 'trxn_date' => date('Y-m-d'),
122 ];
123 $this->callAPISuccess('payment', 'create', $paymentParams);
124 $paymentParams['total_amount'] = 30;
125 $this->callAPISuccess('payment', 'create', $paymentParams);
126
127 $paymentParams['total_amount'] = 50;
128 $this->callAPISuccess('payment', 'create', $paymentParams);
129
130 //check if contribution status is set to "Completed".
131 $contribution = $this->callAPISuccessGetSingle('Contribution', [
132 'id' => $contributionID,
133 ]);
134 $this->assertEquals('Completed', $contribution['contribution_status']);
135
136 //Get Payment using options
137 $getParams = [
138 'sequential' => 1,
139 'contribution_id' => $contributionID,
140 'is_payment' => 1,
141 'options' => ['limit' => 0, 'sort' => 'total_amount DESC'],
142 ];
143 $payments = $this->callAPISuccess('Payment', 'get', $getParams);
144 $this->assertEquals(3, $payments['count']);
145 foreach ([50, 30, 20] as $key => $total_amount) {
146 $this->assertEquals($total_amount, $payments['values'][$key]['total_amount']);
147 }
148 }
149
150 /**
151 * Retrieve Payment using trxn_id.
152 *
153 * @throws \CRM_Core_Exception
154 * @throws \CiviCRM_API3_Exception
155 */
156 public function testGetPaymentWithTrxnID(): void {
157 $individual2 = $this->individualCreate();
158 $params1 = [
159 'contact_id' => $this->_individualId,
160 'trxn_id' => 111111,
161 'total_amount' => 10,
162 ];
163 $contributionID1 = $this->contributionCreate($params1);
164
165 $params2 = [
166 'contact_id' => $individual2,
167 'trxn_id' => 222222,
168 'total_amount' => 20,
169 ];
170 $contributionID2 = $this->contributionCreate($params2);
171
172 $paymentParams = ['trxn_id' => 111111];
173 $payment = $this->callAPISuccess('payment', 'get', $paymentParams);
174 $expectedResult = [
175 $payment['id'] => [
176 'total_amount' => 10,
177 'trxn_id' => 111111,
178 'status_id' => 1,
179 'is_payment' => 1,
180 'contribution_id' => $contributionID1,
181 ],
182 ];
183 $this->checkPaymentResult($payment, $expectedResult);
184
185 $paymentParams = ['trxn_id' => 222222];
186 $payment = $this->callAPISuccess('payment', 'get', $paymentParams);
187 $expectedResult = [
188 $payment['id'] => [
189 'total_amount' => 20,
190 'trxn_id' => 222222,
191 'status_id' => 1,
192 'is_payment' => 1,
193 'contribution_id' => $contributionID2,
194 ],
195 ];
196 $this->checkPaymentResult($payment, $expectedResult);
197 $this->callAPISuccess('Payment', 'create', ['total_amount' => '-20', 'contribution_id' => $contributionID2]);
198 $this->validateAllPayments();
199 }
200
201 /**
202 * Test contribution receipts triggered by Payment.create with is_send_contribution_notification = TRUE.
203 *
204 * @throws \CRM_Core_Exception
205 */
206 public function testPaymentSendContributionReceipt(): void {
207 $mut = new CiviMailUtils($this);
208 $contribution = $this->createPartiallyPaidParticipantOrder();
209 $event = $this->callAPISuccess('Event', 'get', []);
210 $this->addLocationToEvent($event['id']);
211 $params = [
212 'contribution_id' => $contribution['id'],
213 'total_amount' => 150,
214 'check_number' => '345',
215 'trxn_date' => '2018-08-13 17:57:56',
216 'is_send_contribution_notification' => TRUE,
217 ];
218 $this->callAPISuccess('Payment', 'create', $params);
219 $contribution = $this->callAPISuccessGetSingle('Contribution', ['id' => $contribution['id']]);
220 $this->assertNotEmpty($contribution['receipt_date']);
221 $mut->checkMailLog([
222 'Price Field - Price Field 1 1 $ 100.00 $ 100.00',
223 'event place',
224 'streety street',
225 ]);
226 }
227
228 /**
229 * Test full refund when no payment has actually been record.
230 *
231 * @throws \CRM_Core_Exception
232 */
233 public function testFullRefundWithPaymentAlreadyRefunded(): void {
234 $params1 = [
235 'contact_id' => $this->_individualId,
236 'trxn_id' => 111111,
237 'total_amount' => 10,
238 ];
239 $contributionID1 = $this->contributionCreate($params1);
240 $paymentParams = ['contribution_id' => $contributionID1];
241 $this->callAPISuccess('Payment', 'create', ['total_amount' => '-10', 'contribution_id' => $contributionID1]);
242 $this->callAPISuccess('payment', 'get', $paymentParams);
243 $this->callAPISuccess('Payment', 'create', ['total_amount' => '-10', 'contribution_id' => $contributionID1]);
244 $this->callAPISuccess('payment', 'get', $paymentParams);
245 $this->validateAllPayments();
246 }
247
248 /**
249 * @throws \CRM_Core_Exception
250 */
251 public function testNegativePaymentWithNegativeContribution(): void {
252 $params1 = [
253 'contact_id' => $this->_individualId,
254 'trxn_id' => 111111,
255 'total_amount' => -10,
256 ];
257 $contributionID1 = $this->contributionCreate($params1);
258 $this->callAPISuccess('Payment', 'create', ['total_amount' => '-20', 'contribution_id' => $contributionID1]);
259 $paymentParams = ['contribution_id' => $contributionID1];
260 $this->callAPISuccess('payment', 'get', $paymentParams);
261 $this->validateAllPayments();
262 }
263
264 /**
265 * Test email receipt for partial payment.
266 *
267 * @throws \CRM_Core_Exception
268 */
269 public function testPaymentEmailReceipt(): void {
270 $mut = new CiviMailUtils($this);
271 $contribution = $this->createPartiallyPaidParticipantOrder();
272 $event = $this->callAPISuccess('Event', 'get', []);
273 $this->addLocationToEvent($event['id']);
274 $params = [
275 'contribution_id' => $contribution['id'],
276 'total_amount' => 50,
277 'check_number' => '345',
278 'trxn_date' => '2018-08-13 17:57:56',
279 ];
280 $payment = $this->callAPISuccess('payment', 'create', $params);
281 $this->checkPaymentResult($payment, [
282 $payment['id'] => [
283 'from_financial_account_id' => 7,
284 'to_financial_account_id' => 6,
285 'total_amount' => 50,
286 'status_id' => 1,
287 'is_payment' => 1,
288 ],
289 ]);
290
291 $this->callAPISuccess('Payment', 'sendconfirmation', ['id' => $payment['id']]);
292 $mut->assertSubjects(['Payment Receipt - Annual CiviCRM meet - Mr. Anthony Anderson II']);
293 $mut->checkMailLog([
294 'From: "FIXME" <info@EXAMPLE.ORG>',
295 'Dear Anthony,',
296 'Total Fee: $ 300.00',
297 'This Payment Amount: $ 50.00',
298 //150 was paid in the 1st payment.
299 'Balance Owed: $ 100.00',
300 'Event Information and Location',
301 'Paid By: Check',
302 'Check Number: 345',
303 'Transaction Date: August 13th, 2018 5:57 PM',
304 'event place',
305 'streety street',
306 ]);
307 $this->validateAllPayments();
308 }
309
310 /**
311 * Test email receipt for partial payment.
312 *
313 * @throws \CRM_Core_Exception
314 */
315 public function testPaymentEmailReceiptFullyPaid(): void {
316 $mut = new CiviMailUtils($this);
317 CRM_Core_Config::singleton()->userPermissionClass->permissions = ['access CiviContribute', 'edit contributions', 'access CiviCRM'];
318 $contribution = $this->createPartiallyPaidParticipantOrder();
319
320 $params = [
321 'contribution_id' => $contribution['id'],
322 'total_amount' => 150,
323 ];
324 $payment = $this->callAPISuccess('payment', 'create', $params);
325
326 // Here we set the email to an invalid email & use check_permissions, domain email should be used.
327 $email = $this->callAPISuccess('Email', 'create', ['contact_id' => 1, 'email' => 'bob@example.com']);
328 $this->callAPISuccess('Payment', 'sendconfirmation', ['id' => $payment['id'], 'from' => $email['id'], 'check_permissions' => 1]);
329 $mut->assertSubjects(['Payment Receipt - Annual CiviCRM meet - Mr. Anthony Anderson II', 'Registration Confirmation - Annual CiviCRM meet - Mr. Anthony Anderson II']);
330 $mut->checkMailLog([
331 'From: "FIXME" <info@EXAMPLE.ORG>',
332 'Dear Anthony,',
333 'Below you will find a receipt for this payment.',
334 'Total Fee: $ 300.00',
335 'This Payment Amount: $ 150.00',
336 'Balance Owed: $ 0.00',
337 'Thank you for completing this payment.',
338 ]);
339 }
340
341 /**
342 * Test email receipt for partial payment.
343 *
344 * @dataProvider getThousandSeparators
345 *
346 * @param string $thousandSeparator
347 *
348 * @throws \CRM_Core_Exception
349 */
350 public function testRefundEmailReceipt(string $thousandSeparator): void {
351 $this->setCurrencySeparators($thousandSeparator);
352 $decimalSeparator = ($thousandSeparator === ',' ? '.' : ',');
353 $mut = new CiviMailUtils($this);
354 $contribution = $this->createPartiallyPaidParticipantOrder();
355 $this->callAPISuccess('payment', 'create', [
356 'contribution_id' => $contribution['id'],
357 'total_amount' => 50,
358 'check_number' => '345',
359 'trxn_date' => '2018-08-13 17:57:56',
360 ]);
361
362 $payment = $this->callAPISuccess('payment', 'create', [
363 'contribution_id' => $contribution['id'],
364 'total_amount' => -30,
365 'trxn_date' => '2018-11-13 12:01:56',
366 'sequential' => TRUE,
367 ])['values'][0];
368
369 $expected = [
370 'from_financial_account_id' => 7,
371 'to_financial_account_id' => 6,
372 'total_amount' => -30,
373 'status_id' => CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_FinancialTrxn', 'status_id', 'Refunded'),
374 'is_payment' => 1,
375 ];
376 foreach ($expected as $key => $value) {
377 $this->assertEquals($value, $payment[$key], 'mismatch on key ' . $key);
378 }
379
380 $this->callAPISuccess('Payment', 'sendconfirmation', ['id' => $payment['id']]);
381 $mut->assertSubjects(['Refund Notification - Annual CiviCRM meet - Mr. Anthony Anderson II']);
382 $mut->checkMailLog([
383 'Dear Anthony,',
384 'A refund has been issued based on changes in your registration selections.',
385 'Total Fee: $ 300' . $decimalSeparator . '00',
386 'Refund Amount: $ -30' . $decimalSeparator . '00',
387 'Event Information and Location',
388 'Paid By: Check',
389 'Transaction Date: November 13th, 2018 12:01 PM',
390 'Total Paid: $ 170' . $decimalSeparator . '00',
391 ]);
392 }
393
394 /**
395 * Test adding a payment to a pending multi-line order.
396 *
397 * @throws \CRM_Core_Exception
398 */
399 public function testCreatePaymentPendingOrderNoLineItems(): void {
400 $order = $this->createPendingParticipantOrder();
401 $this->callAPISuccess('Payment', 'create', [
402 'order_id' => $order['id'],
403 'total_amount' => 50,
404 ]);
405 }
406
407 /**
408 * Test that Payment.create does not fail if the line items are missing.
409 *
410 * In the original spec it was anticipated that financial items would not be created
411 * for pending contributions in some circumstances. We've backed away from this and
412 * I mostly could not find a way to do it through the UI. But I did seem to once &
413 * I want to be sure that if they ARE missing no fatal occurs so this tests
414 * that in an artificial way.
415 *
416 * @throws \CRM_Core_Exception
417 * @throws \CiviCRM_API3_Exception
418 */
419 public function testAddPaymentMissingFinancialItems(): void {
420 $contribution = $this->callAPISuccess('Contribution', 'create', [
421 'total_amount' => 50,
422 'financial_type_id' => 'Donation',
423 'contact_id' => $this->individualCreate(),
424 'contribution_status_id' => 'Pending',
425 ]);
426 CRM_Core_DAO::executeQuery('DELETE FROM civicrm_financial_item');
427 $this->callAPISuccess('Payment', 'create', ['contribution_id' => $contribution['id'], 'payment_instrument_id' => 'Check', 'total_amount' => 5]);
428 }
429
430 /**
431 * Add participant with contribution
432 *
433 * @return array
434 *
435 * @throws \CRM_Core_Exception
436 */
437 protected function createPendingParticipantOrder(): array {
438 return $this->callAPISuccess('Order', 'create', $this->getParticipantOrderParams());
439 }
440
441 /**
442 * Test create payment api with no line item in params
443 *
444 * @throws \CRM_Core_Exception
445 */
446 public function testCreatePaymentNoLineItems(): void {
447 $contribution = $this->createPartiallyPaidParticipantOrder();
448
449 //Create partial payment
450 $params = [
451 'contribution_id' => $contribution['id'],
452 'total_amount' => 50,
453 ];
454 $payment = $this->callAPIAndDocument('payment', 'create', $params, __FUNCTION__, __FILE__);
455 $this->checkPaymentIsValid($payment['id'], $contribution['id']);
456
457 $params = [
458 'entity_table' => 'civicrm_financial_item',
459 'financial_trxn_id' => $payment['id'],
460 ];
461 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params);
462 $amounts = [33.33, 16.67];
463 foreach ($eft['values'] as $value) {
464 $this->assertEquals($value['amount'], array_pop($amounts));
465 }
466
467 // Now create payment to complete total amount of contribution
468 $params = [
469 'contribution_id' => $contribution['id'],
470 'total_amount' => 100,
471 ];
472 $payment = $this->callAPISuccess('payment', 'create', $params);
473 $expectedResult = [
474 $payment['id'] => [
475 'from_financial_account_id' => 7,
476 'to_financial_account_id' => 6,
477 'total_amount' => 100,
478 'status_id' => 1,
479 'is_payment' => 1,
480 ],
481 ];
482 $this->checkPaymentResult($payment, $expectedResult);
483 $params = [
484 'entity_table' => 'civicrm_financial_item',
485 'financial_trxn_id' => $payment['id'],
486 ];
487 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params);
488 $amounts = [66.67, 33.33];
489 foreach ($eft['values'] as $value) {
490 $this->assertEquals($value['amount'], array_pop($amounts));
491 }
492 // Check contribution for completed status
493 $contribution = $this->callAPISuccess('contribution', 'get', ['id' => $contribution['id']]);
494
495 $this->assertEquals('Completed', $contribution['values'][$contribution['id']]['contribution_status']);
496 $this->assertEquals(300.00, $contribution['values'][$contribution['id']]['total_amount']);
497 $paymentParticipant = [
498 'contribution_id' => $contribution['id'],
499 ];
500 $this->callAPISuccessGetCount('ParticipantPayment', $paymentParticipant, 2);
501 $this->callAPISuccessGetCount('Participant', ['status_id' => 'Registered'], 2);
502 }
503
504 /**
505 * Function to assert db values
506 *
507 * @param array $payment
508 * @param array $expectedResult
509 *
510 * @throws \CRM_Core_Exception
511 */
512 public function checkPaymentResult(array $payment, array $expectedResult): void {
513 $refreshedPayment = $this->callAPISuccessGetSingle('Payment', ['financial_trxn_id' => $payment['id']]);
514 foreach ($expectedResult[$payment['id']] as $key => $value) {
515 $this->assertEquals($refreshedPayment[$key], $value, 'mismatch on ' . $key); $this->assertEquals($refreshedPayment[$key], $value, 'mismatch on ' . $key);
516 }
517 }
518
519 /**
520 * Test create payment api with line item in params
521 *
522 * @throws \CRM_Core_Exception
523 */
524 public function testCreatePaymentLineItems(): void {
525 $contribution = $this->createPartiallyPaidParticipantOrder();
526 $lineItems = $this->callAPISuccess('LineItem', 'get', ['contribution_id' => $contribution['id']])['values'];
527
528 // Create partial payment by passing line item array is params.
529 $params = [
530 'contribution_id' => $contribution['id'],
531 'total_amount' => 50,
532 ];
533 $amounts = [40, 10];
534 foreach ($lineItems as $id => $ignore) {
535 $params['line_item'][] = [$id => array_pop($amounts)];
536 }
537 $payment = $this->callAPIAndDocument('Payment', 'create', $params, __FUNCTION__, __FILE__, 'Payment with line item', 'CreatePaymentWithLineItems');
538 $this->checkPaymentIsValid($payment['id'], $contribution['id']);
539
540 $params = [
541 'entity_table' => 'civicrm_financial_item',
542 'financial_trxn_id' => $payment['id'],
543 'return' => ['entity_id.entity_id', 'amount'],
544 ];
545 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params)['values'];
546 $this->assertCount(2, $eft);
547 $amounts = [40, 10];
548 foreach ($eft as $value) {
549 $this->assertEquals($value['amount'], array_pop($amounts));
550 }
551
552 // Now create payment to complete total amount of contribution
553 $params = [
554 'contribution_id' => $contribution['id'],
555 'total_amount' => 100,
556 ];
557 $amounts = [80, 20];
558 foreach ($lineItems as $id => $ignore) {
559 $params['line_item'][] = [$id => array_pop($amounts)];
560 }
561 $payment = $this->callAPISuccess('Payment', 'create', $params);
562 $expectedResult = [
563 $payment['id'] => [
564 'from_financial_account_id' => 7,
565 'to_financial_account_id' => 6,
566 'total_amount' => 100,
567 'status_id' => 1,
568 'is_payment' => 1,
569 ],
570 ];
571 $this->checkPaymentResult($payment, $expectedResult);
572 $params = [
573 'entity_table' => 'civicrm_financial_item',
574 'financial_trxn_id' => $payment['id'],
575 ];
576 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params)['values'];
577 $this->assertCount(2, $eft);
578 $amounts = [80, 20];
579 foreach ($eft as $value) {
580 $this->assertEquals($value['amount'], array_pop($amounts));
581 }
582 // Check contribution for completed status
583 $contribution = $this->callAPISuccess('Contribution', 'get', ['id' => $contribution['id']]);
584
585 $this->assertEquals('Completed', $contribution['values'][$contribution['id']]['contribution_status']);
586 $this->assertEquals(300.00, $contribution['values'][$contribution['id']]['total_amount']);
587 $paymentParticipant = [
588 'contribution_id' => $contribution['id'],
589 ];
590 $this->callAPISuccessGetCount('ParticipantPayment', $paymentParticipant, 2);
591 $this->callAPISuccessGetCount('participant', ['status_id' => 'Registered'], 2);
592 }
593
594 /**
595 * Test negative payment using create API.
596 *
597 * @throws \CRM_Core_Exception
598 */
599 public function testRefundPayment(): void {
600 $result = $this->callAPISuccess('Contribution', 'create', [
601 'financial_type_id' => 'Donation',
602 'total_amount' => 100,
603 'contact_id' => $this->_individualId,
604 ]);
605 $contributionID = $result['id'];
606
607 //Refund a part of the main amount.
608 $this->callAPISuccess('Payment', 'create', [
609 'contribution_id' => $contributionID,
610 'total_amount' => -10,
611 ]);
612
613 $contribution = $this->callAPISuccessGetSingle('Contribution', [
614 'return' => ['contribution_status_id'],
615 'id' => $contributionID,
616 ]);
617 //Still we've a status of Completed after refunding a partial amount.
618 $this->assertEquals('Completed', $contribution['contribution_status']);
619
620 //Refund the complete amount.
621 $this->callAPISuccess('Payment', 'create', [
622 'contribution_id' => $contributionID,
623 'total_amount' => -90,
624 ]);
625 $contribution = $this->callAPISuccessGetSingle('Contribution', [
626 'return' => ['contribution_status_id'],
627 'id' => $contributionID,
628 ]);
629 //Assert if main contribution status is updated to "Refunded".
630 $this->assertEquals('Refunded Label**', $contribution['contribution_status']);
631 }
632
633 /**
634 * Test negative payment using create API when the "cancelled_payment_id" param is set.
635 *
636 * @throws \CRM_Core_Exception
637 */
638 public function testRefundPaymentWithCancelledPaymentId(): void {
639 $result = $this->callAPISuccess('Contribution', 'create', [
640 'financial_type_id' => 'Donation',
641 'total_amount' => 100,
642 'contact_id' => $this->_individualId,
643 ]);
644 $contributionID = $result['id'];
645
646 //Refund the complete amount.
647 $this->callAPISuccess('Payment', 'create', [
648 'contribution_id' => $contributionID,
649 'total_amount' => -100,
650 'cancelled_payment_id' => (int) $this->callAPISuccess('Payment', 'get', [])['id'],
651 ]);
652 $contribution = $this->callAPISuccessGetSingle('Contribution', [
653 'return' => ['contribution_status_id'],
654 'id' => $contributionID,
655 ]);
656 //Assert if main contribution status is updated to "Refunded".
657 $this->assertEquals('Refunded Label**', $contribution['contribution_status']);
658 }
659
660 /**
661 * Test cancel payment api
662 *
663 * @throws \CRM_Core_Exception
664 */
665 public function testCancelPayment(): void {
666 CRM_Core_Config::singleton()->userPermissionClass->permissions = ['administer CiviCRM', 'access CiviContribute'];
667 $contribution = $this->createPartiallyPaidParticipantOrder();
668
669 $params = [
670 'contribution_id' => $contribution['id'],
671 ];
672
673 $payment = $this->callAPISuccess('payment', 'get', $params);
674 $this->assertEquals(1, $payment['count']);
675
676 $cancelParams = [
677 'id' => $payment['id'],
678 'check_permissions' => TRUE,
679 ];
680 $this->callAPIFailure('payment', 'cancel', $cancelParams, 'API permission check failed for Payment/cancel call; insufficient permission: require access CiviCRM and access CiviContribute and edit contributions');
681
682 array_push(CRM_Core_Config::singleton()->userPermissionClass->permissions, 'access CiviCRM', 'edit contributions');
683
684 $this->callAPIAndDocument('payment', 'cancel', $cancelParams, __FUNCTION__, __FILE__);
685
686 $payment = $this->callAPISuccess('payment', 'get', $params);
687 $this->assertEquals(2, $payment['count']);
688 $amounts = [-150.00, 150.00];
689 foreach ($payment['values'] as $value) {
690 $this->assertEquals($value['total_amount'], array_pop($amounts), 'Mismatch total amount');
691 }
692 }
693
694 /**
695 * Test delete payment api
696 *
697 * @throws \CRM_Core_Exception
698 */
699 public function testDeletePayment(): void {
700 CRM_Core_Config::singleton()->userPermissionClass->permissions = ['administer CiviCRM', 'access CiviContribute'];
701 $contribution = $this->createPartiallyPaidParticipantOrder();
702
703 $params = [
704 'contribution_id' => $contribution['id'],
705 ];
706
707 $payment = $this->callAPISuccessGetSingle('payment', $params);
708
709 $deleteParams = [
710 'id' => $payment['id'],
711 'check_permissions' => TRUE,
712 ];
713 $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');
714
715 array_push(CRM_Core_Config::singleton()->userPermissionClass->permissions, 'access CiviCRM', 'delete in CiviContribute');
716 $this->callAPIAndDocument('payment', 'delete', $deleteParams, __FUNCTION__, __FILE__);
717 $this->callAPISuccessGetCount('payment', $params, 0);
718
719 $this->callAPISuccess('Contribution', 'Delete', ['id' => $contribution['id']]);
720 }
721
722 /**
723 * Test update payment api.
724 *
725 * 1) create a contribution for $300 with a partial payment of $150
726 * - this results in 2 financial transactions. The accounts receivable transaction is linked
727 * via entity_financial_trxns to the 2 line items. The $150 payment is not linked to the line items
728 * so the line items are fully allocated even though they are only half paid.
729 *
730 * 2) add a payment of $50 -
731 * This payment transaction IS linked to the line items so $350 of the $300 in line items is allocated
732 * but $200 is paid
733 *
734 * 3) update that payment to be $100
735 * This results in a negative and a positive payment ($50 & $100) - the negative payment results in
736 * financial_items but the positive payment does not.
737 *
738 * The final result is we have
739 * - 1 partly paid contribution of $300
740 * - payment financial_trxns totalling $250
741 * - 1 Accounts receivable financial_trxn totalling $300
742 * - 2 financial items totalling $300 linked to the Accounts receivable financial_trxn
743 * - 6 entries in the civicrm_entity_financial_trxn linked to line items - totalling $450.
744 * - 5 entries in the civicrm_entity_financial_trxn linked to contributions - totalling $550.
745 *
746 * @throws \CRM_Core_Exception
747 */
748 public function testUpdatePayment(): void {
749 CRM_Core_Config::singleton()->userPermissionClass->permissions = ['administer CiviCRM', 'access CiviContribute', 'edit contributions'];
750 $contribution = $this->createPartiallyPaidParticipantOrder();
751
752 //Create partial payment by passing line item array is params
753 $params = [
754 'contribution_id' => $contribution['id'],
755 'total_amount' => 50,
756 ];
757
758 $payment = $this->callAPISuccess('payment', 'create', $params);
759 $expectedResult = [
760 $payment['id'] => [
761 'from_financial_account_id' => 7,
762 'to_financial_account_id' => 6,
763 'total_amount' => 50,
764 'status_id' => 1,
765 'is_payment' => 1,
766 ],
767 ];
768 $this->checkPaymentResult($payment, $expectedResult);
769
770 $params = [
771 'entity_table' => 'civicrm_financial_item',
772 'financial_trxn_id' => $payment['id'],
773 ];
774 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params);
775 $amounts = [33.33, 16.67];
776 foreach ($eft['values'] as $value) {
777 $this->assertEquals($value['amount'], array_pop($amounts));
778 }
779
780 // update the amount for payment
781 $params = [
782 'contribution_id' => $contribution['id'],
783 'total_amount' => 100,
784 'id' => $payment['id'],
785 'check_permissions' => TRUE,
786 ];
787 // @todo - move this permissions test to it's own test - it just confuses here.
788 CRM_Core_Config::singleton()->userPermissionClass->permissions = ['administer CiviCRM', 'access CiviContribute'];
789 $this->callAPIFailure('payment', 'create', $params, 'API permission check failed for Payment/create call; insufficient permission: require access CiviCRM and access CiviContribute and edit contributions');
790
791 CRM_Core_Config::singleton()->userPermissionClass->permissions = ['administer CiviCRM', 'access CiviContribute', 'access CiviCRM', 'edit contributions'];
792 $payment = $this->callAPIAndDocument('payment', 'create', $params, __FUNCTION__, __FILE__, 'Update Payment', 'UpdatePayment');
793
794 $this->validateAllPayments();
795 // Check for proportional cancelled payment against line items.
796 $minParams = [
797 'entity_table' => 'civicrm_financial_item',
798 'financial_trxn_id' => $payment['id'] - 1,
799 ];
800
801 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $minParams)['values'];
802 $this->assertCount(2, $eft);
803 $amounts = [-33.33, -16.67];
804
805 foreach ($eft as $value) {
806 $this->assertEquals($value['amount'], array_pop($amounts));
807 }
808
809 // Check for proportional updated payment against line items.
810 $params = [
811 'entity_table' => 'civicrm_financial_item',
812 'financial_trxn_id' => $payment['id'],
813 ];
814 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params)['values'];
815 $amounts = [66.67, 33.33];
816 foreach ($eft as $value) {
817 $this->assertEquals($value['amount'], array_pop($amounts));
818 }
819 $items = $this->callAPISuccess('FinancialItem', 'get', [])['values'];
820 $this->assertCount(2, $items);
821 $itemSum = 0;
822 foreach ($items as $item) {
823 $this->assertEquals('civicrm_line_item', $item['entity_table']);
824 $itemSum += $item['amount'];
825 }
826 $this->assertEquals(300, $itemSum);
827
828 $params = [
829 'contribution_id' => $contribution['id'],
830 ];
831 $payment = $this->callAPISuccess('payment', 'get', $params);
832 $amounts = [100.00, -50.00, 50.00, 150.00];
833 foreach ($payment['values'] as $value) {
834 $amount = array_pop($amounts);
835 $this->assertEquals($value['total_amount'], $amount, 'Mismatch total amount');
836
837 // Check entity financial trxn created properly
838 $params = [
839 'entity_id' => $contribution['id'],
840 'entity_table' => 'civicrm_contribution',
841 'financial_trxn_id' => $value['id'],
842 ];
843 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params);
844 $this->assertEquals($eft['values'][$eft['id']]['amount'], $amount);
845 }
846 }
847
848 /**
849 * Test that a contribution can be overpaid with the payment api.
850 *
851 * @throws \API_Exception
852 * @throws \CRM_Core_Exception
853 * @throws \CiviCRM_API3_Exception
854 * @throws \Civi\API\Exception\UnauthorizedException
855 */
856 public function testCreatePaymentOverPay(): void {
857 $contributionID = $this->contributionCreate(['contact_id' => $this->individualCreate()]);
858 $payment = $this->callAPISuccess('Payment', 'create', ['total_amount' => 5, 'order_id' => $contributionID]);
859 $contribution = $this->callAPISuccessGetSingle('Contribution', ['id' => $contributionID]);
860 $this->assertEquals('Completed', $contribution['contribution_status']);
861 $this->callAPISuccessGetCount('EntityFinancialTrxn', ['financial_trxn_id' => $payment['id'], 'entity_table' => 'civicrm_financial_item'], 0);
862 $this->validateAllPayments();
863 $this->validateAllContributions();
864 }
865
866 /**
867 * Test create payment api for pay later contribution
868 *
869 * @throws \CRM_Core_Exception
870 * @throws \CiviCRM_API3_Exception
871 */
872 public function testCreatePaymentPayLater(): void {
873 $this->createLoggedInUser();
874 $processorID = $this->paymentProcessorCreate();
875 $contributionParams = [
876 'total_amount' => 100,
877 'currency' => 'USD',
878 'contact_id' => $this->_individualId,
879 'financial_type_id' => 1,
880 'contribution_status_id' => 2,
881 'is_pay_later' => 1,
882 ];
883 $contribution = $this->callAPISuccess('Contribution', 'create', $contributionParams);
884 //add payment for pay later transaction
885 $params = [
886 'contribution_id' => $contribution['id'],
887 'total_amount' => 100,
888 'card_type_id' => 'Visa',
889 'pan_truncation' => '1234',
890 'trxn_result_code' => 'Startling success',
891 'payment_instrument_id' => $processorID,
892 'trxn_id' => 1234,
893 ];
894 $payment = $this->callAPISuccess('Payment', 'create', $params);
895 $expectedResult = [
896 $payment['id'] => [
897 'from_financial_account_id' => 7,
898 'to_financial_account_id' => 6,
899 'total_amount' => 100,
900 'status_id' => 1,
901 'is_payment' => 1,
902 'card_type_id' => 1,
903 'pan_truncation' => '1234',
904 'trxn_result_code' => 'Startling success',
905 'trxn_id' => 1234,
906 'payment_instrument_id' => 1,
907 ],
908 ];
909 $this->checkPaymentResult($payment, $expectedResult);
910 // Check entity financial trxn created properly
911 $params = [
912 'entity_id' => $contribution['id'],
913 'entity_table' => 'civicrm_contribution',
914 'financial_trxn_id' => $payment['id'],
915 ];
916 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params);
917 $this->assertEquals(100, $eft['values'][$eft['id']]['amount']);
918 $params = [
919 'entity_table' => 'civicrm_financial_item',
920 'financial_trxn_id' => $payment['id'],
921 ];
922 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params);
923 $this->assertEquals(100, $eft['values'][$eft['id']]['amount']);
924 // Check contribution for completed status
925 $contribution = $this->callAPISuccess('contribution', 'get', ['id' => $contribution['id']]);
926 $this->assertEquals('Completed', $contribution['values'][$contribution['id']]['contribution_status']);
927 $this->assertEquals(100.00, $contribution['values'][$contribution['id']]['total_amount']);
928 $this->callAPISuccess('Contribution', 'Delete', [
929 'id' => $contribution['id'],
930 ]);
931 $this->validateAllPayments();
932 }
933
934 /**
935 * Test net amount is set when fee amount is passed in.
936 *
937 * @throws \CRM_Core_Exception
938 */
939 public function testNetAmount(): void {
940 $order = $this->createPendingParticipantOrder();
941 $payment = $this->callAPISuccess('Payment', 'create', ['order_id' => $order['id'], 'total_amount' => 10, 'fee_amount' => .25]);
942 $this->assertEquals('9.75', $this->callAPISuccessGetValue('Payment', ['id' => $payment['id'], 'return' => 'net_amount']));
943 }
944
945 /**
946 * Test create payment api for pay later contribution with partial payment.
947 *
948 * https://lab.civicrm.org/dev/financial/issues/69
949 * @throws \CRM_Core_Exception
950 */
951 public function testCreatePaymentIncompletePaymentPartialPayment(): void {
952 $contributionParams = [
953 'total_amount' => 100,
954 'currency' => 'USD',
955 'contact_id' => $this->_individualId,
956 'financial_type_id' => 1,
957 'contribution_status_id' => 2,
958 ];
959 $contribution = $this->callAPISuccess('Contribution', 'create', $contributionParams);
960 $checkNumber1 = 'C111';
961 $this->callAPISuccess('Payment', 'create', [
962 'contribution_id' => $contribution['id'],
963 'total_amount' => 50,
964 'payment_instrument_id' => 'Check',
965 'check_number' => $checkNumber1,
966 ]);
967 $payments = $this->callAPISuccess('Payment', 'get', ['contribution_id' => $contribution['id']])['values'];
968 $this->assertCount(1, $payments);
969 $this->validateAllPayments();
970
971 $checkNumber2 = 'C222';
972 $this->callAPISuccess('Payment', 'create', [
973 'contribution_id' => $contribution['id'],
974 'total_amount' => 20,
975 'payment_instrument_id' => 'Check',
976 'check_number' => $checkNumber2,
977 ]);
978 $expectedConcatenatedCheckNumbers = implode(',', [$checkNumber1, $checkNumber2]);
979 //Assert check number is concatenated on the main contribution.
980 $contributionValues = $this->callAPISuccess('Contribution', 'getsingle', ['id' => $contribution['id']]);
981 $this->assertEquals($expectedConcatenatedCheckNumbers, $contributionValues['check_number']);
982 }
983
984 /**
985 * Test create payment api for failed contribution.
986 *
987 * @throws \CRM_Core_Exception
988 * @throws \CiviCRM_API3_Exception
989 */
990 public function testCreatePaymentOnFailedContribution(): void {
991 $this->createLoggedInUser();
992 //Create a direct Failed Contribution (no ft record inserted).
993 $contributionParams = [
994 'total_amount' => 50,
995 'currency' => 'USD',
996 'contact_id' => $this->_individualId,
997 'financial_type_id' => 1,
998 'contribution_status_id' => 'Failed',
999 ];
1000 $contribution = $this->callAPISuccess('Contribution', 'create', $contributionParams);
1001
1002 //Complete the payment in a single call.
1003 $params = [
1004 'contribution_id' => $contribution['id'],
1005 'total_amount' => 50,
1006 ];
1007 $this->callAPISuccess('Payment', 'create', $params);
1008
1009 //Verify 2 rows are added to the financial trxn as payment is moved from
1010 //Failed -> Pending -> Completed, i.e, 0 -> 7(Account receivable) -> 6 (Deposit Bank).
1011 $params = [
1012 'entity_id' => $contribution['id'],
1013 'entity_table' => 'civicrm_contribution',
1014 ];
1015 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params);
1016 $this->assertEquals(2, $eft['count']);
1017
1018 //Test 2
1019 //Create a Pending Contribution so an FT record is inserted.
1020 $contributionParams = [
1021 'total_amount' => 100,
1022 'currency' => 'USD',
1023 'contact_id' => $this->_individualId,
1024 'financial_type_id' => 1,
1025 'contribution_status_id' => 'Pending',
1026 'is_pay_later' => 1,
1027 ];
1028 $contribution = $this->callAPISuccess('Order', 'create', $contributionParams);
1029
1030 //Mark it as failed. No FT record inserted on this update
1031 //so the payment is still in the account receivable account id 7.
1032 $this->callAPISuccess('Contribution', 'create', [
1033 'id' => $contribution['id'],
1034 'contribution_status_id' => 'Failed',
1035 ]);
1036 $this->createPartialPaymentOnContribution($contribution['id'], 60, 100.00);
1037
1038 //Call payment create on the failed contribution.
1039 $params = [
1040 'contribution_id' => $contribution['id'],
1041 'total_amount' => 40,
1042 ];
1043 $payment = $this->callAPISuccess('Payment', 'create', $params);
1044 $expectedResult = [
1045 $payment['id'] => [
1046 'from_financial_account_id' => 7,
1047 'to_financial_account_id' => 6,
1048 'total_amount' => 40,
1049 'status_id' => 1,
1050 'is_payment' => 1,
1051 ],
1052 ];
1053 $this->checkPaymentResult($payment, $expectedResult);
1054
1055 //Check total ft rows are 4: 2 from initial pending + partial payment
1056 //+ 2 for failed -> completed transition.
1057 $params = [
1058 'entity_id' => $contribution['id'],
1059 'entity_table' => 'civicrm_contribution',
1060 ];
1061 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params);
1062 $this->assertEquals(4, $eft['count']);
1063
1064 $this->validateAllPayments();
1065 }
1066
1067 /**
1068 * Create partial payment for contribution
1069 *
1070 * @param $contributionID
1071 * @param $partialAmount
1072 * @param $totalAmount
1073 *
1074 * @throws \CRM_Core_Exception
1075 */
1076 public function createPartialPaymentOnContribution($contributionID, $partialAmount, $totalAmount): void {
1077 //Create partial payment
1078 $params = [
1079 'contribution_id' => $contributionID,
1080 'total_amount' => $partialAmount,
1081 ];
1082 $payment = $this->callAPISuccess('Payment', 'create', $params);
1083 $expectedResult = [
1084 $payment['id'] => [
1085 'total_amount' => $partialAmount,
1086 'status_id' => 1,
1087 'is_payment' => 1,
1088 ],
1089 ];
1090 $this->checkPaymentResult($payment, $expectedResult);
1091 // Check entity financial trxn created properly
1092 $params = [
1093 'entity_id' => $contributionID,
1094 'entity_table' => 'civicrm_contribution',
1095 'financial_trxn_id' => $payment['id'],
1096 ];
1097 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params);
1098 $this->assertEquals($eft['values'][$eft['id']]['amount'], $partialAmount);
1099 $params = [
1100 'entity_table' => 'civicrm_financial_item',
1101 'financial_trxn_id' => $payment['id'],
1102 ];
1103 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params);
1104 $this->assertEquals($eft['values'][$eft['id']]['amount'], $partialAmount);
1105 $contribution = $this->callAPISuccess('contribution', 'get', ['id' => $contributionID]);
1106 $this->assertEquals('Partially paid', $contribution['values'][$contribution['id']]['contribution_status']);
1107 $this->assertEquals($contribution['values'][$contribution['id']]['total_amount'], $totalAmount);
1108 }
1109
1110 /**
1111 * Test create payment api for pay later contribution with partial payment.
1112 *
1113 * @throws \CRM_Core_Exception
1114 * @throws \CiviCRM_API3_Exception
1115 */
1116 public function testCreatePaymentPayLaterPartialPayment(): void {
1117 $this->createLoggedInUser();
1118 $contributionParams = [
1119 'total_amount' => 100,
1120 'currency' => 'USD',
1121 'contact_id' => $this->_individualId,
1122 'financial_type_id' => 1,
1123 'contribution_status_id' => 2,
1124 'is_pay_later' => 1,
1125 ];
1126 $contribution = $this->callAPISuccess('Order', 'create', $contributionParams);
1127 $this->createPartialPaymentOnContribution($contribution['id'], 60, 100.00);
1128
1129 //Create full payment
1130 $params = [
1131 'contribution_id' => $contribution['id'],
1132 'total_amount' => 40,
1133 ];
1134 // Rename the 'completed' status label first to check that we are not using the labels!
1135 $this->callAPISuccess('OptionValue', 'get', ['name' => 'Completed', 'option_group_id' => 'contribution_status', 'api.OptionValue.create' => ['label' => 'Unicorn']]);
1136 $payment = $this->callAPISuccess('Payment', 'create', $params);
1137 $expectedResult = [
1138 $payment['id'] => [
1139 'from_financial_account_id' => 7,
1140 'to_financial_account_id' => 6,
1141 'total_amount' => 40,
1142 'status_id' => 1,
1143 'is_payment' => 1,
1144 ],
1145 ];
1146 $this->checkPaymentResult($payment, $expectedResult);
1147 // Check entity financial trxn created properly
1148 $params = [
1149 'entity_id' => $contribution['id'],
1150 'entity_table' => 'civicrm_contribution',
1151 'financial_trxn_id' => $payment['id'],
1152 ];
1153 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params);
1154 $this->assertEquals(40, $eft['values'][$eft['id']]['amount']);
1155 $params = [
1156 'entity_table' => 'civicrm_financial_item',
1157 'financial_trxn_id' => $payment['id'],
1158 ];
1159 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params);
1160 $this->assertEquals(40, $eft['values'][$eft['id']]['amount']);
1161 // Check contribution for completed status
1162 $contribution = $this->callAPISuccess('contribution', 'get', ['id' => $contribution['id']]);
1163 $this->assertEquals('Unicorn', $contribution['values'][$contribution['id']]['contribution_status']);
1164 $this->assertEquals(100.00, $contribution['values'][$contribution['id']]['total_amount']);
1165 $this->callAPISuccess('Contribution', 'Delete', [
1166 'id' => $contribution['id'],
1167 ]);
1168 $this->callAPISuccess('OptionValue', 'get', ['name' => 'Completed', 'option_group_id' => 'contribution_status', 'api.OptionValue.create' => ['label' => 'Completed']]);
1169 $this->callAPISuccessGetCount('Activity', ['target_contact_id' => $this->_individualId, 'activity_type_id' => 'Payment'], 2);
1170 }
1171
1172 /**
1173 * Test that Payment.create uses the to_account of the payment processor.
1174 *
1175 * @throws \CiviCRM_API3_Exception
1176 * @throws \CRM_Core_Exception
1177 */
1178 public function testPaymentWithProcessorWithOddFinancialAccount(): void {
1179 $processor = $this->dummyProcessorCreate(['financial_account_id' => 'Deposit Bank Account', 'payment_instrument_id' => 'Cash']);
1180 $processor2 = $this->dummyProcessorCreate(['financial_account_id' => 'Payment Processor Account', 'name' => 'p2', 'payment_instrument_id' => 'EFT']);
1181 $contributionParams = [
1182 'total_amount' => 100,
1183 'currency' => 'USD',
1184 'contact_id' => $this->_individualId,
1185 'financial_type_id' => 1,
1186 'contribution_status_id' => 'Pending',
1187 ];
1188 $order = $this->callAPISuccess('Order', 'create', $contributionParams);
1189 $this->callAPISuccess('Payment', 'create', ['payment_processor_id' => $processor->getID(), 'total_amount' => 6, 'contribution_id' => $order['id']]);
1190 $this->callAPISuccess('Payment', 'create', ['payment_processor_id' => $processor2->getID(), 'total_amount' => 15, 'contribution_id' => $order['id']]);
1191 $payments = $this->callAPISuccess('Payment', 'get', ['sequential' => 1, 'contribution_id' => $order['id']])['values'];
1192 $this->assertEquals('Deposit Bank Account', CRM_Core_PseudoConstant::getName('CRM_Core_BAO_FinancialTrxn', 'to_financial_account_id', $payments[0]['to_financial_account_id']));
1193 $this->assertEquals('Payment Processor Account', CRM_Core_PseudoConstant::getName('CRM_Core_BAO_FinancialTrxn', 'to_financial_account_id', $payments[1]['to_financial_account_id']));
1194 $this->assertEquals('Accounts Receivable', CRM_Core_PseudoConstant::getName('CRM_Core_BAO_FinancialTrxn', 'from_financial_account_id', $payments[0]['from_financial_account_id']));
1195 $this->assertEquals('Accounts Receivable', CRM_Core_PseudoConstant::getName('CRM_Core_BAO_FinancialTrxn', 'from_financial_account_id', $payments[1]['from_financial_account_id']));
1196 $this->assertEquals('Cash', CRM_Core_PseudoConstant::getName('CRM_Core_BAO_FinancialTrxn', 'payment_instrument_id', $payments[0]['payment_instrument_id']));
1197 $this->assertEquals('EFT', CRM_Core_PseudoConstant::getName('CRM_Core_BAO_FinancialTrxn', 'payment_instrument_id', $payments[1]['payment_instrument_id']));
1198 // $order = $this->callAPISuccessGetSingle('Order', ['id' => $processor->getID()]);
1199 // $this->assertEquals('Cash', CRM_Core_PseudoConstant::getName('CRM_Core_BAO_FinancialTrxn', 'payment_instrument_id', $order['payment_instrument_id']));
1200 }
1201
1202 /**
1203 * Add a location to our event.
1204 *
1205 * @param int $eventID
1206 *
1207 * @throws \CRM_Core_Exception
1208 */
1209 protected function addLocationToEvent(int $eventID): void {
1210 $addressParams = [
1211 'name' => 'event place',
1212 'street_address' => 'streety street',
1213 'location_type_id' => 1,
1214 'is_primary' => 1,
1215 ];
1216 // api requires contact_id - perhaps incorrectly but use add to get past that.
1217 $address = CRM_Core_BAO_Address::add($addressParams);
1218
1219 $location = $this->callAPISuccess('LocBlock', 'create', ['address_id' => $address->id]);
1220 $this->callAPISuccess('Event', 'create', [
1221 'id' => $eventID,
1222 'loc_block_id' => $location['id'],
1223 'is_show_location' => TRUE,
1224 ]);
1225 }
1226
1227 /**
1228 * Check the created payment is valid.
1229 *
1230 * This is probably over-testing really since we are repetitively checking a basic function...
1231 *
1232 * @param int $paymentID
1233 * @param int $contributionID
1234 * @param int $amount
1235 *
1236 * @throws \CRM_Core_Exception
1237 */
1238 protected function checkPaymentIsValid(int $paymentID, int $contributionID, int $amount = 50): void {
1239 $payment = $this->callAPISuccess('Payment', 'getsingle', ['financial_trxn_id' => $paymentID]);
1240 $this->assertEquals(7, $payment['from_financial_account_id']);
1241 $this->assertEquals(6, $payment['to_financial_account_id']);
1242 $this->assertEquals(1, $payment['status_id']);
1243 $this->assertEquals(1, $payment['is_payment']);
1244 $this->assertEquals($amount, $payment['total_amount']);
1245
1246 $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', [
1247 'entity_id' => $contributionID,
1248 'entity_table' => 'civicrm_contribution',
1249 'financial_trxn_id' => $payment['id'],
1250 ]);
1251
1252 $this->assertEquals($eft['values'][$eft['id']]['amount'], $amount);
1253 }
1254
1255 /**
1256 * This test was introduced in
1257 * https://github.com/civicrm/civicrm-core/pull/17688 to ensure that a
1258 * contribution's date is not set to today's date when a payment is received,
1259 * and that the contribution's trxn_id is set to that of the payment.
1260 *
1261 * This tests the current behaviour, but there are questions about whether
1262 * that's right.
1263 *
1264 * The current behaviour is that when a payment is received that completes a
1265 * contribution: the contribution's receive_date is set to that of the
1266 * payment (passed to Payment.create as trxn_date).
1267 *
1268 * But why *should* we update the receive_date at all?
1269 *
1270 * If we decide that receive_date should not be touched, just change
1271 * $trxnDate for $trxnID as detailed in the code comment below, which will
1272 * still make sure we're not setting today's date, as well as confirming
1273 * that the original date is not changed.
1274 *
1275 * @see https://github.com/civicrm/civicrm-core/pull/17688
1276 * @see https://lab.civicrm.org/dev/financial/-/issues/139
1277 *
1278 * @throws \CRM_Core_Exception
1279 * @throws \CiviCRM_API3_Exception
1280 */
1281 public function testPaymentCreateTrxnIdAndDates(): void {
1282
1283 $trxnDate = '2010-01-01 09:00:00';
1284 $trxnID = 'abcd';
1285 $originalReceiveDate = '2010-02-02 22:22:22';
1286
1287 $contributionID = $this->contributionCreate([
1288 'contact_id' => $this->individualCreate(),
1289 'total_amount' => 100,
1290 'contribution_status_id' => 'Pending',
1291 'receive_date' => $originalReceiveDate,
1292 'fee_amount' => 0,
1293 ]);
1294
1295 $this->callAPISuccess('Payment', 'create', [
1296 'total_amount' => 100,
1297 'order_id' => $contributionID,
1298 'trxn_date' => $trxnDate,
1299 'trxn_id' => $trxnID,
1300 'fee_amount' => .2,
1301 ]);
1302
1303 $contribution = $this->callAPISuccessGetSingle('Contribution', ['id' => $contributionID]);
1304 $this->assertEquals('Completed', $contribution['contribution_status']);
1305 $this->assertEquals(.2, $contribution['fee_amount']);
1306 $this->assertEquals(99.8, $contribution['net_amount']);
1307
1308 $this->assertEquals($trxnID, $contribution['trxn_id'],
1309 'Contribution trxn_id should have been set to that of the payment.');
1310
1311 $this->assertEquals($originalReceiveDate, $contribution['receive_date'],
1312 'Contribution receive date was changed, but should not have been.');
1313
1314 }
1315
1316 }