3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
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 +--------------------------------------------------------------------+
11 use Civi\Api4\Activity
;
12 use Civi\Api4\PledgePayment
;
15 * Class CRM_Contribute_BAO_ContributionTest
18 class CRM_Contribute_BAO_ContributionTest
extends CiviUnitTestCase
{
20 use CRMTraits_Financial_FinancialACLTrait
;
21 use CRMTraits_Financial_PriceSetTrait
;
24 * Clean up after tests.
26 public function tearDown(): void
{
27 $this->quickCleanUpFinancialEntities();
32 * Test create method (create and update modes).
34 * @throws \CRM_Core_Exception
36 public function testCreate() {
37 $contactId = $this->individualCreate();
40 'contact_id' => $contactId,
42 'financial_type_id' => 1,
43 'contribution_status_id' => 1,
44 'payment_instrument_id' => 1,
45 'source' => 'STUDENT',
46 'receive_date' => '20080522000000',
47 'receipt_date' => '20080522000000',
48 'non_deductible_amount' => 0.00,
49 'total_amount' => 200.00,
52 'trxn_id' => '22ereerwww444444',
53 'invoice_id' => '86ed39c9e9ee6ef6031621ce0eafe7eb81',
54 'thankyou_date' => '20080522',
58 $contribution = $this->callAPISuccess('Contribution', 'create', $params)['values'][0];
60 $this->assertEquals($params['trxn_id'], $contribution['trxn_id'], 'Check for transaction id creation.');
61 $this->assertEquals($contactId, $contribution['contact_id'], 'Check for contact id creation.');
63 //update contribution amount
64 $params['id'] = $contribution['id'];
65 $params['fee_amount'] = 10;
66 $params['net_amount'] = 190;
68 $contribution = $this->callAPISuccess('Contribution', 'create', $params)['values'][0];
70 $this->assertEquals($params['trxn_id'], $contribution['trxn_id'], 'Check for transcation id .');
71 $this->assertEquals($params['net_amount'], $contribution['net_amount'], 'Check for Amount updation.');
75 * Create() method with custom data.
77 public function testCreateWithCustomData() {
78 $contactId = $this->individualCreate();
81 $customGroup = $this->customGroupCreate(['extends' => 'Contribution']);
82 $customGroupID = $customGroup['id'];
83 $customGroup = $customGroup['values'][$customGroupID];
87 'data_type' => 'String',
88 'html_type' => 'Text',
90 'custom_group_id' => $customGroupID,
92 $customField = CRM_Core_BAO_CustomField
::create($fields);
95 'contact_id' => $contactId,
97 'financial_type_id' => 1,
98 'contribution_status_id' => 1,
99 'payment_instrument_id' => 1,
100 'source' => 'STUDENT',
101 'receive_date' => '20080522000000',
102 'receipt_date' => '20080522000000',
104 'non_deductible_amount' => 0.00,
105 'total_amount' => 200.00,
108 'trxn_id' => '22ereerwww322323',
109 'invoice_id' => '22ed39c9e9ee6ef6031621ce0eafe6da70',
110 'thankyou_date' => '20080522',
111 'skipCleanMoney' => TRUE,
114 $params['custom'] = [
115 $customField->id
=> [
117 'value' => 'Test custom value',
119 'custom_field_id' => $customField->id
,
120 'custom_group_id' => $customGroupID,
121 'table_name' => $customGroup['table_name'],
122 'column_name' => $customField->column_name
,
128 $contribution = CRM_Contribute_BAO_Contribution
::create($params);
130 // Check that the custom field value is saved
131 $customValueParams = [
132 'entityID' => $contribution->id
,
133 'custom_' . $customField->id
=> 1,
135 $values = CRM_Core_BAO_CustomValueTable
::getValues($customValueParams);
136 $this->assertEquals('Test custom value', $values['custom_' . $customField->id
], 'Check the custom field value');
138 $this->assertEquals($params['trxn_id'], $contribution->trxn_id
, 'Check for transcation id creation.');
139 $this->assertEquals($contactId, $contribution->contact_id
, 'Check for contact id for Conribution.');
143 * CRM-21026 Test ContributionCount after contribution created with disabled FT
145 public function testContributionCountDisabledFinancialType() {
146 $contactId = $this->individualCreate();
148 'name' => 'grassvariety1' . substr(sha1(rand()), 0, 7),
152 $finType = $this->callAPISuccess('financial_type', 'create', $financialType);
154 'contact_id' => $contactId,
156 'financial_type_id' => $finType['id'],
157 'contribution_status_id' => 1,
158 'payment_instrument_id' => 1,
159 'source' => 'STUDENT',
160 'receive_date' => '20080522000000',
161 'receipt_date' => '20080522000000',
163 'non_deductible_amount' => 0.00,
164 'total_amount' => 200.00,
167 'trxn_id' => '22ereerwww322323',
168 'invoice_id' => '22ed39c9e9ee6ef6031621ce0eafe6da70',
169 'thankyou_date' => '20080522',
171 $this->callAPISuccess('Contribution', 'create', $params);
172 $this->callAPISuccess('financial_type', 'create', ['is_active' => 0, 'id' => $finType['id']]);
173 $contributionCount = CRM_Contribute_BAO_Contribution
::contributionCount($contactId);
174 $this->assertEquals(1, $contributionCount);
178 * DeleteContribution() method
180 public function testDeleteContribution() {
181 $contactId = $this->individualCreate();
184 'contact_id' => $contactId,
186 'financial_type_id' => 1,
187 'contribution_status_id' => 1,
188 'payment_instrument_id' => 1,
189 'source' => 'STUDENT',
190 'receive_date' => '20080522000000',
191 'receipt_date' => '20080522000000',
193 'non_deductible_amount' => 0.00,
194 'total_amount' => 200.00,
197 'trxn_id' => '33ereerwww322323',
198 'invoice_id' => '33ed39c9e9ee6ef6031621ce0eafe6da70',
199 'thankyou_date' => '20080522',
200 'sequential' => TRUE,
203 $contribution = $this->callAPISuccess('Contribution', 'create', $params)['values'][0];
205 CRM_Contribute_BAO_Contribution
::deleteContribution($contribution['id']);
207 $this->assertDBNull('CRM_Contribute_DAO_Contribution', $contribution['trxn_id'],
208 'id', 'trxn_id', 'Database check for deleted Contribution.'
213 * Create honor-contact method.
215 public function testCreateAndGetHonorContact() {
216 $firstName = 'John_' . substr(sha1(rand()), 0, 7);
217 $lastName = 'Smith_' . substr(sha1(rand()), 0, 7);
218 $email = "{$firstName}.{$lastName}@example.com";
220 //Get profile id of name honoree_individual used to create profileContact
221 $honoreeProfileId = NULL;
222 $ufGroupDAO = new CRM_Core_DAO_UFGroup();
223 $ufGroupDAO->name
= 'honoree_individual';
224 if ($ufGroupDAO->find(TRUE)) {
225 $honoreeProfileId = $ufGroupDAO->id
;
230 'first_name' => $firstName,
231 'last_name' => $lastName,
234 $softParam = ['soft_credit_type_id' => 1];
237 $honoreeContactId = CRM_Contact_BAO_Contact
::createProfileContact($params, $null,
238 NULL, NULL, $honoreeProfileId
241 $this->assertDBCompareValue('CRM_Contact_DAO_Contact', $honoreeContactId, 'first_name', 'id', $firstName,
242 'Database check for created honor contact record.'
244 //create contribution on behalf of honary.
246 $contactId = $this->individualCreate(['first_name' => 'John', 'last_name' => 'Doe']);
249 'contact_id' => $contactId,
251 'financial_type_id' => 4,
252 'contribution_status_id' => 1,
253 'receive_date' => date('Ymd'),
254 'total_amount' => 66,
258 $contribution = $this->callAPISuccess('Contribution', 'create', $param)['values'][0];
259 $id = $contribution['id'];
260 $softParam['contact_id'] = $honoreeContactId;
261 $softParam['contribution_id'] = $id;
262 $softParam['currency'] = $contribution['currency'];
263 $softParam['amount'] = $contribution['total_amount'];
265 //Create Soft Contribution for honoree contact
266 CRM_Contribute_BAO_ContributionSoft
::add($softParam);
268 $this->assertDBCompareValue('CRM_Contribute_DAO_ContributionSoft', $id, 'contact_id',
269 'contribution_id', $honoreeContactId, 'Check DB for honor contact of the contribution'
271 //get honorary information
272 $getHonorContact = CRM_Contribute_BAO_Contribution
::getHonorContacts($honoreeContactId);
273 $this->assertEquals([
275 'honor_type' => 'In Honor of',
276 'honorId' => $contactId,
277 'display_name' => 'Mr. John Doe II',
278 'type' => 'Event Fee',
280 'amount' => '$ 66.00',
282 'receive_date' => date('Y-m-d 00:00:00'),
283 'contribution_status' => 'Completed',
285 ], $getHonorContact);
287 $this->assertDBCompareValue('CRM_Contact_DAO_Contact', $honoreeContactId, 'first_name', 'id', $firstName,
288 'Database check for created honor contact record.'
291 //get annual contribution information
292 $annual = CRM_Contribute_BAO_Contribution
::annual($contactId);
294 $currencySymbol = CRM_Core_DAO
::getFieldValue('CRM_Financial_DAO_Currency', CRM_Core_Config
::singleton()->defaultCurrency
, 'symbol', 'name');
295 $this->assertDBCompareValue('CRM_Contribute_DAO_Contribution', $id, 'total_amount',
296 'id', ltrim($annual[2], $currencySymbol), 'Check DB for total amount of the contribution'
301 * Test that financial type data is not added to the annual query if acls not enabled.
303 public function testAnnualQueryWithFinancialACLsEnabled() {
304 $this->enableFinancialACLs();
305 $this->createLoggedInUserWithFinancialACL();
306 $permittedFinancialType = CRM_Core_PseudoConstant
::getKey('CRM_Contribute_BAO_Contribution', 'financial_type_id', 'Donation');
307 $sql = CRM_Contribute_BAO_Contribution
::getAnnualQuery([1, 2, 3]);
308 $this->assertContains('SUM(total_amount) as amount,', $sql);
309 $this->assertContains('WHERE b.contact_id IN (1,2,3)', $sql);
310 $this->assertContains('b.financial_type_id IN (' . $permittedFinancialType . ')', $sql);
312 // Run it to make sure it's not bad sql.
313 CRM_Core_DAO
::executeQuery($sql);
314 $this->disableFinancialACLs();
318 * Test the annual query returns a correct result when multiple line items are present.
320 public function testAnnualWithMultipleLineItems() {
321 $contactID = $this->createLoggedInUserWithFinancialACL();
322 $this->createContributionWithTwoLineItemsAgainstPriceSet([
323 'contact_id' => $contactID,
326 $this->enableFinancialACLs();
327 $sql = CRM_Contribute_BAO_Contribution
::getAnnualQuery([$contactID]);
328 $result = CRM_Core_DAO
::executeQuery($sql);
330 $this->assertEquals(300, $result->amount
);
331 $this->assertEquals(1, $result->count
);
332 $this->disableFinancialACLs();
336 * Test that financial type data is not added to the annual query if acls not enabled.
338 public function testAnnualQueryWithFinancialACLsDisabled() {
339 $sql = CRM_Contribute_BAO_Contribution
::getAnnualQuery([1, 2, 3]);
340 $this->assertContains('SUM(total_amount) as amount,', $sql);
341 $this->assertContains('WHERE b.contact_id IN (1,2,3)', $sql);
342 $this->assertNotContains('b.financial_type_id', $sql);
343 //$this->assertNotContains('line_item', $sql);
344 // Run it to make sure it's not bad sql.
345 CRM_Core_DAO
::executeQuery($sql);
349 * Test that financial type data is not added to the annual query if acls not enabled.
351 public function testAnnualQueryWithFinancialHook() {
352 $this->hookClass
->setHook('civicrm_selectWhereClause', [$this, 'aclIdNoZero']);
353 $sql = CRM_Contribute_BAO_Contribution
::getAnnualQuery([1, 2, 3]);
354 $this->assertContains('SUM(total_amount) as amount,', $sql);
355 $this->assertContains('WHERE b.contact_id IN (1,2,3)', $sql);
356 $this->assertContains('b.id NOT IN (0)', $sql);
357 $this->assertNotContains('b.financial_type_id', $sql);
358 CRM_Core_DAO
::executeQuery($sql);
362 * Add ACL denying values LIKE '0'.
364 * @param string $entity
365 * @param string $clauses
367 public function aclIdNoZero($entity, &$clauses) {
368 if ($entity != 'Contribution') {
371 $clauses['id'] = "NOT IN (0)";
375 * Display sort name during.
376 * Update multiple contributions
379 public function testsortName() {
381 'first_name' => 'Shane',
382 'last_name' => 'Whatson',
383 'contact_type' => 'Individual',
386 $contact = CRM_Contact_BAO_Contact
::add($params);
388 //Now check $contact is object of contact DAO..
389 $this->assertInstanceOf('CRM_Contact_DAO_Contact', $contact, 'Check for created object');
391 $contactId = $contact->id
;
393 'contact_id' => $contactId,
395 'financial_type_id' => 1,
396 'contribution_status_id' => 1,
397 'payment_instrument_id' => 1,
398 'source' => 'STUDENT',
399 'receive_date' => '20080522000000',
400 'receipt_date' => '20080522000000',
402 'non_deductible_amount' => 0.00,
403 'total_amount' => 300.00,
406 'trxn_id' => '22ereerwww323',
407 'invoice_id' => '22ed39c9e9ee621ce0eafe6da70',
408 'thankyou_date' => '20080522',
409 'sequential' => TRUE,
412 $contribution = $this->callAPISuccess('Contribution', 'create', $param)['values'][0];
414 $this->assertEquals($param['trxn_id'], $contribution['trxn_id'], 'Check for transcation id creation.');
415 $this->assertEquals($contactId, $contribution['contact_id'], 'Check for contact id creation.');
417 //display sort name during Update multiple contributions
418 $sortName = CRM_Contribute_BAO_Contribution
::sortName($contribution['id']);
420 $this->assertEquals('Whatson, Shane', $sortName, 'Check for sort name.');
424 * Add premium during online Contribution.
428 public function testAddPremium() {
429 $contactId = $this->individualCreate();
432 'name' => 'TEST Premium',
434 'imageOption' => 'noImage',
435 'MAX_FILE_SIZE' => 2097152,
438 'min_contribution' => 100,
441 $premium = CRM_Contribute_BAO_Product
::create($params);
443 $this->assertEquals('TEST Premium', $premium->name
, 'Check for premium name.');
445 $contributionParams = [
446 'contact_id' => $contactId,
448 'financial_type_id' => 1,
449 'contribution_status_id' => 1,
450 'payment_instrument_id' => 1,
451 'source' => 'STUDENT',
452 'receive_date' => '20080522000000',
453 'receipt_date' => '20080522000000',
455 'non_deductible_amount' => 0.00,
456 'total_amount' => 300.00,
459 'trxn_id' => '33erdfrwvw434',
460 'invoice_id' => '98ed34f7u9hh672ce0eafe8fb92',
461 'thankyou_date' => '20080522',
462 'sequential' => TRUE,
464 $contribution = $this->callAPISuccess('Contribution', 'create', $contributionParams)['values'][0];
466 $this->assertEquals($contributionParams['trxn_id'], $contribution['trxn_id'], 'Check for transcation id creation.');
467 $this->assertEquals($contactId, $contribution['contact_id'], 'Check for contact id creation.');
469 //parameter for adding premium to contribution
471 'product_id' => $premium->id
,
472 'contribution_id' => $contribution['id'],
473 'product_option' => NULL,
476 $contributionProduct = CRM_Contribute_BAO_Contribution
::addPremium($data);
477 $this->assertEquals($contributionProduct->product_id
, $premium->id
, 'Check for Product id .');
480 CRM_Contribute_BAO_Product
::del($premium->id
);
481 $this->assertDBNull('CRM_Contribute_DAO_Product', $premium->name
,
482 'id', 'name', 'Database check for deleted Product.'
487 * Check duplicate contribution id.
488 * during the contribution import
489 * checkDuplicateIds();
491 public function testcheckDuplicateIds() {
492 $contactId = $this->individualCreate();
495 'contact_id' => $contactId,
497 'financial_type_id' => 1,
498 'contribution_status_id' => 1,
499 'payment_instrument_id' => 1,
500 'source' => 'STUDENT',
501 'receive_date' => '20080522000000',
502 'receipt_date' => '20080522000000',
504 'non_deductible_amount' => 0.00,
505 'total_amount' => 300.00,
508 'trxn_id' => '76ereeswww835',
509 'invoice_id' => '93ed39a9e9hd621bs0eafe3da82',
510 'thankyou_date' => '20080522',
511 'sequential' => TRUE,
514 $contribution = $this->callAPISuccess('Contribution', 'create', $param)['values'][0];
516 $this->assertEquals($param['trxn_id'], $contribution['trxn_id'], 'Check for transcation id creation.');
517 $this->assertEquals($contactId, $contribution['contact_id'], 'Check for contact id creation.');
519 'id' => $contribution['id'],
520 'trxn_id' => $contribution['trxn_id'],
521 'invoice_id' => $contribution['invoice_id'],
523 $contributionID = CRM_Contribute_BAO_Contribution
::checkDuplicateIds($data);
524 $this->assertEquals($contributionID, $contribution['id'], 'Check for duplicate transcation id .');
528 * Create() method (create and update modes).
530 public function testIsPaymentFlag() {
531 $contactId = $this->individualCreate();
534 'contact_id' => $contactId,
536 'financial_type_id' => 1,
537 'contribution_status_id' => 1,
538 'payment_instrument_id' => 1,
539 'source' => 'STUDENT',
540 'receive_date' => '20080522000000',
541 'receipt_date' => '20080522000000',
542 'non_deductible_amount' => 0.00,
543 'total_amount' => 200.00,
546 'trxn_id' => '22ereerwww4444xx',
547 'invoice_id' => '86ed39c9e9ee6ef6541621ce0eafe7eb81',
548 'thankyou_date' => '20080522',
549 'sequential' => TRUE,
551 $contribution = $this->callAPISuccess('Contribution', 'create', $params)['values'][0];
553 $this->assertEquals($params['trxn_id'], $contribution['trxn_id'], 'Check for transcation id creation.');
554 $this->assertEquals($contactId, $contribution['contact_id'], 'Check for contact id creation.');
557 'trxn_id' => $params['trxn_id'],
561 $financialTrxn = CRM_Core_BAO_FinancialTrxn
::retrieve($trxnArray, $defaults);
562 $this->assertEquals(1, $financialTrxn->N
, 'Mismatch count for is payment flag.');
563 //update contribution amount
564 $params['id'] = $contribution['id'];
565 $params['total_amount'] = 150;
566 $contribution = $this->callAPISuccess('Contribution', 'create', $params)['values'][0];
568 $this->assertEquals($params['trxn_id'], $contribution['trxn_id'], 'Check for transcation id .');
569 $this->assertEquals($params['total_amount'], $contribution['total_amount'], 'Check for Amount updation.');
571 'trxn_id' => $params['trxn_id'],
575 $financialTrxn = CRM_Core_BAO_FinancialTrxn
::retrieve($trxnArray, $defaults);
576 $this->assertEquals(2, $financialTrxn->N
, 'Mismatch count for is payment flag.');
577 $trxnArray['is_payment'] = 0;
578 $financialTrxn = CRM_Core_BAO_FinancialTrxn
::retrieve($trxnArray, $defaults);
579 $this->assertEquals(1, $financialTrxn->N
, 'Mismatch count for is payment flag.');
583 * Create() method (create and update modes).
585 public function testIsPaymentFlagForPending() {
586 $contactId = $this->individualCreate();
589 'contact_id' => $contactId,
591 'financial_type_id' => 1,
592 'contribution_status_id' => 2,
593 'payment_instrument_id' => 1,
594 'source' => 'STUDENT',
596 'receive_date' => '20080522000000',
597 'receipt_date' => '20080522000000',
598 'non_deductible_amount' => 0.00,
599 'total_amount' => 200.00,
602 'trxn_id' => '22ereerwww4444yy',
603 'invoice_id' => '86ed39c9e9yy6ef6541621ce0eafe7eb81',
604 'thankyou_date' => '20080522',
605 'sequential' => TRUE,
608 $contribution = $this->callAPISuccess('Contribution', 'create', $params)['values'][0];
610 $this->assertEquals($params['trxn_id'], $contribution['trxn_id'], 'Check for transaction id creation.');
611 $this->assertEquals($contactId, $contribution['contact_id'], 'Check for contact id creation.');
614 'trxn_id' => $params['trxn_id'],
618 $financialTrxn = CRM_Core_BAO_FinancialTrxn
::retrieve($trxnArray, $defaults);
619 $this->assertEquals(2, $financialTrxn->N
, 'Mismatch count for is payment flag.');
620 $trxnArray['is_payment'] = 1;
621 $financialTrxn = CRM_Core_BAO_FinancialTrxn
::retrieve($trxnArray, $defaults);
622 $this->assertEquals(NULL, $financialTrxn, 'Mismatch count for is payment flag.');
623 //update contribution amount
624 $params['id'] = $contribution['id'];
625 $params['contribution_status_id'] = 1;
627 $contribution = $this->callAPISuccess('Contribution', 'create', $params)['values'][0];
629 $this->assertEquals($params['trxn_id'], $contribution['trxn_id'], 'Check for transcation id .');
630 $this->assertEquals($params['contribution_status_id'], $contribution['contribution_status_id'], 'Check for status updation.');
632 'trxn_id' => $params['trxn_id'],
636 $financialTrxn = CRM_Core_BAO_FinancialTrxn
::retrieve($trxnArray, $defaults);
637 $this->assertEquals(1, $financialTrxn->N
, 'Mismatch count for is payment flag.');
638 $trxnArray['is_payment'] = 0;
639 $financialTrxn = CRM_Core_BAO_FinancialTrxn
::retrieve($trxnArray, $defaults);
640 $this->assertEquals(2, $financialTrxn->N
, 'Mismatch count for is payment flag.');
644 * checks db values for financial item
646 public function checkItemValues($contribution) {
647 $toFinancialAccount = CRM_Contribute_PseudoConstant
::getRelationalFinancialAccount(4, 'Accounts Receivable Account is');
648 $query = "SELECT eft1.entity_id, ft.total_amount, eft1.amount FROM civicrm_financial_trxn ft INNER JOIN civicrm_entity_financial_trxn eft ON (eft.financial_trxn_id = ft.id AND eft.entity_table = 'civicrm_contribution')
649 INNER JOIN civicrm_entity_financial_trxn eft1 ON (eft1.financial_trxn_id = eft.financial_trxn_id AND eft1.entity_table = 'civicrm_financial_item')
650 WHERE eft.entity_id = %1 AND ft.to_financial_account_id <> %2";
652 $queryParams[1] = [$contribution->id
, 'Integer'];
653 $queryParams[2] = [$toFinancialAccount, 'Integer'];
655 $dao = CRM_Core_DAO
::executeQuery($query, $queryParams);
656 $amounts = [100.00, 50.00];
657 while ($dao->fetch()) {
658 $this->assertEquals(150.00, $dao->total_amount
, 'Mismatch of total amount paid.');
659 $this->assertEquals($dao->amount
, array_pop($amounts), 'Mismatch of amount proportionally assigned to financial item');
664 * assignProportionalLineItems() method (add and edit modes of participant)
666 * @throws \CRM_Core_Exception
667 * @throws \CiviCRM_API3_Exception
669 public function testAssignProportionalLineItems() {
670 $contribution = $this->addParticipantWithContribution();
671 // Delete existing financial_trxns. This is because we are testing a code flow we
672 // want to deprecate & remove & the test relies on bad data asa starting point.
673 // End goal is the Order.create->Payment.create flow.
674 CRM_Core_DAO
::executeQuery('DELETE FROM civicrm_entity_financial_trxn WHERE entity_table = "civicrm_financial_item"');
676 'contribution_id' => $contribution->id
,
677 'total_amount' => 150.00,
679 $trxn = new CRM_Financial_DAO_FinancialTrxn();
680 $trxn->orderBy('id DESC');
682 CRM_Contribute_BAO_Contribution
::assignProportionalLineItems($params, $trxn->id
, $contribution->total_amount
);
683 $this->checkItemValues($contribution);
687 * Add participant with contribution
689 * @return CRM_Contribute_BAO_Contribution
691 * @throws \CRM_Core_Exception
692 * @throws \CiviCRM_API3_Exception
694 public function addParticipantWithContribution() {
695 // creating price set, price field
696 $this->_contactId
= $this->individualCreate();
697 $event = $this->eventCreatePaid([]);
698 $this->_eventId
= $event['id'];
699 $priceSetID = $this->ids
['PriceSet']['event'];
701 'label' => 'Price Field',
702 'name' => CRM_Utils_String
::titleToVar('Price Field'),
703 'html_type' => 'CheckBox',
704 'option_label' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
705 'option_value' => ['1' => 100, '2' => 200],
706 'option_name' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
707 'option_weight' => ['1' => 1, '2' => 2],
708 'option_amount' => ['1' => 100, '2' => 200],
709 'is_display_amounts' => 1,
711 'options_per_line' => 1,
712 'is_active' => ['1' => 1, '2' => 1],
713 'price_set_id' => $priceSetID,
715 'financial_type_id' => CRM_Core_DAO
::getFieldValue('CRM_Financial_DAO_FinancialType', 'Event Fee', 'id', 'name'),
717 $priceField = CRM_Price_BAO_PriceField
::create($paramsField);
719 'id' => $this->_eventId
,
720 'financial_type_id' => 4,
723 CRM_Event_BAO_Event
::create($eventParams);
724 CRM_Price_BAO_PriceSet
::addTo('civicrm_event', $this->_eventId
, $priceSetID);
726 $priceFields = $this->callAPISuccess('PriceFieldValue', 'get', ['price_field_id' => $priceField->id
]);
727 $participantParams = [
728 'financial_type_id' => 4,
729 'event_id' => $this->_eventId
,
732 'fee_currency' => 'USD',
733 'contact_id' => $this->_contactId
,
735 $participant = CRM_Event_BAO_Participant
::add($participantParams);
736 $contributionParams = [
737 'total_amount' => 300,
739 'contact_id' => $this->_contactId
,
740 'financial_type_id' => 4,
741 'contribution_status_id' => 'Pending',
742 'contribution_mode' => 'participant',
743 'participant_id' => $participant->id
,
744 'sequential' => TRUE,
745 'api.Payment.create' => ['total_amount' => 150],
748 foreach ($priceFields['values'] as $key => $priceField) {
749 $lineItems[1][$key] = [
750 'price_field_id' => $priceField['price_field_id'],
751 'price_field_value_id' => $priceField['id'],
752 'label' => $priceField['label'],
753 'field_title' => $priceField['label'],
755 'unit_price' => $priceField['amount'],
756 'line_total' => $priceField['amount'],
757 'financial_type_id' => $priceField['financial_type_id'],
760 $contributionParams['line_item'] = $lineItems;
761 $contribution = $this->callAPISuccess('Contribution', 'create', $contributionParams)['values'][0];
763 $paymentParticipant = [
764 'participant_id' => $participant->id
,
765 'contribution_id' => $contribution['id'],
767 CRM_Event_BAO_ParticipantPayment
::create($paymentParticipant);
769 $contributionObject = new CRM_Contribute_BAO_Contribution();
770 $contributionObject->id
= $contribution['id'];
771 $contributionObject->find(TRUE);
773 return $contributionObject;
777 * checkLineItems() check if total amount matches the sum of line total
779 public function testcheckLineItems() {
782 'receive_date' => '2010-01-20',
783 'total_amount' => 100,
784 'financial_type_id' => 3,
789 'entity_table' => 'civicrm_contribution',
790 'price_field_id' => 8,
791 'price_field_value_id' => 16,
798 'entity_table' => 'civicrm_contribution',
799 'price_field_id' => 8,
800 'price_field_value_id' => 17,
805 'financial_type_id' => 1,
814 CRM_Contribute_BAO_Contribution
::checkLineItems($params);
815 $this->fail("Missed expected exception");
817 catch (CRM_Contribute_Exception_CheckLineItemsException
$e) {
819 CRM_Contribute_Exception_CheckLineItemsException
::LINE_ITEM_DIFFERRING_TOTAL_EXCEPTON_MSG
,
824 $this->assertEquals(3, $params['line_items'][0]['line_item'][0]['financial_type_id']);
825 $params['total_amount'] = 300;
827 CRM_Contribute_BAO_Contribution
::checkLineItems($params);
831 * Tests CRM_Contribute_BAO_Contribution::checkLineItems() method works with
832 * floating point values.
834 public function testCheckLineItemsWithFloatingPointValues() {
837 'receive_date' => date('Y-m-d'),
838 'total_amount' => 16.67,
839 'financial_type_id' => 3,
844 'entity_table' => 'civicrm_contribution',
845 'price_field_id' => 8,
846 'price_field_value_id' => 16,
849 'unit_price' => 14.85,
850 'line_total' => 14.85,
853 'entity_table' => 'civicrm_contribution',
854 'price_field_id' => 8,
855 'price_field_value_id' => 17,
858 'unit_price' => 1.66,
859 'line_total' => 1.66,
860 'financial_type_id' => 1,
863 'entity_table' => 'civicrm_contribution',
864 'price_field_id' => 8,
865 'price_field_value_id' => 17,
868 'unit_price' => 0.16,
869 'line_total' => 0.16,
870 'financial_type_id' => 1,
878 $foundException = FALSE;
881 CRM_Contribute_BAO_Contribution
::checkLineItems($params);
883 catch (CRM_Contribute_Exception_CheckLineItemsException
$e) {
884 $foundException = TRUE;
887 $this->assertFalse($foundException);
891 * Test activity amount updates activity subject.
893 public function testActivityCreate() {
894 $contactId = $this->individualCreate();
897 'contact_id' => $contactId,
899 'financial_type_id' => 1,
900 'contribution_status_id' => 1,
901 'payment_instrument_id' => 1,
902 'source' => 'STUDENT',
903 'receive_date' => '20080522000000',
904 'receipt_date' => '20080522000000',
905 'non_deductible_amount' => 0.00,
906 'total_amount' => 100.00,
907 'invoice_id' => '86ed39c9e9ee6ef6031621ce0eafe7eb81',
908 'thankyou_date' => '20160519',
912 $contribution = $this->callAPISuccess('Contribution', 'create', $params)['values'][0];
914 $this->assertEquals($params['total_amount'], $contribution['total_amount'], 'Check for total amount in contribution.');
915 $this->assertEquals($contactId, $contribution['contact_id'], 'Check for contact id creation.');
917 ['source_record_id', '=', $contribution['id']],
918 ['activity_type_id:name', '=', 'Contribution'],
920 $activity = Activity
::get()->setWhere($activityWhere)->setSelect(['source_record_id', 'subject'])->execute()->first();
922 $this->assertEquals($contribution['id'], $activity['source_record_id'], 'Check for activity associated with contribution.');
923 $this->assertEquals('$ 100.00 - STUDENT', $activity['subject'], 'Check for total amount in activity.');
925 $params['id'] = $contribution['id'];
926 $params['total_amount'] = 200;
927 $params['campaign_id'] = $this->campaignCreate();
929 $contribution = $this->callAPISuccess('Contribution', 'create', $params)['values'][0];
931 $this->assertEquals($params['total_amount'], $contribution['total_amount'], 'Check for total amount in contribution.');
932 $this->assertEquals($contactId, $contribution['contact_id'], 'Check for contact id creation.');
934 // Retrieve activity again.
935 $activity = Activity
::get()->setWhere($activityWhere)->setSelect(['source_record_id', 'subject', 'campaign_id'])->execute()->first();
937 $this->assertEquals($contribution['id'], $activity['source_record_id'], 'Check for activity associated with contribution.');
938 $this->assertEquals('$ 200.00 - STUDENT', $activity['subject'], 'Check for total amount in activity.');
939 $this->assertEquals($params['campaign_id'], $activity['campaign_id']);
943 * Test allowUpdateRevenueRecognitionDate.
945 * @throws \CRM_Core_Exception
947 public function testAllowUpdateRevenueRecognitionDate() {
948 $contactId = $this->individualCreate();
950 'contact_id' => $contactId,
951 'receive_date' => '2010-01-20',
952 'total_amount' => 100,
953 'financial_type_id' => 4,
954 'contribution_status_id' => 'Pending',
956 $order = $this->callAPISuccess('Order', 'create', $params);
957 $allowUpdate = CRM_Contribute_BAO_Contribution
::allowUpdateRevenueRecognitionDate($order['id']);
958 $this->assertTrue($allowUpdate);
960 $event = $this->eventCreate();
962 'contact_id' => $contactId,
963 'receive_date' => '2010-01-20',
964 'total_amount' => 300,
965 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
966 'contribution_status_id' => 'Pending',
968 $priceFields = $this->createPriceSet('event', $event['id']);
969 foreach ($priceFields['values'] as $key => $priceField) {
971 'price_field_id' => $priceField['price_field_id'],
972 'price_field_value_id' => $priceField['id'],
973 'label' => $priceField['label'],
974 'field_title' => $priceField['label'],
976 'unit_price' => $priceField['amount'],
977 'line_total' => $priceField['amount'],
978 'financial_type_id' => $priceField['financial_type_id'],
979 'entity_table' => 'civicrm_participant',
982 $params['line_items'][] = [
983 'line_item' => $lineItems,
985 'contact_id' => $contactId,
986 'event_id' => $event['id'],
989 'register_date' => '2007-07-21 00:00:00',
990 'source' => 'Online Event Registration: API Testing',
993 $order = $this->callAPISuccess('Order', 'create', $params);
994 $allowUpdate = CRM_Contribute_BAO_Contribution
::allowUpdateRevenueRecognitionDate($order['id']);
995 $this->assertFalse($allowUpdate);
998 'contact_id' => $contactId,
999 'receive_date' => '2010-01-20',
1000 'total_amount' => 200,
1001 'financial_type_id' => $this->getFinancialTypeId('Member Dues'),
1002 'contribution_status_id' => 'Pending',
1004 $membershipType = $this->membershipTypeCreate();
1005 $priceFields = $this->createPriceSet();
1007 foreach ($priceFields['values'] as $key => $priceField) {
1008 $lineItems[$key] = [
1009 'price_field_id' => $priceField['price_field_id'],
1010 'price_field_value_id' => $priceField['id'],
1011 'label' => $priceField['label'],
1012 'field_title' => $priceField['label'],
1014 'unit_price' => $priceField['amount'],
1015 'line_total' => $priceField['amount'],
1016 'financial_type_id' => $priceField['financial_type_id'],
1017 'entity_table' => 'civicrm_membership',
1018 'membership_type_id' => $membershipType,
1021 $params['line_items'][] = [
1022 'line_item' => [array_pop($lineItems)],
1024 'contact_id' => $contactId,
1025 'membership_type_id' => $membershipType,
1026 'join_date' => '2006-01-21',
1027 'start_date' => '2006-01-21',
1028 'end_date' => '2006-12-21',
1029 'source' => 'Payment',
1034 $order = $this->callAPISuccess('Order', 'create', $params);
1035 $allowUpdate = CRM_Contribute_BAO_Contribution
::allowUpdateRevenueRecognitionDate($order['id']);
1036 $this->assertFalse($allowUpdate);
1040 * Test calculateFinancialItemAmount().
1042 public function testcalculateFinancialItemAmount() {
1047 'line_total' => 100,
1048 'previous_line_total' => 300,
1051 'context' => 'changedAmount',
1052 'expectedItemAmount' => -200,
1057 'line_total' => 100,
1058 'previous_line_total' => 100,
1061 // Most contexts are ignored. Removing refs to change payment instrument so placeholder.
1062 'context' => 'not null',
1063 'expectedItemAmount' => -100,
1067 'is_quick_config' => TRUE,
1068 'total_amount' => 110,
1072 'item_amount' => 100,
1074 'context' => 'changedAmount',
1075 'expectedItemAmount' => 100,
1079 'is_quick_config' => TRUE,
1080 'total_amount' => 110,
1084 'item_amount' => NULL,
1086 'context' => 'changedAmount',
1087 'expectedItemAmount' => 110,
1091 'is_quick_config' => TRUE,
1092 'total_amount' => 110,
1096 'item_amount' => NULL,
1099 'expectedItemAmount' => 100,
1102 foreach ($testParams as $params) {
1103 $itemAmount = CRM_Contribute_BAO_Contribution
::calculateFinancialItemAmount($params['params'], $params['amountParams'], $params['context']);
1104 $this->assertEquals($itemAmount, $params['expectedItemAmount'], 'Invalid Financial Item amount.');
1109 * Test recording of amount with comma separator.
1111 * @throws \CRM_Core_Exception
1113 public function testCommaSeparatorAmount() {
1114 $contactId = $this->individualCreate();
1117 'contact_id' => $contactId,
1118 'currency' => 'USD',
1119 'financial_type_id' => 1,
1120 'contribution_status_id' => 'Pending',
1121 'payment_instrument_id' => 1,
1122 'receive_date' => '20080522000000',
1123 'receipt_date' => '20080522000000',
1124 'total_amount' => '20,000.00',
1125 'api.Payment.create' => ['total_amount' => '8,000.00'],
1126 'skipCleanMoney' => FALSE,
1129 $contribution = $this->callAPISuccess('Order', 'create', $params);
1130 $lastFinancialTrxnId = CRM_Core_BAO_FinancialTrxn
::getFinancialTrxnId($contribution['id'], 'DESC');
1131 $financialTrxn = $this->callAPISuccessGetSingle(
1134 'id' => $lastFinancialTrxnId['financialTrxnId'],
1135 'return' => ['total_amount'],
1138 $this->assertEquals($financialTrxn['total_amount'], 8000, 'Invalid amount.');
1142 * Test for function getSalesTaxFinancialAccounts().
1144 public function testgetSalesTaxFinancialAccounts() {
1145 $this->enableTaxAndInvoicing();
1146 $financialType = $this->createFinancialType();
1147 $financialAccount = $this->addTaxAccountToFinancialType($financialType['id']);
1148 $expectedResult = [$financialAccount->financial_account_id
=> $financialAccount->financial_account_id
];
1149 $financialType = $this->createFinancialType();
1150 $financialAccount = $this->addTaxAccountToFinancialType($financialType['id']);
1151 $expectedResult[$financialAccount->financial_account_id
] = $financialAccount->financial_account_id
;
1152 $salesTaxFinancialAccount = CRM_Contribute_BAO_Contribution
::getSalesTaxFinancialAccounts();
1153 $this->assertTrue(($salesTaxFinancialAccount == $expectedResult), 'Function returned wrong values.');
1157 * Test for function createProportionalEntry().
1159 * @param string $thousandSeparator
1160 * punctuation used to refer to thousands.
1162 * @dataProvider getThousandSeparators
1164 public function testCreateProportionalEntry($thousandSeparator) {
1165 $this->setCurrencySeparators($thousandSeparator);
1166 [$contribution, $financialAccount] = $this->createContributionWithTax();
1168 'total_amount' => 55,
1169 'to_financial_account_id' => $financialAccount->financial_account_id
,
1170 'payment_instrument_id' => 1,
1171 'trxn_date' => date('Ymd'),
1173 'entity_id' => $contribution['id'],
1175 $financialTrxn = $this->callAPISuccess('FinancialTrxn', 'create', $params);
1177 'contribution_total_amount' => $contribution['total_amount'],
1178 'trxn_total_amount' => 55,
1179 'line_item_amount' => 100,
1181 $previousLineItem = CRM_Financial_BAO_FinancialItem
::getPreviousFinancialItem($contribution['id']);
1183 'entity_table' => 'civicrm_financial_item',
1184 'entity_id' => $previousLineItem['id'],
1185 'financial_trxn_id' => (string) $financialTrxn['id'],
1187 CRM_Contribute_BAO_Contribution
::createProportionalEntry($entityParams, $eftParams);
1188 $trxnTestArray = array_merge($eftParams, [
1189 'amount' => '50.00',
1191 $this->callAPISuccessGetSingle('EntityFinancialTrxn', $eftParams, $trxnTestArray);
1195 * Test for function createProportionalEntry with zero amount().
1197 * @param string $thousandSeparator
1198 * punctuation used to refer to thousands.
1200 * @throws \CRM_Core_Exception
1201 * @throws \CiviCRM_API3_Exception
1202 * @dataProvider getThousandSeparators
1204 public function testCreateProportionalEntryZeroAmount(string $thousandSeparator): void
{
1205 $this->setCurrencySeparators($thousandSeparator);
1206 [$contribution, $financialAccount] = $this->createContributionWithTax(['total_amount' => 0]);
1208 'total_amount' => 0,
1209 'to_financial_account_id' => $financialAccount->financial_account_id
,
1210 'payment_instrument_id' => 1,
1211 'trxn_date' => date('Ymd'),
1213 'entity_id' => $contribution['id'],
1215 $financialTrxn = $this->callAPISuccess('FinancialTrxn', 'create', $params);
1217 'contribution_total_amount' => $contribution['total_amount'],
1218 'trxn_total_amount' => 0,
1219 'line_item_amount' => 0,
1221 $previousLineItem = CRM_Financial_BAO_FinancialItem
::getPreviousFinancialItem($contribution['id']);
1223 'entity_table' => 'civicrm_financial_item',
1224 'entity_id' => $previousLineItem['id'],
1225 'financial_trxn_id' => (string) $financialTrxn['id'],
1227 CRM_Contribute_BAO_Contribution
::createProportionalEntry($entityParams, $eftParams);
1228 $trxnTestArray = array_merge($eftParams, [
1231 $this->callAPISuccessGetSingle('EntityFinancialTrxn', $eftParams, $trxnTestArray);
1235 * Test for function getLastFinancialItemIds().
1237 public function testgetLastFinancialItemIds() {
1238 [$contribution, $financialAccount] = $this->createContributionWithTax();
1239 [$ftIds, $taxItems] = CRM_Contribute_BAO_Contribution
::getLastFinancialItemIds($contribution['id']);
1240 $this->assertEquals(count($ftIds), 1, 'Invalid count.');
1241 $this->assertEquals(count($taxItems), 1, 'Invalid count.');
1242 foreach ($taxItems as $value) {
1243 $this->assertEquals($value['amount'], 10, 'Invalid tax amount.');
1248 * Test to ensure proportional entries are creating when adding a payment..
1250 * In this test we create a pending contribution for $110 consisting of $100 contribution and $10 tax.
1252 * We pay $50, resulting in it being allocated as $45.45 paymnt & $4.55 tax. This is in equivalent proportions
1253 * to the original payment - ie. .0909 of the $110 is 10 & that * 50 is $4.54 (note the rounding seems wrong as it should be
1254 * saved un-rounded).
1256 public function testCreateProportionalFinancialEntriesViaPaymentCreate() {
1257 [$contribution, $financialAccount] = $this->createContributionWithTax([], FALSE);
1259 'total_amount' => 50,
1260 'to_financial_account_id' => $financialAccount->financial_account_id
,
1261 'payment_instrument_id' => 1,
1262 'trxn_date' => date('Ymd'),
1264 'entity_id' => $contribution['id'],
1265 'contribution_id' => $contribution['id'],
1267 $financialTrxn = $this->callAPISuccess('Payment', 'create', $params);
1269 'entity_table' => 'civicrm_financial_item',
1270 'financial_trxn_id' => $financialTrxn['id'],
1272 $entityFinancialTrxn = $this->callAPISuccess('EntityFinancialTrxn', 'Get', $eftParams);
1273 $this->assertEquals($entityFinancialTrxn['count'], 2, 'Invalid count.');
1274 $testAmount = [4.55, 45.45];
1275 foreach ($entityFinancialTrxn['values'] as $value) {
1276 $this->assertEquals(array_pop($testAmount), $value['amount'], 'Invalid amount stored in civicrm_entity_financial_trxn.');
1281 * Test to check if amount is proportionally asigned for PI change.
1283 public function testProportionallyAssignedForPIChange() {
1284 [$contribution, $financialAccount] = $this->createContributionWithTax();
1286 'id' => $contribution['id'],
1287 'payment_instrument_id' => 3,
1289 $this->callAPISuccess('Contribution', 'create', $params);
1290 $lastFinancialTrxnId = CRM_Core_BAO_FinancialTrxn
::getFinancialTrxnId($contribution['id'], 'DESC');
1292 'entity_table' => 'civicrm_financial_item',
1293 'financial_trxn_id' => $lastFinancialTrxnId['financialTrxnId'],
1295 $entityFinancialTrxn = $this->callAPISuccess('EntityFinancialTrxn', 'Get', $eftParams);
1296 $this->assertEquals($entityFinancialTrxn['count'], 2, 'Invalid count.');
1297 $testAmount = [10, 100];
1298 foreach ($entityFinancialTrxn['values'] as $value) {
1299 $this->assertEquals($value['amount'], array_pop($testAmount), 'Invalid amount stored in civicrm_entity_financial_trxn.');
1304 * Function to create contribution with tax.
1306 public function createContributionWithTax($params = [], $isCompleted = TRUE) {
1307 if (!isset($params['total_amount'])) {
1308 $params['total_amount'] = 100;
1310 $contactId = $this->individualCreate();
1311 $this->enableTaxAndInvoicing();
1312 $financialType = $this->createFinancialType();
1313 $financialAccount = $this->addTaxAccountToFinancialType($financialType['id']);
1314 $form = new CRM_Contribute_Form_Contribution();
1317 'total_amount' => $params['total_amount'],
1318 'financial_type_id' => $financialType['id'],
1319 'contact_id' => $contactId,
1320 'contribution_status_id' => $isCompleted ?
1 : 2,
1321 'price_set_id' => 0,
1322 ], CRM_Core_Action
::ADD
);
1323 $contribution = $this->callAPISuccessGetSingle('Contribution',
1325 'contact_id' => $contactId,
1326 'return' => ['tax_amount', 'total_amount'],
1329 return [$contribution, $financialAccount];
1333 * Test processOnBehalfOrganization() function.
1335 public function testProcessOnBehalfOrganization() {
1337 'phone' => '11111111',
1338 'email' => 'testorg@gmail.com',
1339 'street_address' => 'test Street',
1340 'city' => 'test City',
1341 'state_province' => 'AA',
1342 'postal_code' => '222222',
1343 'country' => 'United States',
1345 $originalContactId = $contactID = $this->individualCreate();
1346 $orgId = $this->organizationCreate(['organization_name' => 'testorg1']);
1347 $orgCount = $this->callAPISuccessGetCount('Contact', [
1348 'contact_type' => "Organization",
1349 'organization_name' => "testorg1",
1351 $this->assertEquals($orgCount, 1);
1353 $values = $params = [];
1354 $originalBehalfOrganization = $behalfOrganization = [
1355 'organization_name' => 'testorg1',
1358 'phone' => $orgInfo['phone'],
1364 'email' => $orgInfo['email'],
1370 'street_address' => $orgInfo['street_address'],
1371 'city' => $orgInfo['city'],
1372 'location_type_id' => 3,
1373 'postal_code' => $orgInfo['postal_code'],
1375 'state_province' => 'AA',
1381 'organization_name' => 1,
1384 'street_address-3' => 1,
1386 'postal_code-3' => 1,
1388 'state_province-3' => 1,
1391 CRM_Contribute_Form_Contribution_Confirm
::processOnBehalfOrganization($behalfOrganization, $contactID, $empty, $empty, $empty);
1393 //Check whether new organisation is created.
1394 $result = $this->callAPISuccess('Contact', 'get', [
1395 'contact_type' => "Organization",
1396 'organization_name' => "testorg1",
1398 $this->assertEquals($result['count'], 1);
1400 //Assert all org values are updated.
1401 foreach ($orgInfo as $key => $val) {
1402 $this->assertEquals($result['values'][$orgId][$key], $val);
1405 //Check if alert is assigned to params if more than 1 dupe exists.
1406 $orgId = $this->organizationCreate(['organization_name' => 'testorg1', 'email' => 'testorg@gmail.com']);
1407 CRM_Contribute_Form_Contribution_Confirm
::processOnBehalfOrganization($originalBehalfOrganization, $originalContactId, $values, $params, $fields);
1408 $this->assertEquals($params['onbehalf_dupe_alert'], 1);
1412 * Test for replaceContributionTokens.
1413 * This function tests whether the contribution tokens are replaced with values from contribution.
1415 public function testReplaceContributionTokens() {
1416 $customGroup = $this->customGroupCreate(['extends' => 'Contribution', 'title' => 'contribution stuff']);
1417 $customField = $this->customFieldOptionValueCreate($customGroup, 'myCustomField');
1418 $contactId1 = $this->individualCreate();
1420 'contact_id' => $contactId1,
1421 'receive_date' => '20120511',
1422 'total_amount' => 100.00,
1423 'financial_type_id' => 1,
1425 'invoice_id' => 67890,
1427 'contribution_status_id' => 2,
1428 "custom_{$customField['id']}" => 'value1',
1429 'currency' => 'EUR',
1431 $contribution1 = $this->contributionCreate($params);
1432 $contactId2 = $this->individualCreate();
1434 'contact_id' => $contactId2,
1435 'receive_date' => '20150511',
1436 'total_amount' => 200.00,
1437 'financial_type_id' => 1,
1439 'invoice_id' => 12345,
1441 'contribution_status_id' => 1,
1442 "custom_{$customField['id']}" => 'value2',
1444 $contribution2 = $this->contributionCreate($params);
1445 $ids = [$contribution1, $contribution2];
1447 $subject = "This is a test for contribution ID: {contribution.contribution_id}";
1448 $text = "Contribution Amount: {contribution.total_amount}";
1449 $html = "<p>Contribution Source: {contribution.contribution_source}</p></br>
1450 <p>Contribution Invoice ID: {contribution.invoice_id}</p></br>
1451 <p>Contribution Receive Date: {contribution.receive_date}</p></br>
1452 <p>Contribution Custom Field: {contribution.custom_{$customField['id']}}</p></br>";
1454 $subjectToken = CRM_Utils_Token
::getTokens($subject);
1455 $messageToken = CRM_Utils_Token
::getTokens($text);
1456 $messageToken = array_merge($messageToken, CRM_Utils_Token
::getTokens($html));
1458 $contributionDetails = CRM_Contribute_BAO_Contribution
::replaceContributionTokens(
1468 $this->assertEquals("Contribution Amount: € 100.00", $contributionDetails[$contactId1]['text'], "The text does not match");
1469 $this->assertEquals("<p>Contribution Source: ABC</p></br>
1470 <p>Contribution Invoice ID: 12345</p></br>
1471 <p>Contribution Receive Date: May 11th, 2015 12:00 AM</p></br>
1472 <p>Contribution Custom Field: Label2</p></br>", $contributionDetails[$contactId2]['html'], "The html does not match");
1476 * Test for contribution with deferred revenue.
1478 public function testContributionWithDeferredRevenue() {
1479 $contactId = $this->individualCreate();
1480 Civi
::settings()->set('deferred_revenue_enabled', TRUE);
1482 'contact_id' => $contactId,
1483 'receive_date' => '20120511',
1484 'total_amount' => 100.00,
1485 'financial_type_id' => 'Event Fee',
1487 'invoice_id' => 67890,
1489 'contribution_status_id' => 'Completed',
1490 'revenue_recognition_date' => date('Ymd', strtotime("+3 month")),
1492 $contribution = $this->callAPISuccess('contribution', 'create', $params);
1494 $this->callAPISuccessGetCount('EntityFinancialTrxn', [
1495 'entity_table' => "civicrm_contribution",
1496 'entity_id' => $contribution['id'],
1500 'financial_trxn_id.to_financial_account_id.name' => 'Deferred Revenue - Event Fee',
1501 'financial_trxn_id.from_financial_account_id.name' => 'Event Fee',
1502 'financial_trxn_id' => '2',
1504 $result = $this->callAPISuccessGetSingle('EntityFinancialTrxn', [
1506 "financial_trxn_id.from_financial_account_id.name",
1507 "financial_trxn_id.to_financial_account_id.name",
1508 "financial_trxn_id",
1510 'entity_table' => "civicrm_contribution",
1511 'entity_id' => $contribution['id'],
1512 'financial_trxn_id.is_payment' => 0,
1515 $result = $this->callAPISuccessGetSingle('EntityFinancialTrxn', [
1516 'entity_table' => "civicrm_financial_item",
1517 'financial_trxn_id' => $result['financial_trxn_id'],
1518 'return' => ['entity_id'],
1522 'financial_account_id.name' => 'Deferred Revenue - Event Fee',
1523 'id' => $result['entity_id'],
1525 $result = $this->callAPISuccessGetSingle('FinancialItem', [
1526 'id' => $result['entity_id'],
1527 'return' => ["financial_account_id.name"],
1532 * https://lab.civicrm.org/dev/financial/issues/56
1533 * Changing financial type on a contribution records correct financial items
1535 public function testChangingFinancialTypeWithoutTax() {
1536 $ids = $values = [];
1537 $contactId = $this->individualCreate();
1539 'contact_id' => $contactId,
1540 'receive_date' => date('YmdHis'),
1541 'total_amount' => 100.00,
1542 'financial_type_id' => 'Donation',
1543 'contribution_status_id' => 'Completed',
1545 /* first test the scenario when sending an email */
1546 $contributionId = $this->callAPISuccess(
1552 // Update Financial Type.
1553 $this->callAPISuccess('contribution', 'create', [
1554 'id' => $contributionId,
1555 'financial_type_id' => 'Event Fee',
1559 $lineItem = $this->callAPISuccessGetSingle('LineItem', [
1560 'contribution_id' => $contributionId,
1561 'return' => ["financial_type_id.name", "line_total"],
1564 $this->assertEquals(
1565 $lineItem['line_total'],
1567 'Invalid line amount.'
1570 $this->assertEquals(
1571 $lineItem['financial_type_id.name'],
1573 'Invalid Financial Type stored.'
1576 // Get Financial Items.
1577 $financialItems = $this->callAPISuccess('FinancialItem', 'get', [
1578 'entity_id' => $lineItem['id'],
1580 'entity_table' => 'civicrm_line_item',
1581 'options' => ['sort' => "id"],
1582 'return' => ["financial_account_id.name", "amount", "description"],
1585 $this->assertEquals($financialItems['count'], 3, 'Count mismatch.');
1588 ['Donation', 100.00],
1589 ['Donation', -100.00],
1590 ['Event Fee', 100.00],
1593 foreach ($financialItems['values'] as $key => $values) {
1594 $this->assertEquals(
1595 $values['financial_account_id.name'],
1597 'Invalid Financial Account stored.'
1599 $this->assertEquals(
1604 $this->assertEquals(
1605 $values['description'],
1606 'Contribution Amount',
1607 'Description mismatch.'
1611 // Check transactions.
1612 $financialTransactions = $this->callAPISuccess('EntityFinancialTrxn', 'get', [
1613 'return' => ["financial_trxn_id"],
1614 'entity_table' => "civicrm_contribution",
1615 'entity_id' => $contributionId,
1618 $this->assertEquals($financialTransactions['count'], 3, 'Count mismatch.');
1620 foreach ($financialTransactions['values'] as $key => $values) {
1621 $this->callAPISuccessGetCount('EntityFinancialTrxn', [
1622 'financial_trxn_id' => $values['financial_trxn_id'],
1623 'amount' => $toCheck[$key][1],
1624 'financial_trxn_id.total_amount' => $toCheck[$key][1],
1630 * CRM-21424 Check if the receipt update is set after composing the receipt message
1632 public function testSendMailUpdateReceiptDate() {
1633 $ids = $values = [];
1634 $contactId = $this->individualCreate();
1636 'contact_id' => $contactId,
1637 'receive_date' => '20120511',
1638 'total_amount' => 100.00,
1639 'financial_type_id' => 'Donation',
1641 'contribution_status_id' => 'Completed',
1643 /* first test the scenario when sending an email */
1644 $contribution = $this->callAPISuccess('contribution', 'create', $params);
1645 $contributionId = $contribution['id'];
1646 $this->assertDBNull('CRM_Contribute_BAO_Contribution', $contributionId, 'receipt_date', 'id', 'After creating receipt date must be null');
1647 $input = ['receipt_update' => 0];
1648 CRM_Contribute_BAO_Contribution
::sendMail($input, $ids, $contributionId, $values);
1649 $this->assertDBNull('CRM_Contribute_BAO_Contribution', $contributionId, 'receipt_date', 'id', 'After sendMail, with the explicit instruction not to update receipt date stays null');
1650 $input = ['receipt_update' => 1];
1651 CRM_Contribute_BAO_Contribution
::sendMail($input, $ids, $contributionId, $values);
1652 $this->assertDBNotNull('CRM_Contribute_BAO_Contribution', $contributionId, 'receipt_date', 'id', 'After sendMail with the permission to allow update receipt date must be set');
1654 /* repeat the same scenario for downloading a pdf */
1655 $contribution = $this->callAPISuccess('contribution', 'create', $params);
1656 $contributionId = $contribution['id'];
1657 $this->assertDBNull('CRM_Contribute_BAO_Contribution', $contributionId, 'receipt_date', 'id', 'After creating receipt date must be null');
1658 $input = ['receipt_update' => 0];
1659 /* setting the lasast parameter (returnmessagetext) to TRUE is done by the download of the pdf */
1660 CRM_Contribute_BAO_Contribution
::sendMail($input, $ids, $contributionId, $values, TRUE);
1661 $this->assertDBNull('CRM_Contribute_BAO_Contribution', $contributionId, 'receipt_date', 'id', 'After sendMail, with the explicit instruction not to update receipt date stays null');
1662 $input = ['receipt_update' => 1];
1663 CRM_Contribute_BAO_Contribution
::sendMail($input, $ids, $contributionId, $values, TRUE);
1664 $this->assertDBNotNull('CRM_Contribute_BAO_Contribution', $contributionId, 'receipt_date', 'id', 'After sendMail with the permission to allow update receipt date must be set');
1668 * Test cancel order api when a pledge is linked.
1670 * The pledge status should be updated. I believe the contribution should
1671 * also be unlinked but the goal at this point is no change.
1673 * @throws CRM_Core_Exception
1674 * @throws \CiviCRM_API3_Exception
1675 * @throws \API_Exception
1677 public function testCancelOrderWithPledge(): void
{
1678 $this->ids
['contact'][0] = $this->individualCreate();
1679 $pledgeID = (int) $this->callAPISuccess('Pledge', 'create', ['contact_id' => $this->ids
['contact'][0], 'amount' => 4, 'installments' => 2, 'frequency_unit' => 'month', 'original_installment_amount' => 2, 'create_date' => 'now', 'financial_type_id' => 'Donation', 'start_date' => '+5 days'])['id'];
1680 $orderID = (int) $this->callAPISuccess('Order', 'create', ['contact_id' => $this->ids
['contact'][0], 'total_amount' => 2, 'financial_type_id' => 'Donation', 'api.Payment.create' => ['total_amount' => 2]])['id'];
1681 $pledgePayments = $this->callAPISuccess('PledgePayment', 'get')['values'];
1682 $this->callAPISuccess('PledgePayment', 'create', ['id' => key($pledgePayments), 'pledge_id' => $pledgeID, 'contribution_id' => $orderID, 'status_id' => 'Completed', 'actual_amount' => 2]);
1683 $beforePledge = $this->callAPISuccessGetSingle('Pledge', ['id' => $pledgeID]);
1684 $this->assertEquals(2, $beforePledge['pledge_total_paid']);
1685 $this->callAPISuccess('Order', 'cancel', ['contribution_id' => $orderID]);
1687 $this->callAPISuccessGetSingle('Contribution', ['contribution_status_id' => 'Cancelled']);
1688 $afterPledge = $this->callAPISuccessGetSingle('Pledge', ['id' => $pledgeID]);
1689 $this->assertEquals('', $afterPledge['pledge_total_paid']);
1690 $payments = PledgePayment
::get(FALSE)->addWhere('contribution_id', 'IS NOT NULL')->execute();
1691 $this->assertCount(0, $payments);
1695 * Test contribution update when more than one quick
1696 * config line item is linked to contribution.
1698 * @throws CRM_Core_Exception
1699 * @throws \CiviCRM_API3_Exception
1700 * @throws \API_Exception
1702 public function testContributionQuickConfigTwoLineItems(): void
{
1703 $contactId1 = $this->individualCreate();
1704 $contactId2 = $this->individualCreate();
1705 $membershipOrganizationId = $this->organizationCreate();
1707 // Created new contribution to bypass the deprecated error
1708 // 'Per https://lab.civicrm.org/dev/core/issues/15 this data fix should not be required.'
1709 // in CRM_Price_BAO_LineItem::processPriceSet();
1710 $this->callAPISuccess('Contribution', 'create', [
1711 'contact_id' => $contactId1,
1712 'receive_date' => '2010-01-20',
1713 'financial_type_id' => 'Member Dues',
1714 'contribution_status_id' => 'Completed',
1715 'total_amount' => 150,
1717 $this->callAPISuccess('Contribution', 'create', [
1718 'contact_id' => $contactId1,
1719 'receive_date' => '2010-01-20',
1720 'financial_type_id' => 'Member Dues',
1721 'contribution_status_id' => 'Completed',
1722 'total_amount' => 150,
1725 // create membership type
1726 $membershipTypeId1 = $this->callAPISuccess('MembershipType', 'create', [
1728 'member_of_contact_id' => $membershipOrganizationId,
1729 'financial_type_id' => 'Member Dues',
1730 'duration_unit' => 'month',
1731 'duration_interval' => 1,
1732 'period_type' => 'rolling',
1733 'minimum_fee' => 100,
1737 $membershipTypeId2 = $this->callAPISuccess('MembershipType', 'create', [
1739 'member_of_contact_id' => $membershipOrganizationId,
1740 'financial_type_id' => 'Member Dues',
1741 'duration_unit' => 'month',
1742 'duration_interval' => 1,
1743 'period_type' => 'rolling',
1744 'minimum_fee' => 50,
1749 $contactId1 => $membershipTypeId1,
1750 $contactId2 => $membershipTypeId2,
1753 $priceFields = CRM_Price_BAO_PriceSet
::getDefaultPriceSet('membership');
1755 // prepare order api params.
1757 'contact_id' => $contactId1,
1758 'receive_date' => '2010-01-20',
1759 'financial_type_id' => 'Member Dues',
1760 'contribution_status_id' => 'Pending',
1761 'total_amount' => 150,
1762 'api.Payment.create' => ['total_amount' => 150],
1766 foreach ($priceFields as $priceField) {
1768 $contactId = array_search($priceField['membership_type_id'], $contactIds);
1770 'price_field_id' => $priceField['priceFieldID'],
1771 'price_field_value_id' => $priceField['priceFieldValueID'],
1772 'label' => $priceField['label'],
1773 'field_title' => $priceField['label'],
1775 'unit_price' => $priceField['amount'],
1776 'line_total' => $priceField['amount'],
1777 'financial_type_id' => $priceField['financial_type_id'],
1778 'entity_table' => 'civicrm_membership',
1779 'membership_type_id' => $priceField['membership_type_id'],
1781 $p['line_items'][] = [
1782 'line_item' => $lineItems,
1784 'contact_id' => $contactId,
1785 'membership_type_id' => $priceField['membership_type_id'],
1786 'source' => 'Payment',
1787 'join_date' => '2020-04-28',
1788 'start_date' => '2020-04-28',
1789 'status_id' => 'Pending',
1794 $order = $this->callAPISuccess('order', 'create', $p);
1795 $contributionId = $order['id'];
1797 $count = CRM_Core_DAO
::singleValueQuery('
1798 SELECT count(*), total_amount
1799 FROM civicrm_contribution cc
1800 INNER JOIN civicrm_line_item cli
1801 ON cli.contribution_id = cc.id
1803 GROUP BY cc.id, total_amount
1804 HAVING SUM(cli.line_total) != total_amount
1805 ', [1 => [$contributionId, 'Integer']]);
1807 $this->assertEquals(0, $count);
1809 $this->callAPISuccess('Contribution', 'create', [
1810 'id' => $contributionId,
1811 'total_amount' => 150,
1813 $count = CRM_Core_DAO
::singleValueQuery('
1814 SELECT count(*), total_amount
1815 FROM civicrm_contribution cc
1816 INNER JOIN civicrm_line_item cli
1817 ON cli.contribution_id = cc.id
1819 GROUP BY cc.id, total_amount
1820 HAVING SUM(cli.line_total) != total_amount
1821 ', [1 => [$contributionId, 'Integer']]);
1823 $this->assertEquals(0, $count);
1827 * Test activity contact is updated when contribution contact is changed
1829 public function testUpdateActivityContactOnContributionContactChange(): void
{
1830 $contactId_1 = $this->individualCreate();
1831 $contactId_2 = $this->individualCreate();
1832 $contactId_3 = $this->individualCreate();
1834 $contributionParams = [
1835 'financial_type_id' => 'Donation',
1836 'receive_date' => date('Y-m-d H:i:s'),
1837 'sequential' => TRUE,
1838 'total_amount' => 50,
1841 // Case 1: Only source contact, no target contact
1843 $contribution = $this->callAPISuccess('Contribution', 'create', array_merge(
1844 $contributionParams,
1845 ['contact_id' => $contactId_1]
1848 $activity = $this->callAPISuccessGetSingle('Activity', ['source_record_id' => $contribution['id']]);
1850 $activityContactParams = [
1851 'activity_id' => $activity['id'],
1852 'record_type_id' => 'Activity Source',
1855 $activityContact = $this->callAPISuccessGetSingle('ActivityContact', $activityContactParams);
1857 $this->assertEquals($activityContact['contact_id'], $contactId_1, 'Check source contact ID matches the first contact');
1859 $contribution = $this->callAPISuccess('Contribution', 'create', array_merge(
1860 $contributionParams,
1862 'id' => $contribution['id'],
1863 'contact_id' => $contactId_2,
1867 $activityContact = $this->callAPISuccessGetSingle('ActivityContact', $activityContactParams);
1869 $this->assertEquals($activityContact['contact_id'], $contactId_2, 'Check source contact ID matches the second contact');
1871 // Case 2: Source and target contact
1873 $contribution = $this->callAPISuccess('Contribution', 'create', array_merge(
1874 $contributionParams,
1876 'contact_id' => $contactId_1,
1877 'source_contact_id' => $contactId_3,
1881 $activity = $this->callAPISuccessGetSingle('Activity', ['source_record_id' => $contribution['id']]);
1883 $activityContactParams = [
1884 'activity_id' => $activity['id'],
1885 'record_type_id' => 'Activity Targets',
1888 $activityContact = $this->callAPISuccessGetSingle('ActivityContact', $activityContactParams);
1890 $this->assertEquals($activityContact['contact_id'], $contactId_1, 'Check target contact ID matches first contact');
1892 $contribution = $this->callAPISuccess('Contribution', 'create', array_merge(
1893 $contributionParams,
1895 'id' => $contribution['id'],
1896 'contact_id' => $contactId_2,
1900 $activityContact = $this->callAPISuccessGetSingle('ActivityContact', $activityContactParams);
1902 $this->assertEquals($activityContact['contact_id'], $contactId_2, 'Check target contact ID matches the second contact');