Test - fix contributionTest to validate contributions
[civicrm-core.git] / tests / phpunit / api / v3 / ContributionTest.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 use Civi\Api4\Contribution;
13 use Civi\Api4\PriceField;
14 use Civi\Api4\PriceFieldValue;
15 use Civi\Api4\PriceSet;
16
17 /**
18 * Test APIv3 civicrm_contribute_* functions
19 *
20 * @package CiviCRM_APIv3
21 * @subpackage API_Contribution
22 * @group headless
23 */
24 class api_v3_ContributionTest extends CiviUnitTestCase {
25
26 use CRMTraits_Profile_ProfileTrait;
27 use CRMTraits_Custom_CustomDataTrait;
28 use CRMTraits_Financial_OrderTrait;
29 use CRMTraits_Financial_TaxTrait;
30
31 protected $_individualId;
32 protected $_contribution;
33 protected $_financialTypeId = 1;
34 protected $entity = 'Contribution';
35 protected $_params;
36 protected $_ids = [];
37 protected $_pageParams = [];
38
39 /**
40 * Payment processor ID (dummy processor).
41 *
42 * @var int
43 */
44 protected $paymentProcessorID;
45
46 /**
47 * Parameters to create payment processor.
48 *
49 * @var array
50 */
51 protected $_processorParams = [];
52
53 /**
54 * ID of created event.
55 *
56 * @var int
57 */
58 protected $_eventID;
59
60 /**
61 * @var CiviMailUtils
62 */
63 protected $mut;
64
65 /**
66 * Should financials be checked after the test but before tear down.
67 *
68 * @var bool
69 */
70 protected $isValidateFinancialsOnPostAssert = TRUE;
71
72 /**
73 * Setup function.
74 *
75 * @throws \CiviCRM_API3_Exception
76 */
77 public function setUp(): void {
78 parent::setUp();
79
80 $this->_apiversion = 3;
81 $this->_individualId = $this->individualCreate();
82 $this->_params = [
83 'contact_id' => $this->_individualId,
84 'receive_date' => '20120511',
85 'total_amount' => 100.00,
86 'financial_type_id' => $this->_financialTypeId,
87 'non_deductible_amount' => 10.00,
88 'fee_amount' => 5.00,
89 'net_amount' => 95.00,
90 'source' => 'SSF',
91 'contribution_status_id' => 1,
92 ];
93 $this->_processorParams = [
94 'domain_id' => 1,
95 'name' => 'Dummy',
96 'payment_processor_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Financial_BAO_PaymentProcessor', 'payment_processor_type_id', 'Dummy'),
97 'financial_account_id' => 12,
98 'is_active' => 1,
99 'user_name' => '',
100 'url_site' => 'http://dummy.com',
101 'url_recur' => 'http://dummy.com',
102 'billing_mode' => 1,
103 ];
104 $this->paymentProcessorID = $this->processorCreate();
105 $this->_pageParams = [
106 'title' => 'Test Contribution Page',
107 'financial_type_id' => 1,
108 'currency' => 'USD',
109 'financial_account_id' => 1,
110 'payment_processor' => $this->paymentProcessorID,
111 'is_active' => 1,
112 'is_allow_other_amount' => 1,
113 'min_amount' => 10,
114 'max_amount' => 1000,
115 ];
116 }
117
118 /**
119 * Clean up after each test.
120 *
121 * @throws \CRM_Core_Exception
122 * @throws \CiviCRM_API3_Exception
123 */
124 public function tearDown(): void {
125 $this->quickCleanUpFinancialEntities();
126 $this->quickCleanup(['civicrm_uf_match'], TRUE);
127 $financialAccounts = $this->callAPISuccess('FinancialAccount', 'get', []);
128 foreach ($financialAccounts['values'] as $financialAccount) {
129 if ($financialAccount['name'] === 'Test Tax financial account ' || $financialAccount['name'] === 'Test taxable financial Type') {
130 $entityFinancialTypes = $this->callAPISuccess('EntityFinancialAccount', 'get', [
131 'financial_account_id' => $financialAccount['id'],
132 ]);
133 foreach ($entityFinancialTypes['values'] as $entityFinancialType) {
134 $this->callAPISuccess('EntityFinancialAccount', 'delete', ['id' => $entityFinancialType['id']]);
135 }
136 $this->callAPISuccess('FinancialAccount', 'delete', ['id' => $financialAccount['id']]);
137 }
138 }
139 $this->restoreUFGroupOne();
140 parent::tearDown();
141 }
142
143 /**
144 * Test Get.
145 *
146 * @throws \CRM_Core_Exception
147 */
148 public function testGetContribution(): void {
149 $this->enableTaxAndInvoicing();
150 $p = [
151 'contact_id' => $this->_individualId,
152 'receive_date' => '2010-01-20',
153 'total_amount' => 100.00,
154 'financial_type_id' => $this->_financialTypeId,
155 'non_deductible_amount' => 10.00,
156 'fee_amount' => 5.00,
157 'net_amount' => 95.00,
158 'trxn_id' => 23456,
159 'invoice_id' => 78910,
160 'source' => 'SSF',
161 'contribution_status_id' => 'Completed',
162 ];
163 $this->_contribution = $this->callAPISuccess('contribution', 'create', $p);
164
165 $params = [
166 'contribution_id' => $this->_contribution['id'],
167 ];
168
169 $contributions = $this->callAPIAndDocument('contribution', 'get', $params, __FUNCTION__, __FILE__);
170 $financialParams['id'] = $this->_financialTypeId;
171 $default = NULL;
172 CRM_Financial_BAO_FinancialType::retrieve($financialParams, $default);
173
174 $this->assertEquals(1, $contributions['count']);
175 $contribution = $contributions['values'][$contributions['id']];
176 $this->assertEquals($this->_individualId, $contribution['contact_id']);
177 $this->assertEquals(1, $contribution['financial_type_id']);
178 $this->assertEquals(100.00, $contribution['total_amount']);
179 $this->assertEquals(10.00, $contribution['non_deductible_amount']);
180 $this->assertEquals($contribution['fee_amount'], 5.00);
181 $this->assertEquals(95.00, $contribution['net_amount']);
182 $this->assertEquals(23456, $contribution['trxn_id']);
183 $this->assertEquals(78910, $contribution['invoice_id']);
184 $this->assertRegExp('/INV_\d+/', $contribution['invoice_number']);
185 $this->assertEquals('SSF', $contribution['contribution_source']);
186 $this->assertEquals('Completed', $contribution['contribution_status']);
187 // Create a second contribution - we are testing that 'id' gets the right contribution id (not the contact id).
188 $p['trxn_id'] = '3847';
189 $p['invoice_id'] = '3847';
190
191 $contribution2 = $this->callAPISuccess('contribution', 'create', $p);
192
193 // Now we have 2 - test getcount.
194 $contribution = $this->callAPISuccess('contribution', 'getcount');
195 $this->assertEquals(2, $contribution);
196 // Test id only format.
197 $contribution = $this->callAPISuccess('contribution', 'get', [
198 'id' => $this->_contribution['id'],
199 'format.only_id' => 1,
200 ]);
201 $this->assertEquals($this->_contribution['id'], $contribution, print_r($contribution, TRUE));
202 // Test id only format.
203 $contribution = $this->callAPISuccess('contribution', 'get', [
204 'id' => $contribution2['id'],
205 'format.only_id' => 1,
206 ]);
207 $this->assertEquals($contribution2['id'], $contribution);
208 // Test id as field.
209 $contribution = $this->callAPISuccess('contribution', 'get', [
210 'id' => $this->_contribution['id'],
211 ]);
212 $this->assertEquals(1, $contribution['count']);
213
214 // Test get by contact id works.
215 $contribution = $this->callAPISuccess('contribution', 'get', ['contact_id' => $this->_individualId]);
216
217 $this->assertEquals(2, $contribution['count']);
218 $this->callAPISuccess('Contribution', 'Delete', [
219 'id' => $this->_contribution['id'],
220 ]);
221 $this->callAPISuccess('Contribution', 'Delete', [
222 'id' => $contribution2['id'],
223 ]);
224 }
225
226 /**
227 * Test that test contributions can be retrieved.
228 *
229 * @throws \CRM_Core_Exception
230 */
231 public function testGetTestContribution(): void {
232 $this->callAPISuccess('Contribution', 'create', array_merge($this->_params, ['is_test' => 1]));
233 $this->callAPISuccessGetSingle('Contribution', ['is_test' => 1]);
234 }
235
236 /**
237 * Test Creating a check contribution with original check_number field
238 *
239 * @throws \CRM_Core_Exception
240 */
241 public function testCreateCheckContribution(): void {
242 $params = $this->_params;
243 $params['contribution_check_number'] = 'bouncer';
244 $params['payment_instrument_id'] = 'Check';
245 $params['cancel_date'] = 'yesterday';
246 $params['receipt_date'] = 'yesterday';
247 $params['thankyou_date'] = 'yesterday';
248 $params['revenue_recognition_date'] = 'yesterday';
249 $params['amount_level'] = 'Unreasonable';
250 $params['cancel_reason'] = 'You lose sucker';
251 $params['creditnote_id'] = 'sudo rm -rf';
252 $address = $this->callAPISuccess('Address', 'create', [
253 'street_address' => 'Knockturn Alley',
254 'contact_id' => $this->_individualId,
255 'location_type_id' => 'Home',
256 ]);
257 $params['address_id'] = $address['id'];
258 $contributionPage = $this->contributionPageCreate();
259 $params['contribution_page_id'] = $contributionPage['id'];
260 $params['campaign_id'] = $this->campaignCreate();
261 $contributionID = $this->contributionCreate($params);
262 $getResult = $this->callAPISuccess('Contribution', 'get', ['id' => $contributionID]);
263 $this->assertEquals('bouncer', $getResult['values'][$contributionID]['check_number']);
264 $entityFinancialTrxn = $this->callAPISuccess('EntityFinancialTrxn', 'get', ['entity_id' => $contributionID, 'entity_table' => 'civicrm_contribution']);
265 foreach ($entityFinancialTrxn['values'] as $eft) {
266 $financialTrxn = $this->callAPISuccess('FinancialTrxn', 'get', ['id' => $eft['financial_trxn_id']]);
267 $this->assertEquals('bouncer', $financialTrxn['values'][$financialTrxn['id']]['check_number']);
268 }
269 }
270
271 /**
272 * Test the 'return' param works for all fields.
273 *
274 * @throws \CRM_Core_Exception
275 */
276 public function testGetContributionReturnFunctionality(): void {
277 $params = $this->_params;
278 $params['contribution_check_number'] = 'bouncer';
279 $params['payment_instrument_id'] = 'Check';
280 $params['cancel_date'] = 'yesterday';
281 $params['receipt_date'] = 'yesterday';
282 $params['thankyou_date'] = 'yesterday';
283 $params['revenue_recognition_date'] = 'yesterday';
284 $params['amount_level'] = 'Unreasonable';
285 $params['cancel_reason'] = 'You lose sucker';
286 $params['creditnote_id'] = 'sudo rm -rf';
287 $address = $this->callAPISuccess('Address', 'create', [
288 'street_address' => 'Knockturn Alley',
289 'contact_id' => $this->_individualId,
290 'location_type_id' => 'Home',
291 ]);
292 $params['address_id'] = $address['id'];
293 $contributionPage = $this->contributionPageCreate();
294 $params['contribution_page_id'] = $contributionPage['id'];
295 $contributionRecur = $this->callAPISuccess('ContributionRecur', 'create', [
296 'contact_id' => $this->_individualId,
297 'frequency_interval' => 1,
298 'amount' => 5,
299 ]);
300 $params['contribution_recur_id'] = $contributionRecur['id'];
301
302 $params['campaign_id'] = $this->campaignCreate();
303
304 $contributionID = $this->contributionCreate($params);
305
306 // update contribution with invoice number
307 $params = array_merge($params, [
308 'id' => $contributionID,
309 'invoice_number' => CRM_Utils_Array::value('invoice_prefix', Civi::settings()->get('contribution_invoice_settings')) . "" . $contributionID,
310 'trxn_id' => 12345,
311 'invoice_id' => 6789,
312 ]);
313 $contributionID = $this->contributionCreate($params);
314
315 $contribution = $this->callAPISuccessGetSingle('Contribution', ['id' => $contributionID]);
316 $this->assertEquals('bouncer', $contribution['check_number']);
317 $this->assertEquals('bouncer', $contribution['contribution_check_number']);
318
319 $fields = CRM_Contribute_BAO_Contribution::fields();
320 // Do not check for tax_amount as this test has not enabled invoicing
321 // & hence it is not reliable.
322 unset($fields['tax_amount']);
323 // Re-add these 2 to the fields to check. They were locked in but the metadata changed so we
324 // need to specify them.
325 $fields['address_id'] = $fields['contribution_address_id'];
326 $fields['check_number'] = $fields['contribution_check_number'];
327
328 $fieldsLockedIn = [
329 'contribution_id', 'contribution_contact_id', 'financial_type_id', 'contribution_page_id',
330 'payment_instrument_id', 'receive_date', 'non_deductible_amount', 'total_amount',
331 'fee_amount', 'net_amount', 'trxn_id', 'invoice_id', 'currency', 'contribution_cancel_date', 'cancel_reason',
332 'receipt_date', 'thankyou_date', 'contribution_source', 'amount_level', 'contribution_recur_id',
333 'is_test', 'is_pay_later', 'contribution_status_id', 'address_id', 'check_number', 'contribution_campaign_id',
334 'creditnote_id', 'revenue_recognition_date', 'decoy',
335 ];
336 $missingFields = array_diff($fieldsLockedIn, array_keys($fields));
337 // If any of the locked in fields disappear from the $fields array we need to make sure it is still
338 // covered as the test contract now guarantees them in the return array.
339 $this->assertEquals([28 => 'decoy'], $missingFields, 'A field which was covered by the test contract has changed.');
340 foreach ($fields as $fieldName => $fieldSpec) {
341 $contribution = $this->callAPISuccessGetSingle('Contribution', ['id' => $contributionID, 'return' => $fieldName]);
342 $returnField = $fieldName;
343 if ($returnField === 'contribution_contact_id') {
344 $returnField = 'contact_id';
345 }
346 $this->assertTrue((!empty($contribution[$returnField]) || $contribution[$returnField] === "0"), $returnField);
347 }
348 $entityFinancialTrxn = $this->callAPISuccess('EntityFinancialTrxn', 'get', ['entity_id' => $contributionID, 'entity_table' => 'civicrm_contribution']);
349 foreach ($entityFinancialTrxn['values'] as $eft) {
350 $financialTrxn = $this->callAPISuccess('FinancialTrxn', 'get', ['id' => $eft['financial_trxn_id']]);
351 $this->assertEquals('bouncer', $financialTrxn['values'][$financialTrxn['id']]['check_number']);
352 }
353 }
354
355 /**
356 * Test cancel reason works as a filter.
357 *
358 * @throws \CRM_Core_Exception
359 */
360 public function testFilterCancelReason(): void {
361 $params = $this->_params;
362 $params['cancel_date'] = 'yesterday';
363 $params['cancel_reason'] = 'You lose sucker';
364 $this->callAPISuccess('Contribution', 'create', $params);
365 $params = $this->_params;
366 $params['cancel_date'] = 'yesterday';
367 $params['cancel_reason'] = 'You are a winner';
368 $this->callAPISuccess('Contribution', 'create', $params);
369 $this->callAPISuccessGetCount('Contribution', ['cancel_reason' => 'You are a winner'], 1);
370 }
371
372 /**
373 * We need to ensure previous tested api contract behaviour still works.
374 *
375 * @throws \CRM_Core_Exception
376 */
377 public function testGetContributionLegacyBehaviour(): void {
378 $p = [
379 'contact_id' => $this->_individualId,
380 'receive_date' => '2010-01-20',
381 'total_amount' => 100.00,
382 'contribution_type_id' => $this->_financialTypeId,
383 'non_deductible_amount' => 10.00,
384 'fee_amount' => 5.00,
385 'net_amount' => 95.00,
386 'trxn_id' => 23456,
387 'invoice_id' => 78910,
388 'source' => 'SSF',
389 'contribution_status_id' => 1,
390 ];
391 $this->_contribution = $this->callAPISuccess('Contribution', 'create', $p);
392
393 $params = [
394 'contribution_id' => $this->_contribution['id'],
395 ];
396 $contribution = $this->callAPISuccess('contribution', 'get', $params);
397 $financialParams['id'] = $this->_financialTypeId;
398 $default = NULL;
399 CRM_Financial_BAO_FinancialType::retrieve($financialParams, $default);
400
401 $this->assertEquals(1, $contribution['count']);
402 $this->assertEquals($contribution['values'][$contribution['id']]['contact_id'], $this->_individualId);
403 $this->assertEquals($contribution['values'][$contribution['id']]['financial_type_id'], $this->_financialTypeId);
404 $this->assertEquals($contribution['values'][$contribution['id']]['contribution_type_id'], $this->_financialTypeId);
405 $this->assertEquals($contribution['values'][$contribution['id']]['total_amount'], 100.00);
406 $this->assertEquals($contribution['values'][$contribution['id']]['non_deductible_amount'], 10.00);
407 $this->assertEquals($contribution['values'][$contribution['id']]['fee_amount'], 5.00);
408 $this->assertEquals($contribution['values'][$contribution['id']]['net_amount'], 95.00);
409 $this->assertEquals($contribution['values'][$contribution['id']]['trxn_id'], 23456);
410 $this->assertEquals($contribution['values'][$contribution['id']]['invoice_id'], 78910);
411 $this->assertEquals($contribution['values'][$contribution['id']]['contribution_source'], 'SSF');
412 $this->assertEquals($contribution['values'][$contribution['id']]['contribution_status'], 'Completed');
413
414 // Create a second contribution - we are testing that 'id' gets the right contribution id (not the contact id).
415 $p['trxn_id'] = '3847';
416 $p['invoice_id'] = '3847';
417
418 $contribution2 = $this->callAPISuccess('contribution', 'create', $p);
419
420 // now we have 2 - test getcount
421 $contribution = $this->callAPISuccess('contribution', 'getcount', []);
422 $this->assertEquals(2, $contribution);
423 //test id only format
424 $contribution = $this->callAPISuccess('contribution', 'get', [
425 'id' => $this->_contribution['id'],
426 'format.only_id' => 1,
427 ]);
428 $this->assertEquals($this->_contribution['id'], $contribution, print_r($contribution, TRUE));
429 //test id only format
430 $contribution = $this->callAPISuccess('contribution', 'get', [
431 'id' => $contribution2['id'],
432 'format.only_id' => 1,
433 ]);
434 $this->assertEquals($contribution2['id'], $contribution);
435 $contribution = $this->callAPISuccess('contribution', 'get', [
436 'id' => $this->_contribution['id'],
437 ]);
438 //test id as field
439 $this->assertEquals(1, $contribution['count']);
440 // $this->assertEquals($this->_contribution['id'], $contribution['id'] ) ;
441 //test get by contact id works
442 $contribution = $this->callAPISuccess('contribution', 'get', ['contact_id' => $this->_individualId]);
443
444 $this->assertEquals(2, $contribution['count']);
445 $this->callAPISuccess('Contribution', 'Delete', [
446 'id' => $this->_contribution['id'],
447 ]);
448 $this->callAPISuccess('Contribution', 'Delete', [
449 'id' => $contribution2['id'],
450 ]);
451 }
452
453 /**
454 * Create an contribution_id=FALSE and financial_type_id=Donation.
455 */
456 public function testCreateEmptyContributionIDUseDonation() {
457 $params = [
458 'contribution_id' => FALSE,
459 'contact_id' => 1,
460 'total_amount' => 1,
461 'check_permissions' => FALSE,
462 'financial_type_id' => 'Donation',
463 ];
464 $this->callAPISuccess('contribution', 'create', $params);
465 }
466
467 /**
468 * Check with complete array + custom field.
469 *
470 * Note that the test is written on purpose without any
471 * variables specific to participant so it can be replicated into other entities
472 * and / or moved to the automated test suite
473 *
474 * @throws \CRM_Core_Exception
475 */
476 public function testCreateWithCustom(): void {
477 $ids = $this->entityCustomGroupWithSingleFieldCreate(__FUNCTION__, __FILE__);
478
479 $params = $this->_params;
480 $params['custom_' . $ids['custom_field_id']] = "custom string";
481
482 $result = $this->callAPIAndDocument($this->entity, 'create', $params, __FUNCTION__, __FILE__);
483 $this->assertEquals($result['id'], $result['values'][$result['id']]['id']);
484 $check = $this->callAPISuccess($this->entity, 'get', [
485 'return.custom_' . $ids['custom_field_id'] => 1,
486 'id' => $result['id'],
487 ]);
488 $group = $this->callAPISuccess('CustomGroup', 'getsingle', ['id' => $ids['custom_group_id']]);
489 $field = $this->callAPISuccess('CustomField', 'getsingle', ['id' => $ids['custom_field_id']]);
490 $contribution = \Civi\Api4\Contribution::get()
491 ->setSelect([
492 'id',
493 'total_amount',
494 $group['name'] . '.' . $field['name'],
495 ])
496 ->addWhere('id', '=', $result['id'])
497 ->execute()
498 ->first();
499 $this->customFieldDelete($ids['custom_field_id']);
500 $this->customGroupDelete($ids['custom_group_id']);
501 $this->assertEquals('custom string', $check['values'][$check['id']]['custom_' . $ids['custom_field_id']]);
502 }
503
504 /**
505 * Check with complete array + custom field.
506 *
507 * Note that the test is written on purpose without any
508 * variables specific to participant so it can be replicated into other
509 * entities and / or moved to the automated test suite
510 *
511 * @throws \CRM_Core_Exception
512 */
513 public function testCreateGetFieldsWithCustom(): void {
514 $ids = $this->entityCustomGroupWithSingleFieldCreate(__FUNCTION__, __FILE__);
515 $idsContact = $this->entityCustomGroupWithSingleFieldCreate(__FUNCTION__, 'ContactTest.php');
516 $result = $this->callAPISuccess('Contribution', 'getfields', []);
517 $this->assertArrayHasKey('custom_' . $ids['custom_field_id'], $result['values']);
518 $this->assertArrayNotHasKey('custom_' . $idsContact['custom_field_id'], $result['values']);
519 $this->customFieldDelete($ids['custom_field_id']);
520 $this->customGroupDelete($ids['custom_group_id']);
521 $this->customFieldDelete($idsContact['custom_field_id']);
522 $this->customGroupDelete($idsContact['custom_group_id']);
523 }
524
525 /**
526 * Test creating a contribution without skipLineItems.
527 *
528 * @throws \CRM_Core_Exception
529 */
530 public function testCreateContributionNoLineItems(): void {
531 // Turn off this validation as this test results in invalid
532 // financial entities.
533 $this->isValidateFinancialsOnPostAssert = FALSE;
534 $params = [
535 'contact_id' => $this->_individualId,
536 'receive_date' => '20120511',
537 'total_amount' => 100.00,
538 'financial_type_id' => $this->_financialTypeId,
539 'payment_instrument_id' => 1,
540 'non_deductible_amount' => 10.00,
541 'fee_amount' => 50.00,
542 'net_amount' => 90.00,
543 'trxn_id' => 12345,
544 'invoice_id' => 67890,
545 'source' => 'SSF',
546 'contribution_status_id' => 1,
547 'skipLineItem' => 1,
548 ];
549
550 $contribution = $this->callAPISuccess('contribution', 'create', $params);
551 $financialItems = $this->callAPISuccess('FinancialItem', 'get', []);
552 foreach ($financialItems['values'] as $financialItem) {
553 $this->assertEquals(date('Y-m-d H:i:s', strtotime($contribution['values'][$contribution['id']]['receive_date'])), date('Y-m-d H:i:s', strtotime($financialItem['transaction_date'])));
554 }
555 $lineItems = $this->callAPISuccess('line_item', 'get', [
556 'entity_id' => $contribution['id'],
557 'entity_table' => 'civicrm_contribution',
558 'sequential' => 1,
559 ]);
560 $this->assertEquals(0, $lineItems['count']);
561 }
562
563 /**
564 * Test checks that passing in line items suppresses the create mechanism.
565 *
566 * @throws \CRM_Core_Exception
567 */
568 public function testCreateContributionChainedLineItems(): void {
569 $params = [
570 'contact_id' => $this->_individualId,
571 'receive_date' => '20120511',
572 'total_amount' => 100.00,
573 'financial_type_id' => $this->_financialTypeId,
574 'payment_instrument_id' => 1,
575 'non_deductible_amount' => 10.00,
576 'fee_amount' => 50.00,
577 'net_amount' => 90.00,
578 'trxn_id' => 12345,
579 'invoice_id' => 67890,
580 'source' => 'SSF',
581 'contribution_status_id' => 'Pending',
582 'skipLineItem' => 1,
583 'api.line_item.create' => [
584 [
585 'price_field_id' => 1,
586 'qty' => 2,
587 'line_total' => '20',
588 'unit_price' => '10',
589 ],
590 [
591 'price_field_id' => 1,
592 'qty' => 1,
593 'line_total' => '80',
594 'unit_price' => '80',
595 ],
596 ],
597 ];
598
599 $description = 'Create Contribution with Nested Line Items.';
600 $subFile = 'CreateWithNestedLineItems';
601 $contribution = $this->callAPIAndDocument('Contribution', 'create', $params, __FUNCTION__, __FILE__, $description, $subFile);
602
603 $lineItems = $this->callAPISuccess('line_item', 'get', [
604 'entity_id' => $contribution['id'],
605 'contribution_id' => $contribution['id'],
606 'entity_table' => 'civicrm_contribution',
607 'sequential' => 1,
608 ]);
609 $this->assertEquals(2, $lineItems['count']);
610 }
611
612 /**
613 * @throws \CRM_Core_Exception
614 */
615 public function testCreateContributionOffline(): void {
616 $params = [
617 'contact_id' => $this->_individualId,
618 'receive_date' => '20120511',
619 'total_amount' => 100.00,
620 'financial_type_id' => 1,
621 'trxn_id' => 12345,
622 'invoice_id' => 67890,
623 'source' => 'SSF',
624 'contribution_status_id' => 1,
625 'sequential' => 1,
626 ];
627
628 $contribution = $this->callAPISuccess('Contribution', 'create', $params)['values'][0];
629 $this->assertEquals($this->_individualId, $contribution['contact_id']);
630 $this->assertEquals(100.00, $contribution['total_amount']);
631 $this->assertEquals(1, $contribution['financial_type_id']);
632 $this->assertEquals(12345, $contribution['trxn_id']);
633 $this->assertEquals(67890, $contribution['invoice_id']);
634 $this->assertEquals('SSF', $contribution['source']);
635 $this->assertEquals(1, $contribution['contribution_status_id']);
636 $lineItems = $this->callAPISuccess('LineItem', 'get', [
637 'entity_id' => $contribution['id'],
638 'contribution_id' => $contribution['id'],
639 'entity_table' => 'civicrm_contribution',
640 'sequential' => 1,
641 ]);
642 $this->assertEquals(1, $lineItems['count']);
643 $this->assertEquals($contribution['id'], $lineItems['values'][0]['entity_id']);
644 $this->assertEquals($contribution['id'], $lineItems['values'][0]['contribution_id']);
645 $this->_checkFinancialRecords($contribution, 'offline');
646 $this->contributionGetnCheck($params, $contribution['id']);
647 }
648
649 /**
650 * Test create with valid payment instrument.
651 *
652 * @throws \CRM_Core_Exception
653 */
654 public function testCreateContributionWithPaymentInstrument(): void {
655 $params = $this->_params + ['payment_instrument' => 'EFT'];
656 $contribution = $this->callAPISuccess('contribution', 'create', $params);
657 $contribution = $this->callAPISuccess('contribution', 'get', [
658 'sequential' => 1,
659 'id' => $contribution['id'],
660 ]);
661 $this->assertArrayHasKey('payment_instrument', $contribution['values'][0]);
662 $this->assertEquals('EFT', $contribution['values'][0]['payment_instrument']);
663
664 $this->callAPISuccess('contribution', 'create', [
665 'id' => $contribution['id'],
666 'payment_instrument' => 'Credit Card',
667 ]);
668 $contribution = $this->callAPISuccess('contribution', 'get', [
669 'sequential' => 1,
670 'id' => $contribution['id'],
671 ]);
672 $this->assertArrayHasKey('payment_instrument', $contribution['values'][0]);
673 $this->assertEquals('Credit Card', $contribution['values'][0]['payment_instrument']);
674 }
675
676 /**
677 * @throws \CRM_Core_Exception
678 */
679 public function testGetContributionByPaymentInstrument(): void {
680 $params = $this->_params + ['payment_instrument' => 'EFT'];
681 $params2 = $this->_params + ['payment_instrument' => 'Cash'];
682 $this->callAPISuccess('contribution', 'create', $params);
683 $this->callAPISuccess('contribution', 'create', $params2);
684 $contribution = $this->callAPISuccess('contribution', 'get', [
685 'sequential' => 1,
686 'contribution_payment_instrument' => 'Cash',
687 ]);
688 $this->assertArrayHasKey('payment_instrument', $contribution['values'][0]);
689 $this->assertEquals('Cash', $contribution['values'][0]['payment_instrument']);
690 $this->assertEquals(1, $contribution['count']);
691 $contribution = $this->callAPISuccess('contribution', 'get', ['sequential' => 1, 'payment_instrument' => 'Cash']);
692 $this->assertArrayHasKey('payment_instrument', $contribution['values'][0]);
693 $this->assertEquals('Cash', $contribution['values'][0]['payment_instrument']);
694 $this->assertEquals(1, $contribution['count']);
695 $contribution = $this->callAPISuccess('contribution', 'get', [
696 'sequential' => 1,
697 'payment_instrument_id' => 5,
698 ]);
699 $this->assertArrayHasKey('payment_instrument', $contribution['values'][0]);
700 $this->assertEquals('EFT', $contribution['values'][0]['payment_instrument']);
701 $this->assertEquals(1, $contribution['count']);
702 $contribution = $this->callAPISuccess('contribution', 'get', [
703 'sequential' => 1,
704 'payment_instrument' => 'EFT',
705 ]);
706 $this->assertArrayHasKey('payment_instrument', $contribution['values'][0]);
707 $this->assertEquals('EFT', $contribution['values'][0]['payment_instrument']);
708 $this->assertEquals(1, $contribution['count']);
709 $contribution = $this->callAPISuccess('contribution', 'create', [
710 'id' => $contribution['id'],
711 'payment_instrument' => 'Credit Card',
712 ]);
713 $contribution = $this->callAPISuccess('contribution', 'get', ['sequential' => 1, 'id' => $contribution['id']]);
714 $this->assertArrayHasKey('payment_instrument', $contribution['values'][0]);
715 $this->assertEquals('Credit Card', $contribution['values'][0]['payment_instrument']);
716 $this->assertEquals(1, $contribution['count']);
717 }
718
719 /**
720 * CRM-16227 introduces invoice_id as a parameter.
721 *
722 * @throws \CRM_Core_Exception
723 */
724 public function testGetContributionByInvoice(): void {
725 $this->callAPISuccess('Contribution', 'create', array_merge($this->_params, ['invoice_id' => 'curly']));
726 $this->callAPISuccess('Contribution', 'create', array_merge($this->_params), ['invoice_id' => 'churlish']);
727 $this->callAPISuccessGetCount('Contribution', [], 2);
728 $this->callAPISuccessGetSingle('Contribution', ['invoice_id' => 'curly']);
729 // The following don't work. They are the format we are trying to introduce but although the form uses this format
730 // CRM_Contact_BAO_Query::convertFormValues puts them into the other format & the where only supports that.
731 // ideally the where clause would support this format (as it does on contact_BAO_Query) and those lines would
732 // come out of convertFormValues
733 // $this->callAPISuccessGetSingle('Contribution', array('invoice_id' => array('LIKE' => '%ish%')));
734 // $this->callAPISuccessGetSingle('Contribution', array('invoice_id' => array('NOT IN' => array('curly'))));
735 // $this->callAPISuccessGetCount('Contribution', array('invoice_id' => array('LIKE' => '%ly%')), 2);
736 // $this->callAPISuccessGetCount('Contribution', array('invoice_id' => array('IN' => array('curly', 'churlish'))),
737 // 2);
738 }
739
740 /**
741 * Check the credit note retrieval is case insensitive.
742 *
743 * @throws \CRM_Core_Exception
744 */
745 public function testGetCreditNoteCaseInsensitive(): void {
746 $this->contributionCreate(['contact_id' => $this->_individualId]);
747 $this->contributionCreate(['creditnote_id' => 'cN1234', 'contact_id' => $this->_individualId, 'invoice_id' => 91011, 'trxn_id' => 456]);
748 $contribution = $this->callAPISuccess('Contribution', 'getsingle', ['creditnote_id' => 'CN1234']);
749 $this->assertEquals('cN1234', $contribution['creditnote_id']);
750 }
751
752 /**
753 * Test retrieval by total_amount works.
754 *
755 * @throws \CRM_Core_Exception
756 */
757 public function testGetContributionByTotalAmount(): void {
758 $this->callAPISuccess('Contribution', 'create', array_merge($this->_params, ['total_amount' => '5']));
759 $this->callAPISuccess('Contribution', 'create', array_merge($this->_params, ['total_amount' => '10']));
760 $this->callAPISuccessGetCount('Contribution', ['total_amount' => 10], 1);
761 $this->callAPISuccessGetCount('Contribution', ['total_amount' => ['>' => 6]], 1);
762 $this->callAPISuccessGetCount('Contribution', ['total_amount' => ['>' => 0]], 2);
763 $this->callAPISuccessGetCount('Contribution', ['total_amount' => ['>' => -5]], 2);
764 $this->callAPISuccessGetCount('Contribution', ['total_amount' => ['<' => 0]], 0);
765 $this->callAPISuccessGetCount('Contribution', [], 2);
766 }
767
768 /**
769 * @dataProvider createLocalizedContributionDataProvider
770 *
771 * @param float|int|string $totalAmount
772 * @param string $decimalPoint
773 * @param string $thousandSeparator
774 * @param string $currency
775 * @param bool $expectedResult
776 *
777 * @throws \CRM_Core_Exception
778 */
779 public function testCreateLocalizedContribution($totalAmount, string $decimalPoint, string $thousandSeparator, string $currency, bool $expectedResult): void {
780 $this->setDefaultCurrency($currency);
781 $this->setMonetaryDecimalPoint($decimalPoint);
782 $this->setMonetaryThousandSeparator($thousandSeparator);
783
784 $_params = [
785 'contact_id' => $this->_individualId,
786 'receive_date' => '20120511',
787 'total_amount' => $totalAmount,
788 'financial_type_id' => $this->_financialTypeId,
789 'contribution_status_id' => 1,
790 ];
791
792 if ($expectedResult) {
793 $this->callAPISuccess('Contribution', 'create', $_params);
794 }
795 else {
796 $this->callAPIFailure('Contribution', 'create', $_params);
797 }
798 }
799
800 /**
801 * @return array
802 */
803 public function createLocalizedContributionDataProvider(): array {
804 return [
805 [10, '.', ',', 'USD', TRUE],
806 ['145.0E+3', '.', ',', 'USD', FALSE],
807 ['10', '.', ',', 'USD', TRUE],
808 [-10, '.', ',', 'USD', TRUE],
809 ['-10', '.', ',', 'USD', TRUE],
810 ['-10foo', '.', ',', 'USD', FALSE],
811 ['-10.0345619', '.', ',', 'USD', TRUE],
812 ['-10.010,4345619', '.', ',', 'USD', TRUE],
813 ['10.0104345619', '.', ',', 'USD', TRUE],
814 ['-0', '.', ',', 'USD', TRUE],
815 ['-.1', '.', ',', 'USD', TRUE],
816 ['.1', '.', ',', 'USD', TRUE],
817 // Test currency symbols too, default locale uses $, so if we wanted to test others we'd need to reconfigure locale
818 ['$1,234,567.89', '.', ',', 'USD', TRUE],
819 ['-$1,234,567.89', '.', ',', 'USD', TRUE],
820 ['$-1,234,567.89', '.', ',', 'USD', TRUE],
821 // This is the float format. Encapsulated in strings
822 ['1234567.89', '.', ',', 'USD', TRUE],
823 // This is the float format.
824 [1234567.89, '.', ',', 'USD', TRUE],
825 // Test EURO currency
826 ['€1,234,567.89', '.', ',', 'EUR', TRUE],
827 ['-€1,234,567.89', '.', ',', 'EUR', TRUE],
828 ['€-1,234,567.89', '.', ',', 'EUR', TRUE],
829 // This is the float format. Encapsulated in strings
830 ['1234567.89', '.', ',', 'EUR', TRUE],
831 // This is the float format.
832 [1234567.89, '.', ',', 'EUR', TRUE],
833 // Test Norwegian KR currency
834 ['kr1,234,567.89', '.', ',', 'NOK', TRUE],
835 ['kr 1,234,567.89', '.', ',', 'NOK', TRUE],
836 ['-kr1,234,567.89', '.', ',', 'NOK', TRUE],
837 ['-kr 1,234,567.89', '.', ',', 'NOK', TRUE],
838 ['kr-1,234,567.89', '.', ',', 'NOK', TRUE],
839 ['kr -1,234,567.89', '.', ',', 'NOK', TRUE],
840 // This is the float format. Encapsulated in strings
841 ['1234567.89', '.', ',', 'NOK', TRUE],
842 // This is the float format.
843 [1234567.89, '.', ',', 'NOK', TRUE],
844 // Test different localization options: , as decimal separator and dot as thousand separator
845 ['$1.234.567,89', ',', '.', 'USD', TRUE],
846 ['-$1.234.567,89', ',', '.', 'USD', TRUE],
847 ['$-1.234.567,89', ',', '.', 'USD', TRUE],
848 ['1.234.567,89', ',', '.', 'USD', TRUE],
849 // This is the float format. Encapsulated in strings
850 ['1234567.89', ',', '.', 'USD', TRUE],
851 // This is the float format.
852 [1234567.89, ',', '.', 'USD', TRUE],
853 ['$1,234,567.89', ',', '.', 'USD', FALSE],
854 ['-$1,234,567.89', ',', '.', 'USD', FALSE],
855 ['$-1,234,567.89', ',', '.', 'USD', FALSE],
856 // Now with a space as thousand separator
857 ['$1 234 567,89', ',', ' ', 'USD', TRUE],
858 ['-$1 234 567,89', ',', ' ', 'USD', TRUE],
859 ['$-1 234 567,89', ',', ' ', 'USD', TRUE],
860 ['1 234 567,89', ',', ' ', 'USD', TRUE],
861 // This is the float format. Encapsulated in strings
862 ['1234567.89', ',', ' ', 'USD', TRUE],
863 // This is the float format.
864 [1234567.89, ',', ' ', 'USD', TRUE],
865 ];
866 }
867
868 /**
869 * Create test with unique field name on source.
870 */
871 public function testCreateContributionSource() {
872
873 $params = [
874 'contact_id' => $this->_individualId,
875 'receive_date' => date('Ymd'),
876 'total_amount' => 100.00,
877 'financial_type_id' => $this->_financialTypeId,
878 'payment_instrument_id' => 1,
879 'non_deductible_amount' => 10.00,
880 'fee_amount' => 50.00,
881 'net_amount' => 90.00,
882 'trxn_id' => 12345,
883 'invoice_id' => 67890,
884 'contribution_source' => 'SSF',
885 'contribution_status_id' => 1,
886 ];
887
888 $contribution = $this->callAPISuccess('contribution', 'create', $params);
889 $this->assertEquals($contribution['values'][$contribution['id']]['total_amount'], 100.00);
890 $this->assertEquals($contribution['values'][$contribution['id']]['source'], 'SSF');
891 }
892
893 /**
894 * Create test with unique field name on source.
895 *
896 * @param string $thousandSeparator
897 * punctuation used to refer to thousands.
898 *
899 * @dataProvider getThousandSeparators
900 */
901 public function testCreateDefaultNow($thousandSeparator) {
902 $this->setCurrencySeparators($thousandSeparator);
903 $params = $this->_params;
904 unset($params['receive_date'], $params['net_amount']);
905
906 $params['total_amount'] = $this->formatMoneyInput(5000.77);
907 $params['fee_amount'] = $this->formatMoneyInput(.77);
908 $params['skipCleanMoney'] = FALSE;
909
910 $contribution = $this->callAPISuccess('contribution', 'create', $params);
911 $contribution = $this->callAPISuccessGetSingle('contribution', ['id' => $contribution['id']]);
912 $this->assertEquals(date('Y-m-d'), date('Y-m-d', strtotime($contribution['receive_date'])));
913 $this->assertEquals(5000.77, $contribution['total_amount'], 'failed to handle ' . $this->formatMoneyInput(5000.77));
914 $this->assertEquals(.77, $contribution['fee_amount']);
915 $this->assertEquals(5000, $contribution['net_amount']);
916 }
917
918 /**
919 * Create test with unique field name on source.
920 */
921 public function testCreateContributionNullOutThankyouDate() {
922
923 $params = $this->_params;
924 $params['thankyou_date'] = 'yesterday';
925
926 $contribution = $this->callAPISuccess('contribution', 'create', $params);
927 $contribution = $this->callAPISuccessGetSingle('contribution', ['id' => $contribution['id']]);
928 $this->assertEquals(date('Y-m-d', strtotime('yesterday')), date('Y-m-d', strtotime($contribution['thankyou_date'])));
929
930 $params['thankyou_date'] = 'null';
931 $contribution = $this->callAPISuccess('contribution', 'create', $params);
932 $this->assertTrue(empty($contribution['thankyou_date']));
933 }
934
935 /**
936 * Create test with unique field name on source.
937 */
938 public function testCreateContributionSourceInvalidContact() {
939
940 $params = [
941 'contact_id' => 999,
942 'receive_date' => date('Ymd'),
943 'total_amount' => 100.00,
944 'financial_type_id' => $this->_financialTypeId,
945 'payment_instrument_id' => 1,
946 'non_deductible_amount' => 10.00,
947 'fee_amount' => 50.00,
948 'net_amount' => 90.00,
949 'trxn_id' => 12345,
950 'invoice_id' => 67890,
951 'contribution_source' => 'SSF',
952 'contribution_status_id' => 1,
953 ];
954
955 $this->callAPIFailure('contribution', 'create', $params, 'contact_id is not valid : 999');
956 }
957
958 /**
959 * Test note created correctly.
960 *
961 * @throws \CRM_Core_Exception
962 */
963 public function testCreateContributionWithNote(): void {
964 $description = 'Demonstrates creating contribution with Note Entity.';
965 $subFile = 'ContributionCreateWithNote';
966 $params = [
967 'contact_id' => $this->_individualId,
968 'receive_date' => '2012-01-01',
969 'total_amount' => 100.00,
970 'financial_type_id' => $this->_financialTypeId,
971 'payment_instrument_id' => 1,
972 'non_deductible_amount' => 10.00,
973 'fee_amount' => 50.00,
974 'net_amount' => 90.00,
975 'trxn_id' => 12345,
976 'invoice_id' => 67890,
977 'source' => 'SSF',
978 'contribution_status_id' => 1,
979 'note' => 'my contribution note',
980 ];
981
982 $contribution = $this->callAPIAndDocument('contribution', 'create', $params, __FUNCTION__, __FILE__, $description, $subFile);
983 $result = $this->callAPISuccess('note', 'get', [
984 'entity_table' => 'civicrm_contribution',
985 'entity_id' => $contribution['id'],
986 'sequential' => 1,
987 ]);
988 $this->assertEquals('my contribution note', $result['values'][0]['note']);
989 $this->callAPISuccess('contribution', 'delete', ['id' => $contribution['id']]);
990 }
991
992 /**
993 * @throws \CRM_Core_Exception
994 */
995 public function testCreateContributionWithNoteUniqueNameAliases(): void {
996 $params = [
997 'contact_id' => $this->_individualId,
998 'receive_date' => '2012-01-01',
999 'total_amount' => 100.00,
1000 'financial_type_id' => $this->_financialTypeId,
1001 'payment_instrument_id' => 1,
1002 'non_deductible_amount' => 10.00,
1003 'fee_amount' => 50.00,
1004 'net_amount' => 90.00,
1005 'trxn_id' => 12345,
1006 'invoice_id' => 67890,
1007 'source' => 'SSF',
1008 'contribution_status_id' => 1,
1009 'contribution_note' => 'my contribution note',
1010 ];
1011
1012 $contribution = $this->callAPISuccess('contribution', 'create', $params);
1013 $result = $this->callAPISuccess('note', 'get', [
1014 'entity_table' => 'civicrm_contribution',
1015 'entity_id' => $contribution['id'],
1016 'sequential' => 1,
1017 ]);
1018 $this->assertEquals('my contribution note', $result['values'][0]['note']);
1019 $this->callAPISuccess('contribution', 'delete', ['id' => $contribution['id']]);
1020 }
1021
1022 /**
1023 * This is the test for creating soft credits.
1024 */
1025 public function testCreateContributionWithSoftCredit() {
1026 $description = "Demonstrates creating contribution with SoftCredit.";
1027 $subfile = "ContributionCreateWithSoftCredit";
1028 $contact2 = $this->callAPISuccess('Contact', 'create', [
1029 'display_name' => 'superman',
1030 'contact_type' => 'Individual',
1031 ]);
1032 $softParams = [
1033 'contact_id' => $contact2['id'],
1034 'amount' => 50,
1035 'soft_credit_type_id' => 3,
1036 ];
1037
1038 $params = $this->_params + ['soft_credit' => [1 => $softParams]];
1039 $contribution = $this->callAPIAndDocument('contribution', 'create', $params, __FUNCTION__, __FILE__, $description, $subfile);
1040 $result = $this->callAPISuccess('contribution', 'get', ['return' => 'soft_credit', 'sequential' => 1]);
1041
1042 $this->assertEquals($softParams['contact_id'], $result['values'][0]['soft_credit'][1]['contact_id']);
1043 $this->assertEquals($softParams['amount'], $result['values'][0]['soft_credit'][1]['amount']);
1044 $this->assertEquals($softParams['soft_credit_type_id'], $result['values'][0]['soft_credit'][1]['soft_credit_type']);
1045
1046 $this->callAPISuccess('contribution', 'delete', ['id' => $contribution['id']]);
1047 $this->callAPISuccess('contact', 'delete', ['id' => $contact2['id']]);
1048 }
1049
1050 public function testCreateContributionWithSoftCreditDefaults() {
1051 $description = "Demonstrates creating contribution with Soft Credit defaults for amount and type.";
1052 $subfile = "ContributionCreateWithSoftCreditDefaults";
1053 $contact2 = $this->callAPISuccess('Contact', 'create', [
1054 'display_name' => 'superman',
1055 'contact_type' => 'Individual',
1056 ]);
1057 $params = $this->_params + [
1058 'soft_credit_to' => $contact2['id'],
1059 ];
1060 $contribution = $this->callAPIAndDocument('contribution', 'create', $params, __FUNCTION__, __FILE__, $description, $subfile);
1061 $result = $this->callAPISuccess('contribution', 'get', ['return' => 'soft_credit', 'sequential' => 1]);
1062
1063 $this->assertEquals($contact2['id'], $result['values'][0]['soft_credit'][1]['contact_id']);
1064 // Default soft credit amount = contribution.total_amount
1065 $this->assertEquals($this->_params['total_amount'], $result['values'][0]['soft_credit'][1]['amount']);
1066 $this->assertEquals(CRM_Core_OptionGroup::getDefaultValue("soft_credit_type"), $result['values'][0]['soft_credit'][1]['soft_credit_type']);
1067
1068 $this->callAPISuccess('contribution', 'delete', ['id' => $contribution['id']]);
1069 $this->callAPISuccess('contact', 'delete', ['id' => $contact2['id']]);
1070 }
1071
1072 /**
1073 * Test creating contribution with Soft Credit by passing in honor_contact_id.
1074 */
1075 public function testCreateContributionWithHonoreeContact() {
1076 $description = "Demonstrates creating contribution with Soft Credit by passing in honor_contact_id.";
1077 $subfile = "ContributionCreateWithHonoreeContact";
1078 $contact2 = $this->callAPISuccess('Contact', 'create', [
1079 'display_name' => 'superman',
1080 'contact_type' => 'Individual',
1081 ]);
1082 $params = $this->_params + [
1083 'honor_contact_id' => $contact2['id'],
1084 ];
1085 $contribution = $this->callAPIAndDocument('contribution', 'create', $params, __FUNCTION__, __FILE__, $description, $subfile);
1086 $result = $this->callAPISuccess('contribution', 'get', ['return' => 'soft_credit', 'sequential' => 1]);
1087
1088 $this->assertEquals($contact2['id'], $result['values'][0]['soft_credit'][1]['contact_id']);
1089 // Default soft credit amount = contribution.total_amount
1090 // Legacy mode in create api (honor_contact_id param) uses the standard "In Honor of" soft credit type
1091 $this->assertEquals($this->_params['total_amount'], $result['values'][0]['soft_credit'][1]['amount']);
1092 $softCreditValueTypeID = $result['values'][0]['soft_credit'][1]['soft_credit_type'];
1093 $this->assertEquals('in_honor_of', CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_ContributionSoft', 'soft_credit_type_id', $softCreditValueTypeID));
1094
1095 $this->callAPISuccess('contribution', 'delete', ['id' => $contribution['id']]);
1096 $this->callAPISuccess('contact', 'delete', ['id' => $contact2['id']]);
1097 }
1098
1099 /**
1100 * Test using example code.
1101 */
1102 public function testContributionCreateExample() {
1103 //make sure at least on page exists since there is a truncate in tear down
1104 $this->callAPISuccess('contribution_page', 'create', $this->_pageParams);
1105 require_once 'api/v3/examples/Contribution/Create.ex.php';
1106 $result = contribution_create_example();
1107 $id = $result['id'];
1108 $expectedResult = contribution_create_expectedresult();
1109 $this->checkArrayEquals($expectedResult, $result);
1110 $this->contributionDelete($id);
1111 }
1112
1113 /**
1114 * Function tests that additional financial records are created when fee amount is recorded.
1115 */
1116 public function testCreateContributionWithFee() {
1117 $params = [
1118 'contact_id' => $this->_individualId,
1119 'receive_date' => '20120511',
1120 'total_amount' => 100.00,
1121 'fee_amount' => 50,
1122 'financial_type_id' => 1,
1123 'trxn_id' => 12345,
1124 'invoice_id' => 67890,
1125 'source' => 'SSF',
1126 'contribution_status_id' => 1,
1127 ];
1128
1129 $contribution = $this->callAPISuccess('contribution', 'create', $params);
1130 $this->assertEquals($contribution['values'][$contribution['id']]['contact_id'], $this->_individualId);
1131 $this->assertEquals($contribution['values'][$contribution['id']]['total_amount'], 100.00);
1132 $this->assertEquals($contribution['values'][$contribution['id']]['fee_amount'], 50.00);
1133 $this->assertEquals($contribution['values'][$contribution['id']]['net_amount'], 50.00);
1134 $this->assertEquals($contribution['values'][$contribution['id']]['financial_type_id'], 1);
1135 $this->assertEquals($contribution['values'][$contribution['id']]['trxn_id'], 12345);
1136 $this->assertEquals($contribution['values'][$contribution['id']]['invoice_id'], 67890);
1137 $this->assertEquals($contribution['values'][$contribution['id']]['source'], 'SSF');
1138 $this->assertEquals($contribution['values'][$contribution['id']]['contribution_status_id'], 1);
1139
1140 $lineItems = $this->callAPISuccess('line_item', 'get', [
1141
1142 'entity_id' => $contribution['id'],
1143 'entity_table' => 'civicrm_contribution',
1144 'sequential' => 1,
1145 ]);
1146 $this->assertEquals(1, $lineItems['count']);
1147 $this->assertEquals($contribution['id'], $lineItems['values'][0]['entity_id']);
1148 $this->assertEquals($contribution['id'], $lineItems['values'][0]['contribution_id']);
1149 $lineItems = $this->callAPISuccess('line_item', 'get', [
1150
1151 'entity_id' => $contribution['id'],
1152 'contribution_id' => $contribution['id'],
1153 'entity_table' => 'civicrm_contribution',
1154 'sequential' => 1,
1155 ]);
1156 $this->assertEquals(1, $lineItems['count']);
1157 $this->_checkFinancialRecords($contribution, 'feeAmount');
1158 }
1159
1160 /**
1161 * Function tests that additional financial records are created when online
1162 * contribution is created.
1163 *
1164 * @throws \CRM_Core_Exception
1165 * @throws \CiviCRM_API3_Exception
1166 */
1167 public function testCreateContributionOnline(): void {
1168 CRM_Financial_BAO_PaymentProcessor::create($this->_processorParams);
1169 $contributionPage = $this->callAPISuccess('contribution_page', 'create', $this->_pageParams);
1170 $this->assertAPISuccess($contributionPage);
1171 $params = [
1172 'contact_id' => $this->_individualId,
1173 'receive_date' => '20120511',
1174 'total_amount' => 100.00,
1175 'financial_type_id' => 1,
1176 'contribution_page_id' => $contributionPage['id'],
1177 'payment_processor' => $this->paymentProcessorID,
1178 'trxn_id' => 12345,
1179 'invoice_id' => 67890,
1180 'source' => 'SSF',
1181 'contribution_status_id' => 1,
1182
1183 ];
1184
1185 $contribution = $this->callAPISuccess('contribution', 'create', $params);
1186 $this->assertEquals($contribution['values'][$contribution['id']]['contact_id'], $this->_individualId);
1187 $this->assertEquals($contribution['values'][$contribution['id']]['total_amount'], 100.00);
1188 $this->assertEquals($contribution['values'][$contribution['id']]['financial_type_id'], 1);
1189 $this->assertEquals($contribution['values'][$contribution['id']]['trxn_id'], 12345);
1190 $this->assertEquals($contribution['values'][$contribution['id']]['invoice_id'], 67890);
1191 $this->assertEquals($contribution['values'][$contribution['id']]['source'], 'SSF');
1192 $this->assertEquals($contribution['values'][$contribution['id']]['contribution_status_id'], 1);
1193 $contribution['payment_instrument_id'] = $this->callAPISuccessGetValue('PaymentProcessor', [
1194 'id' => $this->paymentProcessorID,
1195 'return' => 'payment_instrument_id',
1196 ]);
1197 $this->_checkFinancialRecords($contribution, 'online');
1198 }
1199
1200 /**
1201 * Check handling of financial type.
1202 *
1203 * In the interests of removing financial type / contribution type checks from
1204 * legacy format function lets test that the api is doing this for us
1205 */
1206 public function testCreateInvalidFinancialType() {
1207 $params = $this->_params;
1208 $params['financial_type_id'] = 99999;
1209 $this->callAPIFailure($this->entity, 'create', $params, "'99999' is not a valid option for field financial_type_id");
1210 }
1211
1212 /**
1213 * Check handling of financial type.
1214 *
1215 * In the interests of removing financial type / contribution type checks from
1216 * legacy format function lets test that the api is doing this for us
1217 *
1218 * @throws \CRM_Core_Exception
1219 */
1220 public function testValidNamedFinancialType() {
1221 $params = $this->_params;
1222 $params['financial_type_id'] = 'Donation';
1223 $this->callAPISuccess($this->entity, 'create', $params);
1224 }
1225
1226 /**
1227 * Tests that additional financial records are created.
1228 *
1229 * Checks when online contribution with pay later option is created
1230 *
1231 * @throws \CRM_Core_Exception
1232 */
1233 public function testCreateContributionPayLaterOnline() {
1234 $this->_pageParams['is_pay_later'] = 1;
1235 $contributionPage = $this->callAPISuccess('contribution_page', 'create', $this->_pageParams);
1236 $this->assertAPISuccess($contributionPage);
1237 $params = [
1238 'contact_id' => $this->_individualId,
1239 'receive_date' => '20120511',
1240 'total_amount' => 100.00,
1241 'financial_type_id' => 1,
1242 'contribution_page_id' => $contributionPage['id'],
1243 'trxn_id' => 12345,
1244 'is_pay_later' => 1,
1245 'invoice_id' => 67890,
1246 'source' => 'SSF',
1247 'contribution_status_id' => 'Pending',
1248
1249 ];
1250
1251 $contribution = $this->callAPIAndDocument('Contribution', 'create', $params, __FUNCTION__, __FILE__);
1252 $contribution = $contribution['values'][$contribution['id']];
1253 $this->assertEquals($contribution['contact_id'], $this->_individualId);
1254 $this->assertEquals($contribution['total_amount'], 100.00);
1255 $this->assertEquals($contribution['financial_type_id'], 1);
1256 $this->assertEquals($contribution['trxn_id'], 12345);
1257 $this->assertEquals($contribution['invoice_id'], 67890);
1258 $this->assertEquals($contribution['source'], 'SSF');
1259 $this->assertEquals($contribution['contribution_status_id'], 2);
1260 $this->_checkFinancialRecords($contribution, 'payLater');
1261 }
1262
1263 /**
1264 * Function tests that additional financial records are created for online contribution with pending option.
1265 */
1266 public function testCreateContributionPendingOnline() {
1267 CRM_Financial_BAO_PaymentProcessor::create($this->_processorParams);
1268 $contributionPage = $this->callAPISuccess('contribution_page', 'create', $this->_pageParams);
1269 $this->assertAPISuccess($contributionPage);
1270 $params = [
1271 'contact_id' => $this->_individualId,
1272 'receive_date' => '20120511',
1273 'total_amount' => 100.00,
1274 'financial_type_id' => 1,
1275 'contribution_page_id' => $contributionPage['id'],
1276 'trxn_id' => 12345,
1277 'invoice_id' => 67890,
1278 'source' => 'SSF',
1279 'contribution_status_id' => 2,
1280 ];
1281
1282 $contribution = $this->callAPISuccess('contribution', 'create', $params);
1283 $this->assertEquals($contribution['values'][$contribution['id']]['contact_id'], $this->_individualId);
1284 $this->assertEquals($contribution['values'][$contribution['id']]['total_amount'], 100.00);
1285 $this->assertEquals($contribution['values'][$contribution['id']]['financial_type_id'], 1);
1286 $this->assertEquals($contribution['values'][$contribution['id']]['trxn_id'], 12345);
1287 $this->assertEquals($contribution['values'][$contribution['id']]['invoice_id'], 67890);
1288 $this->assertEquals($contribution['values'][$contribution['id']]['source'], 'SSF');
1289 $this->assertEquals($contribution['values'][$contribution['id']]['contribution_status_id'], 2);
1290 $this->_checkFinancialRecords($contribution, 'pending');
1291 }
1292
1293 /**
1294 * Test that BAO defaults work.
1295 *
1296 * @throws \CRM_Core_Exception
1297 */
1298 public function testCreateBAODefaults() {
1299 unset($this->_params['contribution_source_id'], $this->_params['payment_instrument_id']);
1300 $contribution = $this->callAPISuccess('Contribution', 'create', $this->_params);
1301 $contribution = $this->callAPISuccess('Contribution', 'getsingle', [
1302 'id' => $contribution['id'],
1303 'api.contribution.delete' => 1,
1304 ]);
1305 $this->assertEquals(1, $contribution['contribution_status_id']);
1306 $this->assertEquals('Check', $contribution['payment_instrument']);
1307 $this->callAPISuccessGetCount('Contribution', ['id' => $contribution['id']], 0);
1308 }
1309
1310 /**
1311 * Test that getsingle can be chained with delete.
1312 *
1313 * @throws CRM_Core_Exception
1314 */
1315 public function testDeleteChainedGetSingle() {
1316 $contribution = $this->callAPISuccess('Contribution', 'create', $this->_params);
1317 $contribution = $this->callAPISuccess('Contribution', 'getsingle', [
1318 'id' => $contribution['id'],
1319 'api.contribution.delete' => 1,
1320 ]);
1321 $this->callAPISuccessGetCount('Contribution', ['id' => $contribution['id']], 0);
1322 }
1323
1324 /**
1325 * Function tests that line items, financial records are updated when contribution amount is changed.
1326 *
1327 * @throws \CRM_Core_Exception
1328 */
1329 public function testCreateUpdateContributionChangeTotal() {
1330 $contribution = $this->callAPISuccess('contribution', 'create', $this->_params);
1331 $lineItems = $this->callAPISuccess('line_item', 'getvalue', [
1332
1333 'entity_id' => $contribution['id'],
1334 'entity_table' => 'civicrm_contribution',
1335 'sequential' => 1,
1336 'return' => 'line_total',
1337 ]);
1338 $this->assertEquals('100.00', $lineItems);
1339 $trxnAmount = $this->_getFinancialTrxnAmount($contribution['id']);
1340 // Financial trxn SUM = 100 + 5 (fee)
1341 $this->assertEquals('105.00', $trxnAmount);
1342 $newParams = [
1343
1344 'id' => $contribution['id'],
1345 'total_amount' => '125',
1346 ];
1347 $contribution = $this->callAPISuccess('Contribution', 'create', $newParams);
1348
1349 $lineItems = $this->callAPISuccess('line_item', 'getvalue', [
1350
1351 'entity_id' => $contribution['id'],
1352 'entity_table' => 'civicrm_contribution',
1353 'sequential' => 1,
1354 'return' => 'line_total',
1355 ]);
1356
1357 $this->assertEquals('125.00', $lineItems);
1358 $trxnAmount = $this->_getFinancialTrxnAmount($contribution['id']);
1359
1360 // Financial trxn SUM = 125 + 5 (fee).
1361 $this->assertEquals('130.00', $trxnAmount);
1362 $this->assertEquals('125.00', $this->_getFinancialItemAmount($contribution['id']));
1363 }
1364
1365 /**
1366 * Function tests that line items, financial records are updated when pay later contribution is received.
1367 */
1368 public function testCreateUpdateContributionPayLater() {
1369 $contribParams = [
1370 'contact_id' => $this->_individualId,
1371 'receive_date' => '2012-01-01',
1372 'total_amount' => 100.00,
1373 'financial_type_id' => $this->_financialTypeId,
1374 'payment_instrument_id' => 1,
1375 'contribution_status_id' => 2,
1376 'is_pay_later' => 1,
1377
1378 ];
1379 $contribution = $this->callAPISuccess('contribution', 'create', $contribParams);
1380
1381 $newParams = array_merge($contribParams, [
1382 'id' => $contribution['id'],
1383 'contribution_status_id' => 1,
1384 ]);
1385 $contribution = $this->callAPISuccess('contribution', 'create', $newParams);
1386 $contribution = $contribution['values'][$contribution['id']];
1387 $this->assertEquals($contribution['contribution_status_id'], '1');
1388 $this->_checkFinancialItem($contribution['id'], 'paylater');
1389 $this->_checkFinancialTrxn($contribution, 'payLater');
1390 }
1391
1392 /**
1393 * Function tests that financial records are updated when Payment Instrument is changed.
1394 */
1395 public function testCreateUpdateContributionPaymentInstrument() {
1396 $instrumentId = $this->_addPaymentInstrument();
1397 $contribParams = [
1398 'contact_id' => $this->_individualId,
1399 'total_amount' => 100.00,
1400 'financial_type_id' => $this->_financialTypeId,
1401 'payment_instrument_id' => 4,
1402 'contribution_status_id' => 1,
1403
1404 ];
1405 $contribution = $this->callAPISuccess('contribution', 'create', $contribParams);
1406
1407 $newParams = array_merge($contribParams, [
1408 'id' => $contribution['id'],
1409 'payment_instrument_id' => $instrumentId,
1410 ]);
1411 $contribution = $this->callAPISuccess('contribution', 'create', $newParams);
1412 $this->assertAPISuccess($contribution);
1413 $this->checkFinancialTrxnPaymentInstrumentChange($contribution['id'], 4, $instrumentId);
1414
1415 // cleanup - delete created payment instrument
1416 $this->_deletedAddedPaymentInstrument();
1417 }
1418
1419 /**
1420 * Function tests that financial records are updated when Payment Instrument is changed when amount is negative.
1421 */
1422 public function testCreateUpdateNegativeContributionPaymentInstrument() {
1423 $instrumentId = $this->_addPaymentInstrument();
1424 $contribParams = [
1425 'contact_id' => $this->_individualId,
1426 'total_amount' => -100.00,
1427 'financial_type_id' => $this->_financialTypeId,
1428 'payment_instrument_id' => 4,
1429 'contribution_status_id' => 1,
1430
1431 ];
1432 $contribution = $this->callAPISuccess('contribution', 'create', $contribParams);
1433
1434 $newParams = array_merge($contribParams, [
1435 'id' => $contribution['id'],
1436 'payment_instrument_id' => $instrumentId,
1437 ]);
1438 $contribution = $this->callAPISuccess('contribution', 'create', $newParams);
1439 $this->assertAPISuccess($contribution);
1440 $this->checkFinancialTrxnPaymentInstrumentChange($contribution['id'], 4, $instrumentId, -100);
1441
1442 // cleanup - delete created payment instrument
1443 $this->_deletedAddedPaymentInstrument();
1444 }
1445
1446 /**
1447 * Function tests that financial records are added when Contribution is Refunded.
1448 *
1449 * @throws \CRM_Core_Exception
1450 */
1451 public function testCreateUpdateContributionRefund() {
1452 $contributionParams = [
1453 'contact_id' => $this->_individualId,
1454 'receive_date' => '2012-01-01',
1455 'total_amount' => 100.00,
1456 'financial_type_id' => $this->_financialTypeId,
1457 'payment_instrument_id' => 4,
1458 'contribution_status_id' => 1,
1459 'trxn_id' => 'original_payment',
1460 ];
1461 $contribution = $this->callAPISuccess('Contribution', 'create', $contributionParams);
1462 $newParams = array_merge($contributionParams, [
1463 'id' => $contribution['id'],
1464 'contribution_status_id' => 'Refunded',
1465 'cancel_date' => '2015-01-01 09:00',
1466 'refund_trxn_id' => 'the refund',
1467 ]);
1468
1469 $contribution = $this->callAPISuccess('contribution', 'create', $newParams);
1470 $this->_checkFinancialTrxn($contribution, 'refund');
1471 $this->_checkFinancialItem($contribution['id'], 'refund');
1472 $this->assertEquals('original_payment', $this->callAPISuccessGetValue('Contribution', [
1473 'id' => $contribution['id'],
1474 'return' => 'trxn_id',
1475 ]));
1476 }
1477
1478 /**
1479 * Refund a contribution for a financial type with a contra account.
1480 *
1481 * CRM-17951 the contra account is a financial account with a relationship to a
1482 * financial type. It is not always configured but should be reflected
1483 * in the financial_trxn & financial_item table if it is.
1484 *
1485 * @throws \CRM_Core_Exception
1486 */
1487 public function testCreateUpdateChargebackContributionDefaultAccount() {
1488 $contribution = $this->callAPISuccess('Contribution', 'create', $this->_params);
1489 $this->callAPISuccess('Contribution', 'create', [
1490 'id' => $contribution['id'],
1491 'contribution_status_id' => 'Chargeback',
1492 ]);
1493 $this->callAPISuccessGetSingle('Contribution', ['contribution_status_id' => 'Chargeback']);
1494
1495 $lineItems = $this->callAPISuccessGetSingle('LineItem', [
1496 'contribution_id' => $contribution['id'],
1497 'api.FinancialItem.getsingle' => ['amount' => ['<' => 0]],
1498 ]);
1499 $this->assertEquals(1, $lineItems['api.FinancialItem.getsingle']['financial_account_id']);
1500 $this->callAPISuccessGetSingle('FinancialTrxn', [
1501 'total_amount' => -100,
1502 'status_id' => 'Chargeback',
1503 'to_financial_account_id' => 6,
1504 ]);
1505 }
1506
1507 /**
1508 * Refund a contribution for a financial type with a contra account.
1509 *
1510 * CRM-17951 the contra account is a financial account with a relationship to a
1511 * financial type. It is not always configured but should be reflected
1512 * in the financial_trxn & financial_item table if it is.
1513 *
1514 * @throws \CRM_Core_Exception
1515 */
1516 public function testCreateUpdateChargebackContributionCustomAccount() {
1517 $financialAccount = $this->callAPISuccess('FinancialAccount', 'create', [
1518 'name' => 'Chargeback Account',
1519 'is_active' => TRUE,
1520 ]);
1521
1522 $entityFinancialAccount = $this->callAPISuccess('EntityFinancialAccount', 'create', [
1523 'entity_id' => $this->_financialTypeId,
1524 'entity_table' => 'civicrm_financial_type',
1525 'account_relationship' => 'Chargeback Account is',
1526 'financial_account_id' => 'Chargeback Account',
1527 ]);
1528
1529 $contribution = $this->callAPISuccess('Contribution', 'create', $this->_params);
1530 $this->callAPISuccess('Contribution', 'create', [
1531 'id' => $contribution['id'],
1532 'contribution_status_id' => 'Chargeback',
1533 ]);
1534 $this->callAPISuccessGetSingle('Contribution', ['contribution_status_id' => 'Chargeback']);
1535
1536 $lineItems = $this->callAPISuccessGetSingle('LineItem', [
1537 'contribution_id' => $contribution['id'],
1538 'api.FinancialItem.getsingle' => ['amount' => ['<' => 0]],
1539 ]);
1540 $this->assertEquals($financialAccount['id'], $lineItems['api.FinancialItem.getsingle']['financial_account_id']);
1541
1542 $this->callAPISuccess('Contribution', 'delete', ['id' => $contribution['id']]);
1543 $this->callAPISuccess('EntityFinancialAccount', 'delete', ['id' => $entityFinancialAccount['id']]);
1544 $this->callAPISuccess('FinancialAccount', 'delete', ['id' => $financialAccount['id']]);
1545 }
1546
1547 /**
1548 * Refund a contribution for a financial type with a contra account.
1549 *
1550 * CRM-17951 the contra account is a financial account with a relationship to a
1551 * financial type. It is not always configured but should be reflected
1552 * in the financial_trxn & financial_item table if it is.
1553 */
1554 public function testCreateUpdateRefundContributionConfiguredContraAccount() {
1555 $financialAccount = $this->callAPISuccess('FinancialAccount', 'create', [
1556 'name' => 'Refund Account',
1557 'is_active' => TRUE,
1558 ]);
1559
1560 $entityFinancialAccount = $this->callAPISuccess('EntityFinancialAccount', 'create', [
1561 'entity_id' => $this->_financialTypeId,
1562 'entity_table' => 'civicrm_financial_type',
1563 'account_relationship' => 'Credit/Contra Revenue Account is',
1564 'financial_account_id' => 'Refund Account',
1565 ]);
1566
1567 $contribution = $this->callAPISuccess('Contribution', 'create', $this->_params);
1568 $this->callAPISuccess('Contribution', 'create', [
1569 'id' => $contribution['id'],
1570 'contribution_status_id' => 'Refunded',
1571 ]);
1572
1573 $lineItems = $this->callAPISuccessGetSingle('LineItem', [
1574 'contribution_id' => $contribution['id'],
1575 'api.FinancialItem.getsingle' => ['amount' => ['<' => 0]],
1576 ]);
1577 $this->assertEquals($financialAccount['id'], $lineItems['api.FinancialItem.getsingle']['financial_account_id']);
1578
1579 $this->callAPISuccess('Contribution', 'delete', ['id' => $contribution['id']]);
1580 $this->callAPISuccess('EntityFinancialAccount', 'delete', ['id' => $entityFinancialAccount['id']]);
1581 $this->callAPISuccess('FinancialAccount', 'delete', ['id' => $financialAccount['id']]);
1582 }
1583
1584 /**
1585 * Function tests that trxn_id is set when passed in.
1586 *
1587 * Here we ensure that the civicrm_financial_trxn.trxn_id & the civicrm_contribution.trxn_id are set
1588 * when trxn_id is passed in.
1589 */
1590 public function testCreateUpdateContributionRefundTrxnIDPassedIn() {
1591 $contributionParams = [
1592 'contact_id' => $this->_individualId,
1593 'receive_date' => '2012-01-01',
1594 'total_amount' => 100.00,
1595 'financial_type_id' => $this->_financialTypeId,
1596 'payment_instrument_id' => 4,
1597 'contribution_status_id' => 1,
1598 'trxn_id' => 'original_payment',
1599 ];
1600 $contribution = $this->callAPISuccess('contribution', 'create', $contributionParams);
1601 $newParams = array_merge($contributionParams, [
1602 'id' => $contribution['id'],
1603 'contribution_status_id' => 'Refunded',
1604 'cancel_date' => '2015-01-01 09:00',
1605 'trxn_id' => 'the refund',
1606 ]);
1607
1608 $contribution = $this->callAPISuccess('contribution', 'create', $newParams);
1609 $this->_checkFinancialTrxn($contribution, 'refund');
1610 $this->_checkFinancialItem($contribution['id'], 'refund');
1611 $this->assertEquals('the refund', $this->callAPISuccessGetValue('Contribution', [
1612 'id' => $contribution['id'],
1613 'return' => 'trxn_id',
1614 ]));
1615 }
1616
1617 /**
1618 * Function tests that trxn_id is set when passed in.
1619 *
1620 * Here we ensure that the civicrm_contribution.trxn_id is set
1621 * when trxn_id is passed in but if refund_trxn_id is different then that
1622 * is kept for the refund transaction.
1623 */
1624 public function testCreateUpdateContributionRefundRefundAndTrxnIDPassedIn() {
1625 $contributionParams = [
1626 'contact_id' => $this->_individualId,
1627 'receive_date' => '2012-01-01',
1628 'total_amount' => 100.00,
1629 'financial_type_id' => $this->_financialTypeId,
1630 'payment_instrument_id' => 4,
1631 'contribution_status_id' => 1,
1632 'trxn_id' => 'original_payment',
1633 ];
1634 $contribution = $this->callAPISuccess('contribution', 'create', $contributionParams);
1635 $newParams = array_merge($contributionParams, [
1636 'id' => $contribution['id'],
1637 'contribution_status_id' => 'Refunded',
1638 'cancel_date' => '2015-01-01 09:00',
1639 'trxn_id' => 'cont id',
1640 'refund_trxn_id' => 'the refund',
1641 ]);
1642
1643 $contribution = $this->callAPISuccess('contribution', 'create', $newParams);
1644 $this->_checkFinancialTrxn($contribution, 'refund');
1645 $this->_checkFinancialItem($contribution['id'], 'refund');
1646 $this->assertEquals('cont id', $this->callAPISuccessGetValue('Contribution', [
1647 'id' => $contribution['id'],
1648 'return' => 'trxn_id',
1649 ]));
1650 }
1651
1652 /**
1653 * Function tests that refund_trxn_id is set when passed in empty.
1654 *
1655 * Here we ensure that the civicrm_contribution.trxn_id is set
1656 * when trxn_id is passed in but if refund_trxn_id isset but empty then that
1657 * is kept for the refund transaction.
1658 */
1659 public function testCreateUpdateContributionRefundRefundNullTrxnIDPassedIn() {
1660 $contributionParams = [
1661 'contact_id' => $this->_individualId,
1662 'receive_date' => '2012-01-01',
1663 'total_amount' => 100.00,
1664 'financial_type_id' => $this->_financialTypeId,
1665 'payment_instrument_id' => 4,
1666 'contribution_status_id' => 1,
1667 'trxn_id' => 'original_payment',
1668 ];
1669 $contribution = $this->callAPISuccess('contribution', 'create', $contributionParams);
1670 $newParams = array_merge($contributionParams, [
1671 'id' => $contribution['id'],
1672 'contribution_status_id' => 'Refunded',
1673 'cancel_date' => '2015-01-01 09:00',
1674 'trxn_id' => 'cont id',
1675 'refund_trxn_id' => '',
1676 ]);
1677
1678 $contribution = $this->callAPISuccess('contribution', 'create', $newParams);
1679 $this->_checkFinancialTrxn($contribution, 'refund', NULL, ['trxn_id' => NULL]);
1680 $this->_checkFinancialItem($contribution['id'], 'refund');
1681 $this->assertEquals('cont id', $this->callAPISuccessGetValue('Contribution', [
1682 'id' => $contribution['id'],
1683 'return' => 'trxn_id',
1684 ]));
1685 }
1686
1687 /**
1688 * Function tests invalid contribution status change.
1689 */
1690 public function testCreateUpdateContributionInValidStatusChange() {
1691 $contribParams = [
1692 'contact_id' => 1,
1693 'receive_date' => '2012-01-01',
1694 'total_amount' => 100.00,
1695 'financial_type_id' => 1,
1696 'payment_instrument_id' => 1,
1697 'contribution_status_id' => 1,
1698 ];
1699 $contribution = $this->callAPISuccess('contribution', 'create', $contribParams);
1700 $newParams = array_merge($contribParams, [
1701 'id' => $contribution['id'],
1702 'contribution_status_id' => 2,
1703 ]);
1704 $this->callAPIFailure('contribution', 'create', $newParams, ts('Cannot change contribution status from Completed to Pending.'));
1705
1706 }
1707
1708 /**
1709 * Function tests that financial records are added when Pending Contribution is Canceled.
1710 *
1711 * @throws \CRM_Core_Exception
1712 */
1713 public function testCreateUpdateContributionCancelPending() {
1714 $contribParams = [
1715 'contact_id' => $this->_individualId,
1716 'receive_date' => '2012-01-01',
1717 'total_amount' => 100.00,
1718 'financial_type_id' => $this->_financialTypeId,
1719 'payment_instrument_id' => 1,
1720 'contribution_status_id' => 2,
1721 'is_pay_later' => 1,
1722
1723 ];
1724 $contribution = $this->callAPISuccess('contribution', 'create', $contribParams);
1725 $newParams = array_merge($contribParams, [
1726 'id' => $contribution['id'],
1727 'contribution_status_id' => 3,
1728 'cancel_date' => '2012-02-02 09:00',
1729 ]);
1730 //Check if trxn_date is same as cancel_date.
1731 $checkTrxnDate = [
1732 'trxn_date' => '2012-02-02 09:00:00',
1733 ];
1734 $contribution = $this->callAPISuccess('contribution', 'create', $newParams);
1735 $this->_checkFinancialTrxn($contribution, 'cancelPending', NULL, $checkTrxnDate);
1736 $this->_checkFinancialItem($contribution['id'], 'cancelPending');
1737 }
1738
1739 /**
1740 * Function tests that financial records are added when Financial Type is Changed.
1741 *
1742 * @throws \CRM_Core_Exception
1743 */
1744 public function testCreateUpdateContributionChangeFinancialType() {
1745 $contribParams = [
1746 'contact_id' => $this->_individualId,
1747 'receive_date' => '2012-01-01',
1748 'total_amount' => 100.00,
1749 'financial_type_id' => 1,
1750 'payment_instrument_id' => 1,
1751 'contribution_status_id' => 1,
1752
1753 ];
1754 $contribution = $this->callAPISuccess('contribution', 'create', $contribParams);
1755 $newParams = array_merge($contribParams, [
1756 'id' => $contribution['id'],
1757 'financial_type_id' => 3,
1758 ]);
1759 $contribution = $this->callAPISuccess('contribution', 'create', $newParams);
1760 $this->_checkFinancialTrxn($contribution, 'changeFinancial');
1761 $this->_checkFinancialItem($contribution['id'], 'changeFinancial');
1762 }
1763
1764 /**
1765 * Function tets that financial records are correctly added when financial type is changed
1766 *
1767 * @throws \CRM_Core_Exception
1768 */
1769 public function testCreateUpdateContributionWithFeeAmountChangeFinancialType() {
1770 $contribParams = [
1771 'contact_id' => $this->_individualId,
1772 'receive_date' => '2012-01-01',
1773 'total_amount' => 100.00,
1774 'fee_amount' => 0.57,
1775 'financial_type_id' => 1,
1776 'payment_instrument_id' => 1,
1777 'contribution_status_id' => 1,
1778
1779 ];
1780 $contribution = $this->callAPISuccess('contribution', 'create', $contribParams);
1781 $newParams = array_merge($contribParams, [
1782 'id' => $contribution['id'],
1783 'financial_type_id' => 3,
1784 ]);
1785 $contribution = $this->callAPISuccess('contribution', 'create', $newParams);
1786 $this->_checkFinancialTrxn($contribution, 'changeFinancial', NULL, ['fee_amount' => '-0.57', 'net_amount' => '-99.43']);
1787 $this->_checkFinancialItem($contribution['id'], 'changeFinancial');
1788 }
1789
1790 /**
1791 * Test that update does not change status id CRM-15105.
1792 */
1793 public function testCreateUpdateWithoutChangingPendingStatus() {
1794 $contribution = $this->callAPISuccess('contribution', 'create', array_merge($this->_params, ['contribution_status_id' => 2]));
1795 $this->callAPISuccess('contribution', 'create', ['id' => $contribution['id'], 'source' => 'new source']);
1796 $contribution = $this->callAPISuccess('contribution', 'getsingle', [
1797 'id' => $contribution['id'],
1798 'api.contribution.delete' => 1,
1799 ]);
1800 $this->assertEquals(2, $contribution['contribution_status_id']);
1801 }
1802
1803 /**
1804 * Test Updating a Contribution.
1805 *
1806 * CHANGE: we require the API to do an incremental update
1807 */
1808 public function testCreateUpdateContribution() {
1809 $contributionID = $this->contributionCreate([
1810 'contact_id' => $this->_individualId,
1811 'trxn_id' => 212355,
1812 'financial_type_id' => $this->_financialTypeId,
1813 'invoice_id' => 'old_invoice',
1814 ]);
1815 $old_params = [
1816 'contribution_id' => $contributionID,
1817 ];
1818 $original = $this->callAPISuccess('contribution', 'get', $old_params);
1819 $this->assertEquals($original['id'], $contributionID);
1820 //set up list of old params, verify
1821
1822 //This should not be required on update:
1823 $old_contact_id = $original['values'][$contributionID]['contact_id'];
1824 $old_payment_instrument = $original['values'][$contributionID]['instrument_id'];
1825 $old_fee_amount = $original['values'][$contributionID]['fee_amount'];
1826 $old_source = $original['values'][$contributionID]['contribution_source'];
1827
1828 $old_trxn_id = $original['values'][$contributionID]['trxn_id'];
1829 $old_invoice_id = $original['values'][$contributionID]['invoice_id'];
1830
1831 //check against values in CiviUnitTestCase::createContribution()
1832 $this->assertEquals($old_contact_id, $this->_individualId);
1833 $this->assertEquals($old_fee_amount, 5.00);
1834 $this->assertEquals($old_source, 'SSF');
1835 $this->assertEquals($old_trxn_id, 212355);
1836 $this->assertEquals($old_invoice_id, 'old_invoice');
1837 $params = [
1838 'id' => $contributionID,
1839 'contact_id' => $this->_individualId,
1840 'total_amount' => 105.00,
1841 'fee_amount' => 7.00,
1842 'financial_type_id' => $this->_financialTypeId,
1843 'non_deductible_amount' => 22.00,
1844 'contribution_status_id' => 1,
1845 'note' => 'Donating for Noble Cause',
1846 ];
1847
1848 $contribution = $this->callAPISuccess('contribution', 'create', $params);
1849
1850 $new_params = [
1851 'contribution_id' => $contribution['id'],
1852 ];
1853 $contribution = $this->callAPISuccessGetSingle('contribution', $new_params);
1854
1855 $this->assertEquals($contribution['contact_id'], $this->_individualId);
1856 $this->assertEquals($contribution['total_amount'], 105.00);
1857 $this->assertEquals($contribution['financial_type_id'], $this->_financialTypeId);
1858 $this->assertEquals($contribution['financial_type'], 'Donation');
1859 $this->assertEquals($contribution['instrument_id'], $old_payment_instrument);
1860 $this->assertEquals($contribution['non_deductible_amount'], 22.00);
1861 $this->assertEquals($contribution['fee_amount'], 7.00);
1862 $this->assertEquals($contribution['trxn_id'], $old_trxn_id);
1863 $this->assertEquals($contribution['invoice_id'], $old_invoice_id);
1864 $this->assertEquals($contribution['contribution_source'], $old_source);
1865 $this->assertEquals($contribution['contribution_status'], 'Completed');
1866
1867 $this->assertEquals($contribution['net_amount'], $contribution['total_amount'] - $contribution['fee_amount']);
1868
1869 $params = [
1870 'contribution_id' => $contributionID,
1871 ];
1872 $result = $this->callAPISuccess('contribution', 'delete', $params);
1873 $this->assertAPISuccess($result);
1874 }
1875
1876 /**
1877 * Check that net_amount is updated when a contribution is updated.
1878 *
1879 * Update fee amount AND total amount, just fee amount, just total amount
1880 * and neither to check that net_amount is keep updated.
1881 */
1882 public function testUpdateContributionNetAmountVariants() {
1883 $contributionID = $this->contributionCreate(['contact_id' => $this->individualCreate()]);
1884
1885 $this->callAPISuccess('Contribution', 'create', [
1886 'id' => $contributionID,
1887 'total_amount' => 90,
1888 'fee_amount' => 6,
1889 ]);
1890 $contribution = $this->callAPISuccessGetSingle('Contribution', [
1891 'id' => $contributionID,
1892 'return' => ['net_amount', 'fee_amount', 'total_amount'],
1893 ]);
1894 $this->assertEquals(6, $contribution['fee_amount']);
1895 $this->assertEquals(90, $contribution['total_amount']);
1896 $this->assertEquals(84, $contribution['net_amount']);
1897
1898 $this->callAPISuccess('Contribution', 'create', [
1899 'id' => $contributionID,
1900 'fee_amount' => 3,
1901 ]);
1902 $contribution = $this->callAPISuccessGetSingle('Contribution', [
1903 'id' => $contributionID,
1904 'return' => ['net_amount', 'fee_amount', 'total_amount'],
1905 ]);
1906 $this->assertEquals(3, $contribution['fee_amount']);
1907 $this->assertEquals(90, $contribution['total_amount']);
1908 $this->assertEquals(87, $contribution['net_amount']);
1909
1910 $this->callAPISuccess('Contribution', 'create', [
1911 'id' => $contributionID,
1912 'total_amount' => 200,
1913 ]);
1914 $contribution = $this->callAPISuccessGetSingle('Contribution', [
1915 'id' => $contributionID,
1916 'return' => ['net_amount', 'fee_amount', 'total_amount'],
1917 ]);
1918 $this->assertEquals(3, $contribution['fee_amount']);
1919 $this->assertEquals(200, $contribution['total_amount']);
1920 $this->assertEquals(197, $contribution['net_amount']);
1921
1922 $this->callAPISuccess('Contribution', 'create', [
1923 'id' => $contributionID,
1924 'payment_instrument' => 'Cash',
1925 ]);
1926 $contribution = $this->callAPISuccessGetSingle('Contribution', [
1927 'id' => $contributionID,
1928 'return' => ['net_amount', 'fee_amount', 'total_amount'],
1929 ]);
1930 $this->assertEquals(3, $contribution['fee_amount']);
1931 $this->assertEquals(200, $contribution['total_amount']);
1932 $this->assertEquals(197, $contribution['net_amount']);
1933 }
1934
1935 /**
1936 * Attempt (but fail) to delete a contribution without parameters.
1937 */
1938 public function testDeleteEmptyParamsContribution() {
1939 $params = [];
1940 $this->callAPIFailure('contribution', 'delete', $params);
1941 }
1942
1943 public function testDeleteWrongParamContribution() {
1944 $params = [
1945 'contribution_source' => 'SSF',
1946 ];
1947 $this->callAPIFailure('contribution', 'delete', $params);
1948 }
1949
1950 public function testDeleteContribution() {
1951 $contributionID = $this->contributionCreate([
1952 'contact_id' => $this->_individualId,
1953 'financial_type_id' => $this->_financialTypeId,
1954 ]);
1955 $params = [
1956 'id' => $contributionID,
1957 ];
1958 $this->callAPIAndDocument('contribution', 'delete', $params, __FUNCTION__, __FILE__);
1959 }
1960
1961 /**
1962 * Test civicrm_contribution_search with empty params.
1963 *
1964 * All available contributions expected.
1965 */
1966 public function testSearchEmptyParams() {
1967 $params = [];
1968
1969 $p = [
1970 'contact_id' => $this->_individualId,
1971 'receive_date' => date('Ymd'),
1972 'total_amount' => 100.00,
1973 'financial_type_id' => $this->_financialTypeId,
1974 'non_deductible_amount' => 10.00,
1975 'fee_amount' => 5.00,
1976 'net_amount' => 95.00,
1977 'trxn_id' => 23456,
1978 'invoice_id' => 78910,
1979 'source' => 'SSF',
1980 'contribution_status_id' => 1,
1981 ];
1982 $contribution = $this->callAPISuccess('contribution', 'create', $p);
1983
1984 $result = $this->callAPISuccess('contribution', 'get', $params);
1985 // We're taking the first element.
1986 $res = $result['values'][$contribution['id']];
1987
1988 $this->assertEquals($p['contact_id'], $res['contact_id']);
1989 $this->assertEquals($p['total_amount'], $res['total_amount']);
1990 $this->assertEquals($p['financial_type_id'], $res['financial_type_id']);
1991 $this->assertEquals($p['net_amount'], $res['net_amount']);
1992 $this->assertEquals($p['non_deductible_amount'], $res['non_deductible_amount']);
1993 $this->assertEquals($p['fee_amount'], $res['fee_amount']);
1994 $this->assertEquals($p['trxn_id'], $res['trxn_id']);
1995 $this->assertEquals($p['invoice_id'], $res['invoice_id']);
1996 $this->assertEquals($p['source'], $res['contribution_source']);
1997 // contribution_status_id = 1 => Completed
1998 $this->assertEquals('Completed', $res['contribution_status']);
1999
2000 $this->contributionDelete($contribution['id']);
2001 }
2002
2003 /**
2004 * Test civicrm_contribution_search. Success expected.
2005 */
2006 public function testSearch() {
2007 $p1 = [
2008 'contact_id' => $this->_individualId,
2009 'receive_date' => date('Ymd'),
2010 'total_amount' => 100.00,
2011 'financial_type_id' => $this->_financialTypeId,
2012 'non_deductible_amount' => 10.00,
2013 'contribution_status_id' => 1,
2014 ];
2015 $contribution1 = $this->callAPISuccess('contribution', 'create', $p1);
2016
2017 $p2 = [
2018 'contact_id' => $this->_individualId,
2019 'receive_date' => date('Ymd'),
2020 'total_amount' => 200.00,
2021 'financial_type_id' => $this->_financialTypeId,
2022 'non_deductible_amount' => 20.00,
2023 'trxn_id' => 5454565,
2024 'invoice_id' => 1212124,
2025 'fee_amount' => 50.00,
2026 'net_amount' => 60.00,
2027 'contribution_status_id' => 2,
2028 ];
2029 $contribution2 = $this->callAPISuccess('contribution', 'create', $p2);
2030
2031 $params = [
2032 'contribution_id' => $contribution2['id'],
2033 ];
2034 $result = $this->callAPISuccess('contribution', 'get', $params);
2035 $res = $result['values'][$contribution2['id']];
2036
2037 $this->assertEquals($p2['contact_id'], $res['contact_id']);
2038 $this->assertEquals($p2['total_amount'], $res['total_amount']);
2039 $this->assertEquals($p2['financial_type_id'], $res['financial_type_id']);
2040 $this->assertEquals($p2['net_amount'], $res['net_amount']);
2041 $this->assertEquals($p2['non_deductible_amount'], $res['non_deductible_amount']);
2042 $this->assertEquals($p2['fee_amount'], $res['fee_amount']);
2043 $this->assertEquals($p2['trxn_id'], $res['trxn_id']);
2044 $this->assertEquals($p2['invoice_id'], $res['invoice_id']);
2045 $this->assertEquals(CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Pending'), $res['contribution_status_id']);
2046
2047 $this->contributionDelete($contribution1['id']);
2048 $this->contributionDelete($contribution2['id']);
2049 }
2050
2051 /**
2052 * Test completing a transaction via the API.
2053 *
2054 * Note that we are creating a logged in user because email goes out from
2055 * that person
2056 */
2057 public function testCompleteTransaction() {
2058 $mut = new CiviMailUtils($this, TRUE);
2059 $this->swapMessageTemplateForTestTemplate();
2060 $this->createLoggedInUser();
2061 $params = array_merge($this->_params, ['contribution_status_id' => 2]);
2062 $contribution = $this->callAPISuccess('contribution', 'create', $params);
2063 $this->callAPISuccess('contribution', 'completetransaction', [
2064 'id' => $contribution['id'],
2065 ]);
2066 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['id' => $contribution['id']]);
2067 $this->assertEquals('SSF', $contribution['contribution_source']);
2068 $this->assertEquals('Completed', $contribution['contribution_status']);
2069 $this->assertEquals(date('Y-m-d'), date('Y-m-d', strtotime($contribution['receipt_date'])));
2070 $mut->checkMailLog([
2071 'email:::anthony_anderson@civicrm.org',
2072 'is_monetary:::1',
2073 'amount:::100.00',
2074 'currency:::USD',
2075 'receive_date:::' . date('Ymd', strtotime($contribution['receive_date'])),
2076 "receipt_date:::\n",
2077 'contributeMode:::notify',
2078 'title:::Contribution',
2079 'displayName:::Mr. Anthony Anderson II',
2080 'contributionStatus:::Completed',
2081 ]);
2082 $mut->stop();
2083 $this->revertTemplateToReservedTemplate();
2084 }
2085
2086 /**
2087 * Test completing a transaction via the API with a non-USD transaction.
2088 */
2089 public function testCompleteTransactionEuro() {
2090 $mut = new CiviMailUtils($this, TRUE);
2091 $this->swapMessageTemplateForTestTemplate();
2092 $this->createLoggedInUser();
2093 $params = array_merge($this->_params, ['contribution_status_id' => 2, 'currency' => 'EUR']);
2094 $contribution = $this->callAPISuccess('contribution', 'create', $params);
2095
2096 $this->callAPISuccess('contribution', 'completetransaction', [
2097 'id' => $contribution['id'],
2098 ]);
2099
2100 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['id' => $contribution['id']]);
2101 $this->assertEquals('SSF', $contribution['contribution_source']);
2102 $this->assertEquals('Completed', $contribution['contribution_status']);
2103 $this->assertEquals(date('Y-m-d'), date('Y-m-d', strtotime($contribution['receipt_date'])));
2104
2105 $entityFinancialTransactions = $this->getFinancialTransactionsForContribution($contribution['id']);
2106 $entityFinancialTransaction = reset($entityFinancialTransactions);
2107 $financialTrxn = $this->callAPISuccessGetSingle('FinancialTrxn', ['id' => $entityFinancialTransaction['financial_trxn_id']]);
2108 $this->assertEquals('EUR', $financialTrxn['currency']);
2109
2110 $mut->checkMailLog([
2111 'email:::anthony_anderson@civicrm.org',
2112 'is_monetary:::1',
2113 'amount:::100.00',
2114 'currency:::EUR',
2115 'receive_date:::' . date('Ymd', strtotime($contribution['receive_date'])),
2116 "receipt_date:::\n",
2117 'contributeMode:::notify',
2118 'title:::Contribution',
2119 'displayName:::Mr. Anthony Anderson II',
2120 'contributionStatus:::Completed',
2121 ]);
2122 $mut->stop();
2123 $this->revertTemplateToReservedTemplate();
2124 }
2125
2126 /**
2127 * Test to ensure mail is sent for pay later
2128 *
2129 * @throws \CRM_Core_Exception
2130 * @throws \API_Exception
2131 */
2132 public function testPayLater(): void {
2133 $mut = new CiviMailUtils($this, TRUE);
2134 $this->swapMessageTemplateForTestTemplate();
2135 $this->createLoggedInUser();
2136 $contributionPageID = $this->createQuickConfigContributionPage();
2137
2138 $params = [
2139 'id' => $contributionPageID,
2140 'price_' . $this->ids['PriceField']['basic'] => $this->ids['PriceFieldValue']['basic'],
2141 'contact_id' => $this->_individualId,
2142 'email-5' => 'anthony_anderson@civicrm.org',
2143 'payment_processor_id' => 0,
2144 'currencyID' => 'USD',
2145 'is_pay_later' => 1,
2146 'invoiceID' => 'f28e1ddc86f8c4a0ff5bcf46393e4bc8',
2147 'description' => 'Online Contribution: Help Support CiviCRM!',
2148 ];
2149 $this->callAPISuccess('ContributionPage', 'submit', $params);
2150
2151 $mut->checkMailLog([
2152 'is_pay_later:::1',
2153 'email:::anthony_anderson@civicrm.org',
2154 'pay_later_receipt:::This is a pay later receipt',
2155 'displayName:::Mr. Anthony Anderson II',
2156 'contributionPageId:::' . $contributionPageID,
2157 'title:::Test Contribution Page',
2158 'amount:::100',
2159 ]);
2160 $mut->stop();
2161 $this->revertTemplateToReservedTemplate();
2162 }
2163
2164 /**
2165 * Test to check whether contact billing address is used when no contribution address
2166 */
2167 public function testBillingAddress() {
2168 $mut = new CiviMailUtils($this, TRUE);
2169 $this->swapMessageTemplateForTestTemplate();
2170 $this->createLoggedInUser();
2171
2172 //Scenario 1: When Contact don't have any address
2173 $params = array_merge($this->_params, ['contribution_status_id' => 2]);
2174 $contribution = $this->callAPISuccess('contribution', 'create', $params);
2175 $this->callAPISuccess('contribution', 'completetransaction', [
2176 'id' => $contribution['id'],
2177 ]);
2178 $mut->checkMailLog([
2179 'address:::',
2180 ]);
2181
2182 // Scenario 2: Contribution using address
2183 $address = $this->callAPISuccess('address', 'create', [
2184 'street_address' => 'contribution billing st',
2185 'location_type_id' => 2,
2186 'contact_id' => $this->_params['contact_id'],
2187 ]);
2188 $params = array_merge($this->_params, [
2189 'contribution_status_id' => 2,
2190 'address_id' => $address['id'],
2191 ]
2192 );
2193 $contribution = $this->callAPISuccess('contribution', 'create', $params);
2194 $this->callAPISuccess('contribution', 'completetransaction', [
2195 'id' => $contribution['id'],
2196 ]);
2197 $mut->checkMailLog([
2198 'address:::contribution billing st',
2199 ]);
2200
2201 // Scenario 3: Contribution wtth no address but contact has a billing address
2202 $this->callAPISuccess('address', 'create', [
2203 'id' => $address['id'],
2204 'street_address' => 'is billing st',
2205 'contact_id' => $this->_params['contact_id'],
2206 ]);
2207 $params = array_merge($this->_params, ['contribution_status_id' => 2]);
2208 $contribution = $this->callAPISuccess('contribution', 'create', $params);
2209 $this->callAPISuccess('contribution', 'completetransaction', [
2210 'id' => $contribution['id'],
2211 ]);
2212 $mut->checkMailLog([
2213 'address:::is billing st',
2214 ]);
2215
2216 $mut->stop();
2217 $this->revertTemplateToReservedTemplate();
2218 }
2219
2220 /**
2221 * Test completing a transaction via the API.
2222 *
2223 * Note that we are creating a logged in user because email goes out from
2224 * that person
2225 */
2226 public function testCompleteTransactionFeeAmount() {
2227 $this->createLoggedInUser();
2228 $params = array_merge($this->_params, ['contribution_status_id' => 2]);
2229 $contribution = $this->callAPISuccess('contribution', 'create', $params);
2230 $this->callAPISuccess('contribution', 'completetransaction', [
2231 'id' => $contribution['id'],
2232 'fee_amount' => '.56',
2233 'trxn_id' => '7778888',
2234 ]);
2235 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['id' => $contribution['id'], 'sequential' => 1]);
2236 $this->assertEquals('Completed', $contribution['contribution_status']);
2237 $this->assertEquals('7778888', $contribution['trxn_id']);
2238 $this->assertEquals('0.56', $contribution['fee_amount']);
2239 $this->assertEquals('99.44', $contribution['net_amount']);
2240 }
2241
2242 /**
2243 * CRM-19126 Add test to verify when complete transaction is called tax
2244 * amount is not changed.
2245 *
2246 * We start of with a pending contribution.
2247 * - total_amount (input) = 100
2248 * - total_amount post save (based on tax being added) = 105
2249 * - net_amount = 95
2250 * - fee_amount = 5
2251 * - non_deductible_amount = 10
2252 * - tax rate = 5%
2253 * - tax_amount = 5
2254 * - sum of (calculated) line items = 105
2255 *
2256 * Note the fee_amount should really be set when the payment is received
2257 * and whatever the non_deductible amount is, it is ignored.
2258 *
2259 * The fee amount when the payment comes in is 6 rather than 5. The net_amount
2260 * and fee_amount should change, but not the total_amount or
2261 * the line items.
2262 *
2263 * @param string $thousandSeparator
2264 * punctuation used to refer to thousands.
2265 *
2266 * @dataProvider getThousandSeparators
2267 * @throws \CRM_Core_Exception
2268 */
2269 public function testCheckTaxAmount(string $thousandSeparator): void {
2270 $this->setCurrencySeparators($thousandSeparator);
2271 $this->createFinancialTypeWithSalesTax();
2272 $financialTypeId = $this->ids['FinancialType']['taxable'];
2273
2274 $contributionID = $this->callAPISuccess('Order', 'create',
2275 array_merge($this->_params, ['financial_type_id' => $financialTypeId])
2276 )['id'];
2277 $contributionPrePayment = $this->callAPISuccessGetSingle('Contribution', ['id' => $contributionID, 'return' => ['tax_amount', 'total_amount']]);
2278 $this->validateAllContributions();
2279 $this->callAPISuccess('Contribution', 'completetransaction', [
2280 'id' => $contributionID,
2281 'trxn_id' => '777788888',
2282 'fee_amount' => '6.00',
2283 'sequential' => 1,
2284 ]);
2285 $contributionPostPayment = $this->callAPISuccessGetSingle('Contribution', ['id' => $contributionID, 'return' => ['tax_amount', 'fee_amount', 'net_amount']]);
2286 $this->assertEquals(5, $contributionPrePayment['tax_amount']);
2287 $this->assertEquals(5, $contributionPostPayment['tax_amount']);
2288 $this->assertEquals('6.00', $contributionPostPayment['fee_amount']);
2289 $this->assertEquals('99.00', $contributionPostPayment['net_amount']);
2290 $this->validateAllContributions();
2291 $this->validateAllPayments();
2292 }
2293
2294 /**
2295 * Test repeat contribution successfully creates line item.
2296 *
2297 * @throws \CRM_Core_Exception
2298 */
2299 public function testRepeatTransaction(): void {
2300 $originalContribution = $this->setUpRepeatTransaction([], 'single');
2301 $this->callAPISuccess('contribution', 'repeattransaction', [
2302 'original_contribution_id' => $originalContribution['id'],
2303 'contribution_status_id' => 'Completed',
2304 'trxn_id' => 4567,
2305 ]);
2306 $lineItemParams = [
2307 'entity_id' => $originalContribution['id'],
2308 'sequential' => 1,
2309 'return' => [
2310 'entity_table',
2311 'qty',
2312 'unit_price',
2313 'line_total',
2314 'label',
2315 'financial_type_id',
2316 'deductible_amount',
2317 'price_field_value_id',
2318 'price_field_id',
2319 ],
2320 ];
2321 $lineItem1 = $this->callAPISuccess('line_item', 'get', array_merge($lineItemParams, [
2322 'entity_id' => $originalContribution['id'],
2323 ]));
2324 $lineItem2 = $this->callAPISuccess('line_item', 'get', array_merge($lineItemParams, [
2325 'entity_id' => $originalContribution['id'] + 1,
2326 ]));
2327 unset($lineItem1['values'][0]['id'], $lineItem1['values'][0]['entity_id']);
2328 unset($lineItem2['values'][0]['id'], $lineItem2['values'][0]['entity_id']);
2329 $this->assertEquals($lineItem1['values'][0], $lineItem2['values'][0]);
2330 $this->_checkFinancialRecords([
2331 'id' => $originalContribution['id'] + 1,
2332 'payment_instrument_id' => $this->callAPISuccessGetValue('PaymentProcessor', [
2333 'id' => $originalContribution['payment_processor_id'],
2334 'return' => 'payment_instrument_id',
2335 ]),
2336 ], 'online');
2337 }
2338
2339 /**
2340 * Test custom data is copied over from the template transaction.
2341 *
2342 * (Over time various discussions have deemed this to be the most recent one, allowing
2343 * users to alter custom data going forwards. This is implemented for line items already.
2344 *
2345 * @throws \API_Exception
2346 * @throws \CRM_Core_Exception
2347 */
2348 public function testRepeatTransactionWithCustomData(): void {
2349 $this->createCustomGroupWithFieldOfType(['extends' => 'Contribution', 'name' => 'Repeat'], 'text');
2350 $originalContribution = $this->setUpRepeatTransaction([], 'single', [$this->getCustomFieldName('text') => 'first']);
2351 $this->callAPISuccess('contribution', 'repeattransaction', [
2352 'contribution_recur_id' => $originalContribution['contribution_recur_id'],
2353 'contribution_status_id' => 'Completed',
2354 'trxn_id' => 'my_trxn',
2355 ]);
2356
2357 $contribution = Contribution::get()
2358 ->addWhere('trxn_id', '=', 'my_trxn')
2359 ->addSelect('Custom_Group.Enter_text_here')
2360 ->addSelect('id')
2361 ->execute()->first();
2362 $this->assertEquals('first', $contribution['Custom_Group.Enter_text_here']);
2363
2364 Contribution::update()->setValues(['Custom_Group.Enter_text_here' => 'second'])->addWhere('id', '=', $contribution['id'])->execute();
2365
2366 $this->callAPISuccess('contribution', 'repeattransaction', [
2367 'original_contribution_id' => $originalContribution['id'],
2368 'contribution_status_id' => 'Completed',
2369 'trxn_id' => 'number_3',
2370 ]);
2371
2372 $contribution = Contribution::get()
2373 ->addWhere('trxn_id', '=', 'number_3')
2374 ->setSelect(['id', 'Custom_Group.Enter_text_here'])
2375 ->execute()->first();
2376 $this->assertEquals('second', $contribution['Custom_Group.Enter_text_here']);
2377 }
2378
2379 /**
2380 * Test repeat contribution successfully creates line items (plural).
2381 *
2382 * @throws \CRM_Core_Exception
2383 */
2384 public function testRepeatTransactionLineItems(): void {
2385 // @todo - figure out why this test is not valid.
2386 $this->isValidateFinancialsOnPostAssert = FALSE;
2387 // CRM-19309
2388 $originalContribution = $this->setUpRepeatTransaction([], 'multiple');
2389 $this->callAPISuccess('contribution', 'repeattransaction', [
2390 'original_contribution_id' => $originalContribution['id'],
2391 'contribution_status_id' => 'Completed',
2392 'trxn_id' => 1234,
2393 ]);
2394
2395 $lineItemParams = [
2396 'entity_id' => $originalContribution['id'],
2397 'sequential' => 1,
2398 'return' => [
2399 'entity_table',
2400 'qty',
2401 'unit_price',
2402 'line_total',
2403 'label',
2404 'financial_type_id',
2405 'deductible_amount',
2406 'price_field_value_id',
2407 'price_field_id',
2408 ],
2409 ];
2410 $lineItem1 = $this->callAPISuccess('line_item', 'get', array_merge($lineItemParams, [
2411 'entity_id' => $originalContribution['id'],
2412 ]));
2413 $lineItem2 = $this->callAPISuccess('line_item', 'get', array_merge($lineItemParams, [
2414 'entity_id' => $originalContribution['id'] + 1,
2415 ]));
2416
2417 // unset id and entity_id for all of them to be able to compare the lineItems:
2418 unset($lineItem1['values'][0]['id'], $lineItem1['values'][0]['entity_id']);
2419 unset($lineItem2['values'][0]['id'], $lineItem2['values'][0]['entity_id']);
2420 $this->assertEquals($lineItem1['values'][0], $lineItem2['values'][0]);
2421
2422 unset($lineItem1['values'][1]['id'], $lineItem1['values'][1]['entity_id']);
2423 unset($lineItem2['values'][1]['id'], $lineItem2['values'][1]['entity_id']);
2424 $this->assertEquals($lineItem1['values'][1], $lineItem2['values'][1]);
2425
2426 // CRM-19309 so in future we also want to:
2427 // check that financial_line_items have been created for entity_id 3 and 4;
2428
2429 $this->callAPISuccessGetCount('FinancialItem', ['description' => 'Sales Tax', 'amount' => 0], 0);
2430 }
2431
2432 /**
2433 * Test repeat contribution successfully creates is_test transaction.
2434 *
2435 * @throws \CRM_Core_Exception
2436 */
2437 public function testRepeatTransactionIsTest(): void {
2438 $this->_params['is_test'] = 1;
2439 $originalContribution = $this->setUpRepeatTransaction(['is_test' => 1], 'single');
2440
2441 $this->callAPISuccess('contribution', 'repeattransaction', [
2442 'original_contribution_id' => $originalContribution['id'],
2443 'contribution_status_id' => 'Completed',
2444 'trxn_id' => '1234',
2445 ]);
2446 $this->callAPISuccessGetCount('Contribution', ['contribution_test' => 1], 2);
2447 }
2448
2449 /**
2450 * Test repeat contribution passed in status.
2451 *
2452 * @throws \CRM_Core_Exception
2453 */
2454 public function testRepeatTransactionPassedInStatus(): void {
2455 $originalContribution = $this->setUpRepeatTransaction([], 'single');
2456
2457 $this->callAPISuccess('contribution', 'repeattransaction', [
2458 'original_contribution_id' => $originalContribution['id'],
2459 'contribution_status_id' => 'Pending',
2460 'trxn_id' => 1234,
2461 ]);
2462 $this->callAPISuccessGetCount('Contribution', ['contribution_status_id' => 2], 1);
2463 }
2464
2465 /**
2466 * Test repeat contribution accepts recur_id instead of
2467 * original_contribution_id.
2468 *
2469 * @throws \CRM_Core_Exception
2470 */
2471 public function testRepeatTransactionAcceptRecurID(): void {
2472 $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', [
2473 'contact_id' => $this->_individualId,
2474 'installments' => '12',
2475 'frequency_interval' => '1',
2476 'amount' => '100',
2477 'contribution_status_id' => 1,
2478 'start_date' => '2012-01-01 00:00:00',
2479 'currency' => 'USD',
2480 'frequency_unit' => 'month',
2481 'payment_processor_id' => $this->paymentProcessorID,
2482 ]);
2483 $this->callAPISuccess('contribution', 'create', array_merge(
2484 $this->_params,
2485 ['contribution_recur_id' => $contributionRecur['id']])
2486 );
2487
2488 $this->callAPISuccess('contribution', 'repeattransaction', [
2489 'contribution_recur_id' => $contributionRecur['id'],
2490 'contribution_status_id' => 'Completed',
2491 'trxn_id' => 1234,
2492 ]);
2493
2494 }
2495
2496 /**
2497 * CRM-19873 Test repeattransaction if contribution_recur_id is a test.
2498 *
2499 * @throws \CRM_Core_Exception
2500 */
2501 public function testRepeatTransactionTestRecurId() {
2502 $contributionRecur = $this->callAPISuccess('ContributionRecur', 'create', [
2503 'contact_id' => $this->_individualId,
2504 'frequency_interval' => '1',
2505 'amount' => '1.00',
2506 'contribution_status_id' => 1,
2507 'start_date' => '2017-01-01 00:00:00',
2508 'currency' => 'USD',
2509 'frequency_unit' => 'month',
2510 'payment_processor_id' => $this->paymentProcessorID,
2511 'is_test' => 1,
2512 ]);
2513 $this->callAPISuccess('contribution', 'create', array_merge(
2514 $this->_params,
2515 [
2516 'contribution_recur_id' => $contributionRecur['id'],
2517 'is_test' => 1,
2518 ])
2519 );
2520
2521 $repeatedContribution = $this->callAPISuccess('contribution', 'repeattransaction', [
2522 'contribution_recur_id' => $contributionRecur['id'],
2523 'contribution_status_id' => 'Completed',
2524 'trxn_id' => 'magic_number',
2525 ]);
2526
2527 $this->assertEquals($contributionRecur['values'][1]['is_test'], $repeatedContribution['values'][2]['is_test']);
2528 }
2529
2530 /**
2531 * CRM-19945 Tests that Contribute.repeattransaction renews a membership when contribution status=Completed
2532 *
2533 * @throws \CRM_Core_Exception
2534 */
2535 public function testRepeatTransactionMembershipRenewCompletedContribution(): void {
2536 [$originalContribution, $membership] = $this->setUpAutoRenewMembership();
2537
2538 $this->callAPISuccess('Contribution', 'repeattransaction', [
2539 'contribution_recur_id' => $originalContribution['values'][1]['contribution_recur_id'],
2540 'contribution_status_id' => 'Failed',
2541 ]);
2542
2543 $this->callAPISuccess('membership', 'create', [
2544 'id' => $membership['id'],
2545 'end_date' => 'yesterday',
2546 'status_id' => 'Expired',
2547 ]);
2548
2549 $contribution = $this->callAPISuccess('Contribution', 'repeattransaction', [
2550 'contribution_recur_id' => $originalContribution['values'][1]['contribution_recur_id'],
2551 'contribution_status_id' => 'Completed',
2552 'trxn_id' => 'bobsled',
2553 ]);
2554
2555 $membershipStatusId = $this->callAPISuccess('membership', 'getvalue', [
2556 'id' => $membership['id'],
2557 'return' => 'status_id',
2558 ]);
2559
2560 $membership = $this->callAPISuccess('membership', 'get', [
2561 'id' => $membership['id'],
2562 ]);
2563
2564 $this->assertEquals('New', CRM_Core_PseudoConstant::getName('CRM_Member_BAO_Membership', 'status_id', $membershipStatusId));
2565
2566 $lineItem = $this->callAPISuccessGetSingle('LineItem', ['contribution_id' => $contribution['id']]);
2567 $this->assertEquals('civicrm_membership', $lineItem['entity_table']);
2568 $this->callAPISuccessGetCount('MembershipPayment', ['membership_id' => $membership['id']]);
2569 }
2570
2571 /**
2572 * This is one of those tests that locks in existing behaviour.
2573 *
2574 * I feel like correct behaviour is arguable & has been discussed in the past. However, if the membership has
2575 * a date which says it should be expired then the result of repeattransaction is to push that date
2576 * to be one membership term from 'now' with status 'new'.
2577 */
2578 public function testRepeattransactionRenewMembershipOldMembership() {
2579 $entities = $this->setUpAutoRenewMembership();
2580 $newStatusID = CRM_Core_PseudoConstant::getKey('CRM_Member_BAO_Membership', 'status_id', 'New');
2581 $membership = $this->callAPISuccess('Membership', 'create', [
2582 'id' => $entities[1]['id'],
2583 'join_date' => '4 months ago',
2584 'start_date' => '3 months ago',
2585 'end_date' => '2 months ago',
2586 ]);
2587 $membership = $membership['values'][$membership['id']];
2588
2589 // This status does not appear to be calculated at all and is set to 'new'. Feels like a bug.
2590 $this->assertEquals($newStatusID, $membership['status_id']);
2591
2592 // So it seems renewing this expired membership results in it's new status being current and it being pushed to a future date
2593 $this->callAPISuccess('Contribution', 'repeattransaction', ['original_contribution_id' => $entities[0]['id'], 'contribution_status_id' => 'Completed']);
2594 $membership = $this->callAPISuccessGetSingle('Membership', ['id' => $membership['id']]);
2595 // If this date calculation winds up being flakey the spirit of the test would be maintained by just checking
2596 // date is greater than today.
2597 $this->assertEquals(date('Y-m-d', strtotime('+ 1 month -1 day')), $membership['end_date']);
2598 $this->assertEquals($newStatusID, $membership['membership_type_id']);
2599 }
2600
2601 /**
2602 * CRM-19945 Tests that Contribute.repeattransaction DOES NOT renew a membership when contribution status=Failed
2603 *
2604 * @dataProvider contributionStatusProvider
2605 *
2606 * @throws \CRM_Core_Exception
2607 */
2608 public function testRepeatTransactionMembershipRenewContributionNotCompleted($contributionStatus): void {
2609 // Completed status should renew so we don't test that here
2610 // In Progress status was never actually intended to be available for contributions.
2611 // Partially paid is not valid.
2612 if (in_array($contributionStatus['name'], ['Completed', 'In Progress', 'Partially paid'])) {
2613 return;
2614 }
2615 [$originalContribution, $membership] = $this->setUpAutoRenewMembership();
2616
2617 $this->callAPISuccess('Contribution', 'repeattransaction', [
2618 'original_contribution_id' => $originalContribution['id'],
2619 'contribution_status_id' => 'Completed',
2620 ]);
2621
2622 $this->callAPISuccess('membership', 'create', [
2623 'id' => $membership['id'],
2624 'end_date' => 'yesterday',
2625 'status_id' => 'Expired',
2626 ]);
2627
2628 $contribution = $this->callAPISuccess('contribution', 'repeattransaction', [
2629 'contribution_recur_id' => $originalContribution['values'][1]['contribution_recur_id'],
2630 'contribution_status_id' => $contributionStatus['name'],
2631 'trxn_id' => 'bobsled',
2632 ]);
2633
2634 $updatedMembership = $this->callAPISuccess('membership', 'getsingle', ['id' => $membership['id']]);
2635
2636 $dateTime = new DateTime('yesterday');
2637 $this->assertEquals($dateTime->format('Y-m-d'), $updatedMembership['end_date']);
2638 $this->assertEquals(CRM_Core_PseudoConstant::getKey('CRM_Member_BAO_Membership', 'status_id', 'Expired'), $updatedMembership['status_id']);
2639
2640 $lineItem = $this->callAPISuccessGetSingle('LineItem', ['contribution_id' => $contribution['id']]);
2641 $this->assertEquals('civicrm_membership', $lineItem['entity_table']);
2642 $this->callAPISuccessGetCount('MembershipPayment', ['membership_id' => $membership['id']]);
2643 }
2644
2645 /**
2646 * Dataprovider provides contribution status as [optionvalue=>contribution_status_name]
2647 * FIXME: buildOptions seems to die in CRM_Core_Config::_construct when in test mode.
2648 *
2649 * @return array
2650 * @throws \CiviCRM_API3_Exception
2651 */
2652 public function contributionStatusProvider() {
2653 $contributionStatuses = civicrm_api3('OptionValue', 'get', [
2654 'return' => ["id", "name"],
2655 'option_group_id' => "contribution_status",
2656 ]);
2657 foreach ($contributionStatuses['values'] as $statusName) {
2658 $statuses[] = [$statusName];
2659 }
2660 return $statuses;
2661 }
2662
2663 /**
2664 * CRM-16397 test appropriate action if total amount has changed for single
2665 * line items.
2666 *
2667 * @throws \CRM_Core_Exception
2668 */
2669 public function testRepeatTransactionAlteredAmount(): void {
2670 $paymentProcessorID = $this->paymentProcessorCreate();
2671 $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', [
2672 'contact_id' => $this->_individualId,
2673 'installments' => '12',
2674 'frequency_interval' => '1',
2675 'amount' => '500',
2676 'contribution_status_id' => 1,
2677 'start_date' => '2012-01-01 00:00:00',
2678 'currency' => 'USD',
2679 'frequency_unit' => 'month',
2680 'payment_processor_id' => $paymentProcessorID,
2681 ]);
2682 $originalContribution = $this->callAPISuccess('contribution', 'create', array_merge(
2683 $this->_params,
2684 [
2685 'contribution_recur_id' => $contributionRecur['id'],
2686 ])
2687 );
2688
2689 $this->callAPISuccess('contribution', 'repeattransaction', [
2690 'original_contribution_id' => $originalContribution['id'],
2691 'contribution_status_id' => 'Completed',
2692 'trxn_id' => 1234,
2693 'total_amount' => '400',
2694 'fee_amount' => 50,
2695 ]);
2696
2697 $lineItemParams = [
2698 'entity_id' => $originalContribution['id'],
2699 'sequential' => 1,
2700 'return' => [
2701 'entity_table',
2702 'qty',
2703 'unit_price',
2704 'line_total',
2705 'label',
2706 'financial_type_id',
2707 'deductible_amount',
2708 'price_field_value_id',
2709 'price_field_id',
2710 ],
2711 ];
2712 $this->callAPISuccessGetSingle('contribution', [
2713 'total_amount' => 400,
2714 'fee_amount' => 50,
2715 'net_amount' => 350,
2716 ]);
2717 $lineItem1 = $this->callAPISuccess('line_item', 'get', array_merge($lineItemParams, [
2718 'entity_id' => $originalContribution['id'],
2719 ]));
2720 $expectedLineItem = array_merge(
2721 $lineItem1['values'][0], [
2722 'line_total' => '400.00',
2723 'unit_price' => '400.00',
2724 ]
2725 );
2726
2727 $lineItem2 = $this->callAPISuccess('line_item', 'get', array_merge($lineItemParams, [
2728 'entity_id' => $originalContribution['id'] + 1,
2729 ]));
2730
2731 unset($expectedLineItem['id'], $expectedLineItem['entity_id'], $lineItem2['values'][0]['id'], $lineItem2['values'][0]['entity_id']);
2732 $this->assertEquals($expectedLineItem, $lineItem2['values'][0]);
2733 }
2734
2735 /**
2736 * Test financial_type_id override behaviour with a single line item.
2737 *
2738 * CRM-17718 a passed in financial_type_id is allowed to override the
2739 * original contribution where there is only one line item.
2740 *
2741 * @throws \CRM_Core_Exception
2742 */
2743 public function testRepeatTransactionPassedInFinancialType() {
2744 $originalContribution = $this->setUpRecurringContribution();
2745
2746 $this->callAPISuccess('Contribution', 'repeattransaction', [
2747 'original_contribution_id' => $originalContribution['id'],
2748 'contribution_status_id' => 'Completed',
2749 'trxn_id' => 12345,
2750 'financial_type_id' => 2,
2751 ]);
2752 $lineItemParams = [
2753 'entity_id' => $originalContribution['id'],
2754 'sequential' => 1,
2755 'return' => [
2756 'entity_table',
2757 'qty',
2758 'unit_price',
2759 'line_total',
2760 'label',
2761 'financial_type_id',
2762 'deductible_amount',
2763 'price_field_value_id',
2764 'price_field_id',
2765 ],
2766 ];
2767
2768 $this->callAPISuccessGetSingle('Contribution', [
2769 'total_amount' => 100,
2770 'financial_type_id' => 2,
2771 ]);
2772 $lineItem1 = $this->callAPISuccess('line_item', 'get', array_merge($lineItemParams, [
2773 'entity_id' => $originalContribution['id'],
2774 ]));
2775 $expectedLineItem = array_merge(
2776 $lineItem1['values'][0], [
2777 'line_total' => '100.00',
2778 'unit_price' => '100.00',
2779 'financial_type_id' => 2,
2780 'contribution_type_id' => 2,
2781 ]
2782 );
2783 $lineItem2 = $this->callAPISuccess('line_item', 'get', array_merge($lineItemParams, [
2784 'entity_id' => $originalContribution['id'] + 1,
2785 ]));
2786 unset($expectedLineItem['id'], $expectedLineItem['entity_id']);
2787 unset($lineItem2['values'][0]['id'], $lineItem2['values'][0]['entity_id']);
2788 $this->assertEquals($expectedLineItem, $lineItem2['values'][0]);
2789 }
2790
2791 /**
2792 * Test Contribution with Order api.
2793 *
2794 * @throws \CRM_Core_Exception|\CiviCRM_API3_Exception
2795 */
2796 public function testContributionOrder() {
2797 $this->_contactID = $this->individualCreate();
2798 $this->createContributionAndMembershipOrder();
2799 $contribution = $this->callAPISuccess('contribution', 'get')['values'][$this->ids['Contribution'][0]];
2800 $this->assertEquals('Pending Label**', $contribution['contribution_status']);
2801 $membership = $this->callAPISuccessGetSingle('Membership', ['contact_id' => $this->_contactID]);
2802
2803 $this->callAPISuccess('Payment', 'create', [
2804 'contribution_id' => $this->ids['Contribution'][0],
2805 'payment_instrument_id' => 'Check',
2806 'total_amount' => 300,
2807 ]);
2808 $contribution = $this->callAPISuccess('contribution', 'get')['values'][$this->ids['Contribution'][0]];
2809 $this->assertEquals('Completed', $contribution['contribution_status']);
2810
2811 $lineItem = $this->callAPISuccess('LineItem', 'get', [
2812 'sequential' => 1,
2813 'contribution_id' => $this->ids['Contribution'][0],
2814 ])['values'];
2815 $this->assertCount(2, $lineItem);
2816 $this->assertEquals($this->ids['Contribution'][0], $lineItem[0]['entity_id']);
2817 $this->assertEquals('civicrm_contribution', $lineItem[0]['entity_table']);
2818 $this->assertEquals($this->ids['Contribution'][0], $lineItem[0]['contribution_id']);
2819 $this->assertEquals($this->ids['Contribution'][0], $lineItem[1]['contribution_id']);
2820 $this->assertEquals('100.00', $lineItem[0]['line_total']);
2821 $this->assertEquals('200.00', $lineItem[1]['line_total']);
2822 $this->assertEquals($membership['id'], $lineItem[1]['entity_id']);
2823 $this->assertEquals('civicrm_membership', $lineItem[1]['entity_table']);
2824 }
2825
2826 /**
2827 * Test financial_type_id override behaviour with a single line item.
2828 *
2829 * CRM-17718 a passed in financial_type_id is not allowed to override the
2830 * original contribution where there is more than one line item.
2831 *
2832 * @throws \CRM_Core_Exception
2833 */
2834 public function testRepeatTransactionPassedInFinancialTypeTwoLineItems(): void {
2835 $this->_params = $this->getParticipantOrderParams();
2836 $originalContribution = $this->setUpRecurringContribution();
2837
2838 $this->callAPISuccess('Contribution', 'repeattransaction', [
2839 'original_contribution_id' => $originalContribution['id'],
2840 'contribution_status_id' => 'Completed',
2841 'trxn_id' => 'repeat',
2842 'financial_type_id' => 2,
2843 ]);
2844
2845 // Retrieve the new contribution and note the financial type passed in has been ignored.
2846 $contribution = $this->callAPISuccessGetSingle('Contribution', [
2847 'trxn_id' => 'repeat',
2848 ]);
2849 $this->assertEquals(4, $contribution['financial_type_id']);
2850
2851 $lineItems = $this->callAPISuccess('line_item', 'get', [
2852 'entity_id' => $contribution['id'],
2853 ])['values'];
2854 foreach ($lineItems as $lineItem) {
2855 $this->assertNotEquals(2, $lineItem['financial_type_id']);
2856 }
2857 }
2858
2859 /**
2860 * CRM-17718 test appropriate action if financial type has changed for single
2861 * line items.
2862 *
2863 * @throws \CRM_Core_Exception
2864 */
2865 public function testRepeatTransactionUpdatedFinancialType(): void {
2866 $originalContribution = $this->setUpRecurringContribution([], ['financial_type_id' => 2]);
2867
2868 $this->callAPISuccess('contribution', 'repeattransaction', [
2869 'contribution_recur_id' => $originalContribution['id'],
2870 'contribution_status_id' => 'Completed',
2871 'trxn_id' => 234,
2872 ]);
2873 $lineItemParams = [
2874 'entity_id' => $originalContribution['id'],
2875 'sequential' => 1,
2876 'return' => [
2877 'entity_table',
2878 'qty',
2879 'unit_price',
2880 'line_total',
2881 'label',
2882 'financial_type_id',
2883 'deductible_amount',
2884 'price_field_value_id',
2885 'price_field_id',
2886 ],
2887 ];
2888
2889 $this->callAPISuccessGetSingle('contribution', [
2890 'total_amount' => 100,
2891 'financial_type_id' => 2,
2892 ]);
2893 $lineItem1 = $this->callAPISuccess('line_item', 'get', array_merge($lineItemParams, [
2894 'entity_id' => $originalContribution['id'],
2895 ]));
2896 $expectedLineItem = array_merge(
2897 $lineItem1['values'][0], [
2898 'line_total' => '100.00',
2899 'unit_price' => '100.00',
2900 'financial_type_id' => 2,
2901 'contribution_type_id' => 2,
2902 ]
2903 );
2904
2905 $lineItem2 = $this->callAPISuccess('line_item', 'get', array_merge($lineItemParams, [
2906 'entity_id' => $originalContribution['id'] + 1,
2907 ]));
2908 unset($expectedLineItem['id'], $expectedLineItem['entity_id']);
2909 unset($lineItem2['values'][0]['id'], $lineItem2['values'][0]['entity_id']);
2910 $this->assertEquals($expectedLineItem, $lineItem2['values'][0]);
2911 }
2912
2913 /**
2914 * CRM-16397 test appropriate action if campaign has been passed in.
2915 */
2916 public function testRepeatTransactionPassedInCampaign() {
2917 $paymentProcessorID = $this->paymentProcessorCreate();
2918 $campaignID = $this->campaignCreate();
2919 $campaignID2 = $this->campaignCreate();
2920 $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', [
2921 'contact_id' => $this->_individualId,
2922 'installments' => '12',
2923 'frequency_interval' => '1',
2924 'amount' => '100',
2925 'contribution_status_id' => 1,
2926 'start_date' => '2012-01-01 00:00:00',
2927 'currency' => 'USD',
2928 'frequency_unit' => 'month',
2929 'payment_processor_id' => $paymentProcessorID,
2930 ]);
2931 $originalContribution = $this->callAPISuccess('contribution', 'create', array_merge(
2932 $this->_params,
2933 [
2934 'contribution_recur_id' => $contributionRecur['id'],
2935 'campaign_id' => $campaignID,
2936 ])
2937 );
2938
2939 $this->callAPISuccess('contribution', 'repeattransaction', [
2940 'original_contribution_id' => $originalContribution['id'],
2941 'contribution_status_id' => 'Completed',
2942 'trxn_id' => 2345,
2943 'campaign_id' => $campaignID2,
2944 ]);
2945
2946 $this->callAPISuccessGetSingle('contribution', [
2947 'total_amount' => 100,
2948 'campaign_id' => $campaignID2,
2949 ]);
2950 }
2951
2952 /**
2953 * CRM-17718 campaign stored on contribution recur gets priority.
2954 *
2955 * This reflects the fact we permit people to update them.
2956 *
2957 * @throws \CRM_Core_Exception
2958 */
2959 public function testRepeatTransactionUpdatedCampaign(): void {
2960 $paymentProcessorID = $this->paymentProcessorCreate();
2961 $campaignID = $this->campaignCreate();
2962 $campaignID2 = $this->campaignCreate();
2963 $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', [
2964 'contact_id' => $this->_individualId,
2965 'installments' => '12',
2966 'frequency_interval' => '1',
2967 'amount' => '100',
2968 'contribution_status_id' => 1,
2969 'start_date' => '2012-01-01 00:00:00',
2970 'currency' => 'USD',
2971 'frequency_unit' => 'month',
2972 'payment_processor_id' => $paymentProcessorID,
2973 'campaign_id' => $campaignID,
2974 ]);
2975 $originalContribution = $this->callAPISuccess('contribution', 'create', array_merge(
2976 $this->_params,
2977 [
2978 'contribution_recur_id' => $contributionRecur['id'],
2979 'campaign_id' => $campaignID2,
2980 ])
2981 );
2982
2983 $this->callAPISuccess('contribution', 'repeattransaction', [
2984 'original_contribution_id' => $originalContribution['id'],
2985 'contribution_status_id' => 'Completed',
2986 'trxn_id' => 789,
2987 ]);
2988
2989 $this->callAPISuccessGetSingle('Contribution', [
2990 'total_amount' => 100,
2991 'campaign_id' => $campaignID,
2992 ]);
2993 }
2994
2995 /**
2996 * CRM-20685 Repeattransaction produces incorrect Financial Type ID (in
2997 * specific circumstance) - if number of lineItems = 1.
2998 *
2999 * This case happens when the line item & contribution do not have the same
3000 * type in his initiating transaction.
3001 *
3002 * @throws \CRM_Core_Exception
3003 */
3004 public function testRepeatTransactionUpdatedFinancialTypeAndNotEquals() {
3005 $originalContribution = $this->setUpRecurringContribution([], ['financial_type_id' => 2]);
3006 // This will made the trick to get the not equals behaviour.
3007 $this->callAPISuccess('line_item', 'create', ['id' => 1, 'financial_type_id' => 4]);
3008 $this->callAPISuccess('contribution', 'repeattransaction', [
3009 'contribution_recur_id' => $originalContribution['id'],
3010 'contribution_status_id' => 'Completed',
3011 'trxn_id' => 1234,
3012 ]);
3013 $lineItemParams = [
3014 'entity_id' => $originalContribution['id'],
3015 'sequential' => 1,
3016 'return' => [
3017 'entity_table',
3018 'qty',
3019 'unit_price',
3020 'line_total',
3021 'label',
3022 'financial_type_id',
3023 'deductible_amount',
3024 'price_field_value_id',
3025 'price_field_id',
3026 ],
3027 ];
3028 $this->callAPISuccessGetSingle('contribution', [
3029 'total_amount' => 100,
3030 'financial_type_id' => 2,
3031 ]);
3032 $lineItem1 = $this->callAPISuccess('line_item', 'get', array_merge($lineItemParams, [
3033 'entity_id' => $originalContribution['id'],
3034 ]));
3035 $expectedLineItem = array_merge(
3036 $lineItem1['values'][0], [
3037 'line_total' => '100.00',
3038 'unit_price' => '100.00',
3039 'financial_type_id' => 4,
3040 'contribution_type_id' => 4,
3041 ]
3042 );
3043
3044 $lineItem2 = $this->callAPISuccess('line_item', 'get', array_merge($lineItemParams, [
3045 'entity_id' => $originalContribution['id'] + 1,
3046 ]));
3047 $this->callAPISuccess('line_item', 'create', ['id' => 1, 'financial_type_id' => 1]);
3048 unset($expectedLineItem['id'], $expectedLineItem['entity_id']);
3049 unset($lineItem2['values'][0]['id'], $lineItem2['values'][0]['entity_id']);
3050 $this->assertEquals($expectedLineItem, $lineItem2['values'][0]);
3051 }
3052
3053 /**
3054 * Test completing a transaction does not 'mess' with net amount (CRM-15960).
3055 */
3056 public function testCompleteTransactionNetAmountOK() {
3057 $this->createLoggedInUser();
3058 $params = array_merge($this->_params, ['contribution_status_id' => 2]);
3059 unset($params['net_amount']);
3060 $contribution = $this->callAPISuccess('contribution', 'create', $params);
3061 $this->callAPISuccess('contribution', 'completetransaction', [
3062 'id' => $contribution['id'],
3063 ]);
3064 $contribution = $this->callAPISuccess('contribution', 'getsingle', ['id' => $contribution['id']]);
3065 $this->assertEquals('Completed', $contribution['contribution_status']);
3066 $this->assertTrue(($contribution['total_amount'] - $contribution['net_amount']) == $contribution['fee_amount']);
3067 }
3068
3069 /**
3070 * CRM-14151 - Test completing a transaction via the API.
3071 */
3072 public function testCompleteTransactionWithReceiptDateSet() {
3073 $this->swapMessageTemplateForTestTemplate();
3074 $mut = new CiviMailUtils($this, TRUE);
3075 $this->createLoggedInUser();
3076 $params = array_merge($this->_params, ['contribution_status_id' => 2, 'receipt_date' => 'now']);
3077 $contribution = $this->callAPISuccess('contribution', 'create', $params);
3078 $this->callAPISuccess('contribution', 'completetransaction', ['id' => $contribution['id'], 'trxn_date' => date('Y-m-d')]);
3079 $contribution = $this->callAPISuccess('contribution', 'get', ['id' => $contribution['id'], 'sequential' => 1]);
3080 $this->assertEquals('Completed', $contribution['values'][0]['contribution_status']);
3081 // Make sure receive_date is original date and make sure payment date is today
3082 $this->assertEquals('2012-05-11', date('Y-m-d', strtotime($contribution['values'][0]['receive_date'])));
3083 $payment = $this->callAPISuccess('payment', 'get', ['contribution_id' => $contribution['id'], 'sequential' => 1]);
3084 $this->assertEquals(date('Y-m-d'), date('Y-m-d', strtotime($payment['values'][0]['trxn_date'])));
3085 $mut->checkMailLog([
3086 'Receipt - Contribution',
3087 'receipt_date:::' . date('Ymd'),
3088 ]);
3089 $mut->stop();
3090 $this->revertTemplateToReservedTemplate();
3091 }
3092
3093 /**
3094 * CRM-1960 - Test to ensure that completetransaction respects the is_email_receipt setting
3095 */
3096 public function testCompleteTransactionWithEmailReceiptInput() {
3097 $contributionPage = $this->createReceiptableContributionPage();
3098
3099 $this->_params['contribution_page_id'] = $contributionPage['id'];
3100 $params = array_merge($this->_params, ['contribution_status_id' => 2]);
3101 $contribution = $this->callAPISuccess('contribution', 'create', $params);
3102 // Complete the transaction overriding is_email_receipt to = FALSE
3103 $this->callAPISuccess('contribution', 'completetransaction', [
3104 'id' => $contribution['id'],
3105 'trxn_date' => date('2011-04-09'),
3106 'trxn_id' => 'kazam',
3107 'is_email_receipt' => 0,
3108 ]);
3109 // Check if a receipt was issued
3110 $receipt_date = $this->callAPISuccess('Contribution', 'getvalue', ['id' => $contribution['id'], 'return' => 'receipt_date']);
3111 $this->assertEquals('', $receipt_date);
3112 }
3113
3114 /**
3115 * Test that $is_recur is assigned to the receipt.
3116 */
3117 public function testCompleteTransactionForRecurring() {
3118 $this->mut = new CiviMailUtils($this, TRUE);
3119 $this->swapMessageTemplateForTestTemplate();
3120 $recurring = $this->setUpRecurringContribution();
3121 $contributionPage = $this->createReceiptableContributionPage(['is_recur' => TRUE, 'recur_frequency_unit' => 'month', 'recur_interval' => 1]);
3122
3123 $this->_params['contribution_page_id'] = $contributionPage['id'];
3124 $this->_params['contribution_recur_id'] = $recurring['id'];
3125
3126 $contribution = $this->setUpForCompleteTransaction();
3127
3128 $this->callAPISuccess('contribution', 'completetransaction', [
3129 'id' => $contribution['id'],
3130 'trxn_date' => date('2011-04-09'),
3131 'trxn_id' => 'kazam',
3132 'is_email_receipt' => 1,
3133 ]);
3134
3135 $this->mut->checkMailLog([
3136 'is_recur:::1',
3137 'cancelSubscriptionUrl:::' . CIVICRM_UF_BASEURL,
3138 ]);
3139 $this->mut->stop();
3140 $this->revertTemplateToReservedTemplate();
3141 }
3142
3143 /**
3144 * CRM-19710 - Test to ensure that completetransaction respects the input for
3145 * is_email_receipt setting.
3146 *
3147 * If passed in it will override the default from contribution page.
3148 *
3149 * @throws \CRM_Core_Exception
3150 */
3151 public function testCompleteTransactionWithEmailReceiptInputTrue(): void {
3152 $mut = new CiviMailUtils($this, TRUE);
3153 $this->createLoggedInUser();
3154 $contributionPageParams = ['is_email_receipt' => 0];
3155 // Create a Contribution Page with is_email_receipt = FALSE
3156 $contributionPageID = $this->createQuickConfigContributionPage($contributionPageParams);
3157 $this->_params['contribution_page_id'] = $contributionPageID;
3158 $params = array_merge($this->_params, ['contribution_status_id' => 2, 'receipt_date' => 'now']);
3159 $contribution = $this->callAPISuccess('contribution', 'create', $params);
3160 // Complete the transaction overriding is_email_receipt to = TRUE
3161 $this->callAPISuccess('contribution', 'completetransaction', [
3162 'id' => $contribution['id'],
3163 'is_email_receipt' => 1,
3164 ]);
3165 $mut->checkMailLog([
3166 'Contribution Information',
3167 ]);
3168 $mut->stop();
3169 }
3170
3171 /**
3172 * Complete the transaction using the template with all the possible.
3173 */
3174 public function testCompleteTransactionWithTestTemplate() {
3175 $this->swapMessageTemplateForTestTemplate();
3176 $contribution = $this->setUpForCompleteTransaction();
3177 $this->callAPISuccess('contribution', 'completetransaction', [
3178 'id' => $contribution['id'],
3179 'trxn_date' => date('2011-04-09'),
3180 'trxn_id' => 'kazam',
3181 ]);
3182 $receive_date = $this->callAPISuccess('Contribution', 'getvalue', ['id' => $contribution['id'], 'return' => 'receive_date']);
3183 $this->mut->checkMailLog([
3184 'email:::anthony_anderson@civicrm.org',
3185 'is_monetary:::1',
3186 'amount:::100.00',
3187 'currency:::USD',
3188 'receive_date:::' . date('Ymd', strtotime($receive_date)),
3189 'receipt_date:::' . date('Ymd'),
3190 'contributeMode:::notify',
3191 'title:::Contribution',
3192 'displayName:::Mr. Anthony Anderson II',
3193 'trxn_id:::kazam',
3194 'contactID:::' . $this->_params['contact_id'],
3195 'contributionID:::' . $contribution['id'],
3196 'financialTypeId:::1',
3197 'financialTypeName:::Donation',
3198 ]);
3199 $this->mut->stop();
3200 $this->revertTemplateToReservedTemplate();
3201 }
3202
3203 /**
3204 * Complete the transaction using the template with all the possible.
3205 */
3206 public function testCompleteTransactionContributionPageFromAddress() {
3207 $contributionPage = $this->callAPISuccess('ContributionPage', 'create', [
3208 'receipt_from_name' => 'Mickey Mouse',
3209 'receipt_from_email' => 'mickey@mouse.com',
3210 'title' => "Test Contribution Page",
3211 'financial_type_id' => 1,
3212 'currency' => 'NZD',
3213 'goal_amount' => 50,
3214 'is_pay_later' => 1,
3215 'is_monetary' => TRUE,
3216 'is_email_receipt' => TRUE,
3217 ]);
3218 $this->_params['contribution_page_id'] = $contributionPage['id'];
3219 $contribution = $this->setUpForCompleteTransaction();
3220 $this->callAPISuccess('contribution', 'completetransaction', ['id' => $contribution['id']]);
3221 $this->mut->checkMailLog([
3222 'mickey@mouse.com',
3223 'Mickey Mouse <',
3224 ]);
3225 $this->mut->stop();
3226 }
3227
3228 /**
3229 * Test completing first transaction in a recurring series.
3230 *
3231 * The status should be set to 'in progress' and the next scheduled payment date calculated.
3232 *
3233 * @dataProvider getScheduledDateData
3234 *
3235 * @param array $dataSet
3236 *
3237 * @throws \Exception
3238 */
3239 public function testCompleteTransactionSetStatusToInProgress($dataSet) {
3240 $paymentProcessorID = $this->paymentProcessorCreate();
3241 $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', array_merge([
3242 'contact_id' => $this->_individualId,
3243 'installments' => '2',
3244 'frequency_interval' => '1',
3245 'amount' => '500',
3246 'contribution_status_id' => 'Pending',
3247 'start_date' => '2012-01-01 00:00:00',
3248 'currency' => 'USD',
3249 'frequency_unit' => 'month',
3250 'payment_processor_id' => $paymentProcessorID,
3251 ], $dataSet['data']));
3252 $contribution = $this->callAPISuccess('contribution', 'create', array_merge(
3253 $this->_params,
3254 [
3255 'contribution_recur_id' => $contributionRecur['id'],
3256 'contribution_status_id' => 'Pending',
3257 'receive_date' => $dataSet['receive_date'],
3258 ])
3259 );
3260 $this->callAPISuccess('Contribution', 'completetransaction', [
3261 'id' => $contribution,
3262 'receive_date' => $dataSet['receive_date'],
3263 ]);
3264 $contributionRecur = $this->callAPISuccessGetSingle('ContributionRecur', [
3265 'id' => $contributionRecur['id'],
3266 'return' => ['next_sched_contribution_date', 'contribution_status_id'],
3267 ]);
3268 $this->assertEquals(5, $contributionRecur['contribution_status_id']);
3269 $this->assertEquals($dataSet['expected'], $contributionRecur['next_sched_contribution_date']);
3270 $this->callAPISuccess('Contribution', 'create', array_merge(
3271 $this->_params,
3272 [
3273 'contribution_recur_id' => $contributionRecur['id'],
3274 'contribution_status_id' => 'Completed',
3275 ]
3276 ));
3277 $contributionRecur = $this->callAPISuccessGetSingle('ContributionRecur', [
3278 'id' => $contributionRecur['id'],
3279 'return' => ['contribution_status_id'],
3280 ]);
3281 $this->assertEquals(1, $contributionRecur['contribution_status_id']);
3282 }
3283
3284 /**
3285 * Get dates for testing.
3286 *
3287 * @return array
3288 */
3289 public function getScheduledDateData() {
3290 $result = [];
3291 $result[]['2016-08-31-1-month'] = [
3292 'data' => [
3293 'start_date' => '2016-08-31',
3294 'frequency_interval' => 1,
3295 'frequency_unit' => 'month',
3296 ],
3297 'receive_date' => '2016-08-31',
3298 'expected' => '2016-10-01 00:00:00',
3299 ];
3300 $result[]['2012-01-01-1-month'] = [
3301 'data' => [
3302 'start_date' => '2012-01-01',
3303 'frequency_interval' => 1,
3304 'frequency_unit' => 'month',
3305 ],
3306 'receive_date' => '2012-01-01',
3307 'expected' => '2012-02-01 00:00:00',
3308 ];
3309 $result[]['2012-01-01-1-month'] = [
3310 'data' => [
3311 'start_date' => '2012-01-01',
3312 'frequency_interval' => 1,
3313 'frequency_unit' => 'month',
3314 ],
3315 'receive_date' => '2012-02-29',
3316 'expected' => '2012-03-29 00:00:00',
3317 ];
3318 $result['receive_date_includes_time']['2012-01-01-1-month'] = [
3319 'data' => [
3320 'start_date' => '2012-01-01',
3321 'frequency_interval' => 1,
3322 'frequency_unit' => 'month',
3323 'next_sched_contribution_date' => '2012-02-29',
3324 ],
3325 'receive_date' => '2012-02-29 16:00:00',
3326 'expected' => '2012-03-29 00:00:00',
3327 ];
3328 return $result;
3329 }
3330
3331 /**
3332 * Test completing a pledge with the completeTransaction api..
3333 *
3334 * Note that we are creating a logged in user because email goes out from
3335 * that person.
3336 */
3337 public function testCompleteTransactionUpdatePledgePayment() {
3338 $this->swapMessageTemplateForTestTemplate();
3339 $mut = new CiviMailUtils($this, TRUE);
3340 $mut->clearMessages();
3341 $this->createLoggedInUser();
3342 $contributionID = $this->createPendingPledgeContribution();
3343 $this->callAPISuccess('contribution', 'completetransaction', [
3344 'id' => $contributionID,
3345 'trxn_date' => '1 Feb 2013',
3346 ]);
3347 $pledge = $this->callAPISuccessGetSingle('Pledge', [
3348 'id' => $this->_ids['pledge'],
3349 ]);
3350 $this->assertEquals('Completed', $pledge['pledge_status']);
3351
3352 $status = $this->callAPISuccessGetValue('PledgePayment', [
3353 'pledge_id' => $this->_ids['pledge'],
3354 'return' => 'status_id',
3355 ]);
3356 $this->assertEquals(1, $status);
3357 $mut->checkMailLog([
3358 'amount:::500.00',
3359 // The `receive_date` should remain as it was created.
3360 // TODO: the latest payment transaction date (and maybe other details,
3361 // such as amount and payment instrument) would be a useful token to make
3362 // available.
3363 'receive_date:::20120511000000',
3364 "receipt_date:::\n",
3365 ]);
3366 $mut->stop();
3367 $this->revertTemplateToReservedTemplate();
3368 }
3369
3370 /**
3371 * Test completing a transaction with an event via the API.
3372 *
3373 * Note that we are creating a logged in user because email goes out from
3374 * that person
3375 *
3376 * @throws \CRM_Core_Exception
3377 */
3378 public function testCompleteTransactionWithParticipantRecord(): void {
3379 $mut = new CiviMailUtils($this, TRUE);
3380 $mut->clearMessages();
3381 $this->_individualId = $this->createLoggedInUser();
3382 $this->_params['source'] = 'Online Event Registration: Annual CiviCRM meet';
3383 $contributionID = $this->createPendingParticipantContribution();
3384 $this->createJoinedProfile(['entity_id' => $this->_ids['event']['test'], 'entity_table' => 'civicrm_event']);
3385 $this->createJoinedProfile(['entity_id' => $this->_ids['event']['test'], 'entity_table' => 'civicrm_event', 'weight' => 2], ['name' => 'post_1', 'title' => 'title_post_2', 'frontend_title' => 'public 2']);
3386 $this->createJoinedProfile(['entity_id' => $this->_ids['event']['test'], 'entity_table' => 'civicrm_event', 'weight' => 3], ['name' => 'post_2', 'title' => 'title_post_3', 'frontend_title' => 'public 3']);
3387 $this->eliminateUFGroupOne();
3388
3389 $this->callAPISuccess('contribution', 'completetransaction', ['id' => $contributionID]);
3390 $contribution = $this->callAPISuccessGetSingle('Contribution', ['id' => $contributionID, 'return' => ['contribution_source']]);
3391 $this->assertEquals('Online Event Registration: Annual CiviCRM meet', $contribution['contribution_source']);
3392 $participantStatus = $this->callAPISuccessGetValue('participant', [
3393 'id' => $this->_ids['participant'],
3394 'return' => 'participant_status_id',
3395 ]);
3396 $this->assertEquals(1, $participantStatus);
3397
3398 //Assert only three activities are created.
3399 $activities = $this->callAPISuccess('Activity', 'get', [
3400 'contact_id' => $this->_individualId,
3401 ])['values'];
3402
3403 $this->assertCount(3, $activities);
3404 $activityNames = array_count_values(CRM_Utils_Array::collect('activity_name', $activities));
3405 // record two activities before and after completing payment for Event registration
3406 $this->assertEquals(2, $activityNames['Event Registration']);
3407 // update the original 'Contribution' activity created after completing payment
3408 $this->assertEquals(1, $activityNames['Contribution']);
3409
3410 $mut->checkMailLog([
3411 'Annual CiviCRM meet',
3412 'Event',
3413 'This is a confirmation that your registration has been received and your status has been updated to Registered.',
3414 'First Name: Logged In',
3415 'Public title',
3416 'public 2',
3417 'public 3',
3418 ], ['Back end title', 'title_post_2', 'title_post_3']);
3419 $mut->stop();
3420 }
3421
3422 /**
3423 * Test membership is renewed when transaction completed.
3424 */
3425 public function testCompleteTransactionMembershipPriceSet() {
3426 $this->createPriceSetWithPage('membership');
3427 $stateOfGrace = $this->callAPISuccess('MembershipStatus', 'getvalue', [
3428 'name' => 'Grace',
3429 'return' => 'id',
3430 ]);
3431 $this->setUpPendingContribution($this->_ids['price_field_value'][0]);
3432 $membership = $this->callAPISuccess('membership', 'getsingle', ['id' => $this->_ids['membership']]);
3433 $logs = $this->callAPISuccess('MembershipLog', 'get', [
3434 'membership_id' => $this->_ids['membership'],
3435 ]);
3436 $this->assertEquals(1, $logs['count']);
3437 $this->assertEquals($stateOfGrace, $membership['status_id']);
3438 $this->callAPISuccess('contribution', 'completetransaction', ['id' => $this->_ids['contribution']]);
3439 $membership = $this->callAPISuccess('membership', 'getsingle', ['id' => $this->_ids['membership']]);
3440 $this->assertEquals(date('Y-m-d', strtotime('yesterday + 1 year')), $membership['end_date']);
3441 $this->callAPISuccessGetSingle('LineItem', [
3442 'entity_id' => $this->_ids['membership'],
3443 'entity_table' => 'civicrm_membership',
3444 ]);
3445 $logs = $this->callAPISuccess('MembershipLog', 'get', [
3446 'membership_id' => $this->_ids['membership'],
3447 ]);
3448 $this->assertEquals(2, $logs['count']);
3449 $this->assertNotEquals($stateOfGrace, $logs['values'][2]['status_id']);
3450 //Assert only three activities are created.
3451 $activities = CRM_Activity_BAO_Activity::getContactActivity($this->_ids['contact']);
3452 $this->assertEquals(3, count($activities));
3453 $activityNames = array_flip(CRM_Utils_Array::collect('activity_name', $activities));
3454 $this->assertArrayHasKey('Contribution', $activityNames);
3455 $this->assertArrayHasKey('Membership Signup', $activityNames);
3456 $this->assertArrayHasKey('Change Membership Status', $activityNames);
3457 $this->cleanUpAfterPriceSets();
3458 }
3459
3460 /**
3461 * Test if renewal activity is create after changing Pending contribution to
3462 * Completed via offline
3463 *
3464 * @throws \CRM_Core_Exception
3465 * @throws \CRM_Core_Exception
3466 * @throws \CiviCRM_API3_Exception
3467 */
3468 public function testPendingToCompleteContribution(): void {
3469 // @todo - figure out why this test is not valid.
3470 $this->isValidateFinancialsOnPostAssert = FALSE;
3471 $this->createPriceSetWithPage('membership');
3472 $this->setUpPendingContribution($this->_ids['price_field_value'][0]);
3473 $this->callAPISuccess('membership', 'getsingle', ['id' => $this->_ids['membership']]);
3474 // Case 1: Assert that Membership Signup Activity is created on Pending to Completed Contribution via backoffice
3475 $activity = $this->callAPISuccess('Activity', 'get', [
3476 'activity_type_id' => 'Membership Signup',
3477 'source_record_id' => $this->_ids['membership'],
3478 'status_id' => 'Scheduled',
3479 ]);
3480 $this->assertEquals(1, $activity['count']);
3481
3482 // change pending contribution to completed
3483 $form = new CRM_Contribute_Form_Contribution();
3484
3485 $form->_params = [
3486 'id' => $this->_ids['contribution'],
3487 'total_amount' => 20,
3488 'net_amount' => 20,
3489 'fee_amount' => 0,
3490 'financial_type_id' => 1,
3491 'contact_id' => $this->_individualId,
3492 'contribution_status_id' => 1,
3493 'billing_middle_name' => '',
3494 'billing_last_name' => 'Adams',
3495 'billing_street_address-5' => '790L Lincoln St S',
3496 'billing_city-5' => 'Maryknoll',
3497 'billing_state_province_id-5' => 1031,
3498 'billing_postal_code-5' => 10545,
3499 'billing_country_id-5' => 1228,
3500 'frequency_interval' => 1,
3501 'frequency_unit' => 'month',
3502 'installments' => '',
3503 'hidden_AdditionalDetail' => 1,
3504 'hidden_Premium' => 1,
3505 'from_email_address' => '"civi45" <civi45@civicrm.com>',
3506 'receipt_date' => '',
3507 'receipt_date_time' => '',
3508 'payment_processor_id' => $this->paymentProcessorID,
3509 'currency' => 'USD',
3510 'contribution_page_id' => $this->_ids['contribution_page'],
3511 'contribution_mode' => 'membership',
3512 'source' => 'Membership Signup and Renewal',
3513 ];
3514
3515 $form->testSubmit($form->_params, CRM_Core_Action::UPDATE);
3516
3517 // Case 2: After successful payment for Pending backoffice there are three activities created
3518 // 2.a Update status of existing Scheduled Membership Signup (created in step 1) to Completed
3519 $activity = $this->callAPISuccess('Activity', 'get', [
3520 'activity_type_id' => 'Membership Signup',
3521 'source_record_id' => $this->_ids['membership'],
3522 'status_id' => 'Completed',
3523 ]);
3524 $this->assertEquals(1, $activity['count']);
3525 // 2.b Contribution activity created to record successful payment
3526 $activity = $this->callAPISuccess('Activity', 'get', [
3527 'activity_type_id' => 'Contribution',
3528 'source_record_id' => $this->_ids['contribution'],
3529 'status_id' => 'Completed',
3530 ]);
3531 $this->assertEquals(1, $activity['count']);
3532
3533 // 2.c 'Change membership type' activity created to record Membership status change from Grace to Current
3534 $activity = $this->callAPISuccess('Activity', 'get', [
3535 'activity_type_id' => 'Change Membership Status',
3536 'source_record_id' => $this->_ids['membership'],
3537 'status_id' => 'Completed',
3538 ]);
3539 $this->assertEquals(1, $activity['count']);
3540 $this->assertEquals('Status changed from Grace to Current', $activity['values'][$activity['id']]['subject']);
3541 $membershipLogs = $this->callAPISuccess('MembershipLog', 'get', ['sequential' => 1])['values'];
3542 $this->assertEquals('Grace', CRM_Core_PseudoConstant::getName('CRM_Member_BAO_Membership', 'status_id', $membershipLogs[0]['status_id']));
3543 $this->assertEquals('Current', CRM_Core_PseudoConstant::getName('CRM_Member_BAO_Membership', 'status_id', $membershipLogs[1]['status_id']));
3544 //Create another pending contribution for renewal
3545 $contribution = $this->callAPISuccess('contribution', 'create', [
3546 'domain_id' => 1,
3547 'contact_id' => $this->_ids['contact'],
3548 'receive_date' => date('Ymd'),
3549 'total_amount' => 20.00,
3550 'financial_type_id' => 1,
3551 'payment_instrument_id' => 'Credit Card',
3552 'non_deductible_amount' => 10.00,
3553 'trxn_id' => 'rdhfi88',
3554 'invoice_id' => 'dofhiewuyr',
3555 'source' => 'SSF',
3556 'contribution_status_id' => 2,
3557 'contribution_page_id' => $this->_ids['contribution_page'],
3558 // We can't rely on contribution api to link line items correctly to membership
3559 'skipLineItem' => TRUE,
3560 'api.membership_payment.create' => ['membership_id' => $this->_ids['membership']],
3561 ]);
3562
3563 $this->callAPISuccess('line_item', 'create', [
3564 'entity_id' => $contribution['id'],
3565 'entity_table' => 'civicrm_contribution',
3566 'contribution_id' => $contribution['id'],
3567 'price_field_id' => $this->_ids['price_field'][0],
3568 'qty' => 1,
3569 'unit_price' => 20,
3570 'line_total' => 20,
3571 'financial_type_id' => 1,
3572 'price_field_value_id' => $this->_ids['price_field_value']['cont'],
3573 ]);
3574 $this->callAPISuccess('line_item', 'create', [
3575 'entity_id' => $this->_ids['membership'],
3576 'entity_table' => 'civicrm_membership',
3577 'contribution_id' => $contribution['id'],
3578 'price_field_id' => $this->_ids['price_field'][0],
3579 'qty' => 1,
3580 'unit_price' => 20,
3581 'line_total' => 20,
3582 'financial_type_id' => 1,
3583 'price_field_value_id' => $this->_ids['price_field_value'][0],
3584 'membership_type_id' => $this->_ids['membership_type'],
3585 ]);
3586
3587 //Update it to Failed.
3588 $form->_params['id'] = $contribution['id'];
3589 $form->_params['contribution_status_id'] = 4;
3590
3591 $form->testSubmit($form->_params, CRM_Core_Action::UPDATE);
3592 //Existing membership should not get updated to expired.
3593 $membership = $this->callAPISuccess('membership', 'getsingle', ['id' => $this->_ids['membership']]);
3594 $this->assertNotEquals(4, $membership['status_id']);
3595 }
3596
3597 /**
3598 * Test membership is renewed for 2 terms when transaction completed based on the line item having 2 terms as qty.
3599 *
3600 * Also check that altering the qty for the most recent contribution results in repeattransaction picking it up.
3601 */
3602 public function testCompleteTransactionMembershipPriceSetTwoTerms() {
3603 $this->createPriceSetWithPage('membership');
3604 $this->setUpPendingContribution($this->_ids['price_field_value'][1]);
3605 $this->callAPISuccess('contribution', 'completetransaction', ['id' => $this->_ids['contribution']]);
3606 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $this->_ids['membership']]);
3607 $this->assertEquals(date('Y-m-d', strtotime('yesterday + 2 years')), $membership['end_date']);
3608
3609 $paymentProcessorID = $this->paymentProcessorAuthorizeNetCreate();
3610
3611 $contributionRecurID = $this->callAPISuccess('ContributionRecur', 'create', ['contact_id' => $membership['contact_id'], 'payment_processor_id' => $paymentProcessorID, 'amount' => 20, 'frequency_interval' => 1])['id'];
3612 $this->callAPISuccess('Contribution', 'create', ['id' => $this->_ids['contribution'], 'contribution_recur_id' => $contributionRecurID]);
3613 $this->callAPISuccess('contribution', 'repeattransaction', ['contribution_recur_id' => $contributionRecurID, 'contribution_status_id' => 'Completed']);
3614 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $this->_ids['membership']]);
3615 $this->assertEquals(date('Y-m-d', strtotime('yesterday + 4 years')), $membership['end_date']);
3616
3617 // Update the most recent contribution to have a qty of 1 in it's line item and then repeat, expecting just 1 year to be added.
3618 $contribution = Contribution::get()->setOrderBy(['id' => 'DESC'])->setSelect(['id'])->execute()->first();
3619 CRM_Core_DAO::executeQuery('UPDATE civicrm_line_item SET price_field_value_id = ' . $this->_ids['price_field_value'][0] . ' WHERE contribution_id = ' . $contribution['id']);
3620 $this->callAPISuccess('contribution', 'repeattransaction', ['contribution_recur_id' => $contributionRecurID, 'contribution_status_id' => 'Completed']);
3621 $membership = $this->callAPISuccessGetSingle('membership', ['id' => $this->_ids['membership']]);
3622 $this->assertEquals(date('Y-m-d', strtotime('yesterday + 5 years')), $membership['end_date']);
3623
3624 $this->cleanUpAfterPriceSets();
3625 }
3626
3627 public function cleanUpAfterPriceSets() {
3628 $this->quickCleanUpFinancialEntities();
3629 $this->contactDelete($this->_ids['contact']);
3630 }
3631
3632 /**
3633 * Set up a pending transaction with a specific price field id.
3634 *
3635 * @param int $priceFieldValueID
3636 * @param array $contriParams
3637 *
3638 * @throws \CRM_Core_Exception
3639 * @throws \CiviCRM_API3_Exception
3640 */
3641 public function setUpPendingContribution(int $priceFieldValueID, $contriParams = []): void {
3642 $contactID = $this->individualCreate();
3643 $contribution = $this->callAPISuccess('Order', 'create', array_merge([
3644 'domain_id' => 1,
3645 'contact_id' => $contactID,
3646 'receive_date' => date('Ymd'),
3647 'total_amount' => 20.00,
3648 'financial_type_id' => 1,
3649 'payment_instrument_id' => 'Credit Card',
3650 'non_deductible_amount' => 10.00,
3651 'trxn_id' => 'abcd',
3652 'invoice_id' => 'inv',
3653 'source' => 'SSF',
3654 'contribution_status_id' => 2,
3655 'contribution_page_id' => $this->_ids['contribution_page'],
3656 'line_items' => [
3657 [
3658 'line_item' => [
3659 [
3660 'price_field_id' => $this->_ids['price_field'][0],
3661 'qty' => 1,
3662 'entity_table' => 'civicrm_membership',
3663 'unit_price' => 20,
3664 'line_total' => 20,
3665 'financial_type_id' => 1,
3666 'price_field_value_id' => $priceFieldValueID,
3667 ],
3668 ],
3669 'params' => [
3670 'contact_id' => $contactID,
3671 'membership_type_id' => $this->_ids['membership_type'],
3672 'start_date' => 'yesterday - 1 year',
3673 'end_date' => 'yesterday',
3674 'join_date' => 'yesterday - 1 year',
3675 ],
3676 ],
3677 ],
3678 ], $contriParams));
3679
3680 $this->_ids['contact'] = $contactID;
3681 $this->_ids['contribution'] = $contribution['id'];
3682 $this->_ids['membership'] = $this->callAPISuccessGetValue('MembershipPayment', ['return' => 'membership_id', 'contribution_id' => $contribution['id']]);
3683 }
3684
3685 /**
3686 * Test sending a mail via the API.
3687 *
3688 * @throws \CRM_Core_Exception
3689 */
3690 public function testSendMail(): void {
3691 $mut = new CiviMailUtils($this, TRUE);
3692 $orderParams = $this->_params;
3693 $orderParams['contribution_status_id'] = 'Pending';
3694 $orderParams['api.PaymentProcessor.pay'] = [
3695 'payment_processor_id' => $this->paymentProcessorID,
3696 'credit_card_type' => 'Visa',
3697 'credit_card_number' => 41111111111111,
3698 'amount' => 5,
3699 ];
3700
3701 $order = $this->callAPISuccess('Order', 'create', $orderParams);
3702 $this->callAPISuccess('Payment', 'create', ['total_amount' => 5, 'is_send_notification' => 0, 'order_id' => $order['id']]);
3703 $address = $this->callAPISuccess('Address', 'create', ['contribution_id' => $order['id'], 'name' => 'bob', 'contact_id' => 1, 'street_address' => 'blah']);
3704 $this->callAPISuccess('Contribution', 'create', ['id' => $order['id'], 'address_id' => $address['id']]);
3705 $this->callAPISuccess('contribution', 'sendconfirmation', [
3706 'id' => $order['id'],
3707 'receipt_from_email' => 'api@civicrm.org',
3708 ]);
3709 $mut->checkMailLog([
3710 '$ 100.00',
3711 'Contribution Information',
3712 ], [
3713 'Event',
3714 ]);
3715
3716 $this->checkCreditCardDetails($mut, $order['id']);
3717 $mut->stop();
3718 $tplVars = CRM_Core_Smarty::singleton()->get_template_vars();
3719 $this->assertEquals('bob', $tplVars['billingName']);
3720 }
3721
3722 /**
3723 * Test sending a mail via the API.
3724 * This simulates webform_civicrm using pay later contribution page
3725 *
3726 * @throws \CRM_Core_Exception
3727 * @throws \CiviCRM_API3_Exception
3728 */
3729 public function testSendConfirmationPayLater(): void {
3730 $mut = new CiviMailUtils($this, TRUE);
3731 // This probably needs to call the order api in order to generate valid financial entities.
3732 $this->isValidateFinancialsOnPostAssert = FALSE;
3733 // Create contribution page
3734 $pageParams = [
3735 'title' => 'Webform Contributions',
3736 'financial_type_id' => 1,
3737 'contribution_type_id' => 1,
3738 'is_confirm_enabled' => 1,
3739 'is_pay_later' => 1,
3740 'pay_later_text' => 'I will send payment by cheque',
3741 'pay_later_receipt' => 'Send your cheque payable to "CiviCRM LLC" to the office',
3742 ];
3743 $contributionPage = $this->callAPISuccess('contribution_page', 'create', $pageParams);
3744
3745 // Create pay later contribution
3746 $contribParams = [
3747 'contact_id' => $this->_individualId,
3748 'financial_type_id' => 1,
3749 'is_pay_later' => 1,
3750 'contribution_status_id' => 2,
3751 'contribution_page_id' => $contributionPage['id'],
3752 'total_amount' => '10.00',
3753 ];
3754 $contribution = $this->callAPISuccess('contribution', 'create', $contribParams);
3755
3756 // Create line item
3757 $lineItemParams = [
3758 'contribution_id' => $contribution['id'],
3759 'entity_id' => $contribution['id'],
3760 'entity_table' => 'civicrm_contribution',
3761 'label' => 'My lineitem label',
3762 'qty' => 1,
3763 'unit_price' => '10.00',
3764 'line_total' => '10.00',
3765 ];
3766 $this->callAPISuccess('LineItem', 'create', $lineItemParams);
3767
3768 // Create email
3769 try {
3770 civicrm_api3('contribution', 'sendconfirmation', [
3771 'id' => $contribution['id'],
3772 'receipt_from_email' => 'api@civicrm.org',
3773 ]);
3774 }
3775 catch (Exception $e) {
3776 // Need to figure out how to stop this some other day
3777 // We don't care about the Payment Processor because this is Pay Later
3778 // The point of this test is to check we get the pay_later version of the mail
3779 if ($e->getMessage() !== "Undefined variable: CRM16923AnUnreliableMethodHasBeenUserToDeterminePaymentProcessorFromContributionPage") {
3780 throw $e;
3781 }
3782 }
3783
3784 // Retrieve mail & check it has the pay_later_receipt info
3785 $mut->getMostRecentEmail('raw');
3786 $mut->checkMailLog([
3787 (string) $contribParams['total_amount'],
3788 $pageParams['pay_later_receipt'],
3789 ], [
3790 'Event',
3791 ]);
3792 $this->checkReceiptDetails($mut, $contributionPage['id'], $contribution['id'], $pageParams);
3793 $mut->stop();
3794 }
3795
3796 /**
3797 * Check credit card details in sent mail via API
3798 *
3799 * @param CiviMailUtils $mut
3800 * @param int $contributionID Contribution ID
3801 *
3802 * @throws \CRM_Core_Exception
3803 */
3804 public function checkCreditCardDetails($mut, $contributionID) {
3805 $this->callAPISuccess('contribution', 'create', $this->_params);
3806 $this->callAPISuccess('contribution', 'sendconfirmation', [
3807 'id' => $contributionID,
3808 'receipt_from_email' => 'api@civicrm.org',
3809 'payment_processor_id' => $this->paymentProcessorID,
3810 ]);
3811 $mut->checkMailLog([
3812 // billing header
3813 'Billing Name and Address',
3814 // billing name
3815 'anthony_anderson@civicrm.org',
3816 ], [
3817 'Event',
3818 ]);
3819 }
3820
3821 /**
3822 * Check receipt details in sent mail via API
3823 *
3824 * @param CiviMailUtils $mut
3825 * @param int $pageID Page ID
3826 * @param int $contributionID Contribution ID
3827 * @param array $pageParams
3828 *
3829 * @throws \CRM_Core_Exception
3830 */
3831 public function checkReceiptDetails($mut, $pageID, $contributionID, $pageParams): void {
3832 $pageReceipt = [
3833 'receipt_from_name' => 'Page FromName',
3834 'receipt_from_email' => 'page_from@email.com',
3835 'cc_receipt' => 'page_cc@email.com',
3836 'receipt_text' => 'Page Receipt Text',
3837 'pay_later_receipt' => $pageParams['pay_later_receipt'],
3838 ];
3839 $customReceipt = [
3840 'receipt_from_name' => 'Custom FromName',
3841 'receipt_from_email' => 'custom_from@email.com',
3842 'cc_receipt' => 'custom_cc@email.com',
3843 'receipt_text' => 'Test Custom Receipt Text',
3844 'pay_later_receipt' => 'Mail your check to test@example.com within 3 business days.',
3845 ];
3846 $this->callAPISuccess('ContributionPage', 'create', array_merge([
3847 'id' => $pageID,
3848 'is_email_receipt' => 1,
3849 ], $pageReceipt));
3850
3851 $this->callAPISuccess('contribution', 'sendconfirmation', array_merge([
3852 'id' => $contributionID,
3853 'payment_processor_id' => $this->paymentProcessorID,
3854 ], $customReceipt));
3855
3856 //Verify if custom receipt details are present in email.
3857 //Page receipt details shouldn't be included.
3858 $mut->checkMailLog(array_values($customReceipt), array_values($pageReceipt));
3859 }
3860
3861 /**
3862 * Test sending a mail via the API.
3863 */
3864 public function testSendMailEvent() {
3865 $mut = new CiviMailUtils($this, TRUE);
3866 $contribution = $this->callAPISuccess('contribution', 'create', $this->_params);
3867 $event = $this->eventCreate([
3868 'is_email_confirm' => 1,
3869 'confirm_from_email' => 'test@civicrm.org',
3870 ]);
3871 $this->_eventID = $event['id'];
3872 $participantParams = [
3873 'contact_id' => $this->_individualId,
3874 'event_id' => $this->_eventID,
3875 'status_id' => 1,
3876 'role_id' => 1,
3877 // to ensure it matches later on
3878 'register_date' => '2007-07-21 00:00:00',
3879 'source' => 'Online Event Registration: API Testing',
3880
3881 ];
3882 $participant = $this->callAPISuccess('participant', 'create', $participantParams);
3883 $this->callAPISuccess('participant_payment', 'create', [
3884 'participant_id' => $participant['id'],
3885 'contribution_id' => $contribution['id'],
3886 ]);
3887 $this->callAPISuccess('contribution', 'sendconfirmation', [
3888 'id' => $contribution['id'],
3889 'receipt_from_email' => 'api@civicrm.org',
3890 ]);
3891
3892 $mut->checkMailLog([
3893 'Annual CiviCRM meet',
3894 'Event',
3895 'To: "Mr. Anthony Anderson II" <anthony_anderson@civicrm.org>',
3896 ], []);
3897 $mut->stop();
3898 }
3899
3900 /**
3901 * This function does a GET & compares the result against the $params.
3902 *
3903 * Use as a double check on Creates.
3904 *
3905 * @param array $params
3906 * @param int $id
3907 * @param bool $delete
3908 *
3909 * @throws \CRM_Core_Exception
3910 */
3911 public function contributionGetnCheck(array $params, int $id, bool $delete = TRUE): void {
3912 $contribution = $this->callAPISuccess('Contribution', 'Get', [
3913 'id' => $id,
3914 ]);
3915
3916 if ($delete) {
3917 $this->callAPISuccess('contribution', 'delete', ['id' => $id]);
3918 }
3919 $this->assertAPISuccess($contribution, 0);
3920 $values = $contribution['values'][$contribution['id']];
3921 $params['receive_date'] = date('Y-m-d H:i:s', strtotime($params['receive_date']));
3922 // this is not returned in id format
3923 unset($params['payment_instrument_id']);
3924 $params['contribution_source'] = $params['source'];
3925 unset($params['source'], $params['sequential']);
3926 foreach ($params as $key => $value) {
3927 $this->assertEquals($value, $values[$key], $key . " value: $value doesn't match " . print_r($values, TRUE));
3928 }
3929 }
3930
3931 /**
3932 * Create a pending contribution & linked pending pledge record.
3933 *
3934 * @throws \CRM_Core_Exception
3935 */
3936 public function createPendingPledgeContribution() {
3937
3938 $pledgeID = $this->pledgeCreate(['contact_id' => $this->_individualId, 'installments' => 1, 'amount' => 500]);
3939 $this->_ids['pledge'] = $pledgeID;
3940 $contribution = $this->callAPISuccess('Contribution', 'create', array_merge($this->_params, [
3941 'contribution_status_id' => 'Pending',
3942 'total_amount' => 500,
3943 ]));
3944 $paymentID = $this->callAPISuccessGetValue('PledgePayment', [
3945 'options' => ['limit' => 1],
3946 'return' => 'id',
3947 ]);
3948 $this->callAPISuccess('PledgePayment', 'create', [
3949 'id' => $paymentID,
3950 'contribution_id' =>
3951 $contribution['id'],
3952 'status_id' => 'Pending',
3953 'scheduled_amount' => 500,
3954 ]);
3955
3956 return $contribution['id'];
3957 }
3958
3959 /**
3960 * Create a pending contribution & linked pending participant record (along
3961 * with an event).
3962 *
3963 * @throws \CRM_Core_Exception
3964 */
3965 public function createPendingParticipantContribution() {
3966 $this->_ids['event']['test'] = $this->eventCreate(['is_email_confirm' => 1, 'confirm_from_email' => 'test@civicrm.org'])['id'];
3967 $participantID = $this->participantCreate(['event_id' => $this->_ids['event']['test'], 'status_id' => 6, 'contact_id' => $this->_individualId]);
3968 $this->_ids['participant'] = $participantID;
3969 $params = array_merge($this->_params, ['contact_id' => $this->_individualId, 'contribution_status_id' => 2, 'financial_type_id' => 'Event Fee']);
3970 $contribution = $this->callAPISuccess('contribution', 'create', $params);
3971 $this->callAPISuccess('participant_payment', 'create', [
3972 'contribution_id' => $contribution['id'],
3973 'participant_id' => $participantID,
3974 ]);
3975 $this->callAPISuccess('line_item', 'get', [
3976 'entity_id' => $contribution['id'],
3977 'entity_table' => 'civicrm_contribution',
3978 'api.line_item.create' => [
3979 'entity_id' => $participantID,
3980 'entity_table' => 'civicrm_participant',
3981 ],
3982 ]);
3983 return $contribution['id'];
3984 }
3985
3986 /**
3987 * Get financial transaction amount.
3988 *
3989 * @param int $contId
3990 *
3991 * @return null|string
3992 */
3993 public function _getFinancialTrxnAmount($contId) {
3994 $query = "SELECT
3995 SUM( ft.total_amount ) AS total
3996 FROM civicrm_financial_trxn AS ft
3997 LEFT JOIN civicrm_entity_financial_trxn AS ceft ON ft.id = ceft.financial_trxn_id
3998 WHERE ceft.entity_table = 'civicrm_contribution'
3999 AND ceft.entity_id = {$contId}";
4000
4001 return CRM_Core_DAO::singleValueQuery($query);
4002 }
4003
4004 /**
4005 * @param int $contId
4006 *
4007 * @return null|string
4008 */
4009 public function _getFinancialItemAmount($contId) {
4010 $lineItem = key(CRM_Price_BAO_LineItem::getLineItems($contId, 'contribution'));
4011 $query = "SELECT
4012 SUM(amount)
4013 FROM civicrm_financial_item
4014 WHERE entity_table = 'civicrm_line_item'
4015 AND entity_id = {$lineItem}";
4016 return CRM_Core_DAO::singleValueQuery($query);
4017 }
4018
4019 /**
4020 * @param int $contId
4021 * @param $context
4022 */
4023 public function _checkFinancialItem($contId, $context) {
4024 if ($context !== 'paylater') {
4025 $params = [
4026 'entity_id' => $contId,
4027 'entity_table' => 'civicrm_contribution',
4028 ];
4029 $trxn = current(CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($params, TRUE));
4030 $entityParams = [
4031 'financial_trxn_id' => $trxn['financial_trxn_id'],
4032 'entity_table' => 'civicrm_financial_item',
4033 ];
4034 $entityTrxn = current(CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($entityParams));
4035 $params = [
4036 'id' => $entityTrxn['entity_id'],
4037 ];
4038 }
4039 if ($context === 'paylater') {
4040 $lineItems = CRM_Price_BAO_LineItem::getLineItems($contId, 'contribution');
4041 foreach ($lineItems as $key => $item) {
4042 $params = [
4043 'entity_id' => $key,
4044 'entity_table' => 'civicrm_line_item',
4045 ];
4046 $compareParams = ['status_id' => 1];
4047 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $params, $compareParams);
4048 }
4049 }
4050 elseif ($context === 'refund') {
4051 $compareParams = [
4052 'status_id' => 1,
4053 'financial_account_id' => 1,
4054 'amount' => -100,
4055 ];
4056 }
4057 elseif ($context === 'cancelPending') {
4058 $compareParams = [
4059 'status_id' => 3,
4060 'financial_account_id' => 1,
4061 'amount' => -100,
4062 ];
4063 }
4064 elseif ($context === 'changeFinancial') {
4065 $lineKey = key(CRM_Price_BAO_LineItem::getLineItems($contId, 'contribution'));
4066 $params = [
4067 'entity_id' => $lineKey,
4068 'amount' => -100,
4069 ];
4070 $compareParams = [
4071 'financial_account_id' => 1,
4072 ];
4073 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $params, $compareParams);
4074 $params = [
4075 'financial_account_id' => 3,
4076 'entity_id' => $lineKey,
4077 ];
4078 $compareParams = [
4079 'amount' => 100,
4080 ];
4081 }
4082 if ($context !== 'paylater') {
4083 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $params, $compareParams);
4084 }
4085 }
4086
4087 /**
4088 * Check correct financial transaction entries were created for the change in payment instrument.
4089 *
4090 * @param int $contributionID
4091 * @param int $originalInstrumentID
4092 * @param int $newInstrumentID
4093 * @param int $amount
4094 */
4095 public function checkFinancialTrxnPaymentInstrumentChange($contributionID, $originalInstrumentID, $newInstrumentID, $amount = 100) {
4096
4097 $entityFinancialTrxns = $this->getFinancialTransactionsForContribution($contributionID);
4098
4099 $originalTrxnParams = [
4100 'to_financial_account_id' => CRM_Financial_BAO_FinancialTypeAccount::getInstrumentFinancialAccount($originalInstrumentID),
4101 'payment_instrument_id' => $originalInstrumentID,
4102 'amount' => $amount,
4103 'status_id' => 1,
4104 ];
4105
4106 $reversalTrxnParams = [
4107 'to_financial_account_id' => CRM_Financial_BAO_FinancialTypeAccount::getInstrumentFinancialAccount($originalInstrumentID),
4108 'payment_instrument_id' => $originalInstrumentID,
4109 'amount' => -$amount,
4110 'status_id' => 1,
4111 ];
4112
4113 $newTrxnParams = [
4114 'to_financial_account_id' => CRM_Financial_BAO_FinancialTypeAccount::getInstrumentFinancialAccount($newInstrumentID),
4115 'payment_instrument_id' => $newInstrumentID,
4116 'amount' => $amount,
4117 'status_id' => 1,
4118 ];
4119
4120 foreach ([$originalTrxnParams, $reversalTrxnParams, $newTrxnParams] as $index => $transaction) {
4121 $entityFinancialTrxn = $entityFinancialTrxns[$index];
4122 $this->assertEquals($entityFinancialTrxn['amount'], $transaction['amount']);
4123
4124 $financialTrxn = $this->callAPISuccessGetSingle('FinancialTrxn', [
4125 'id' => $entityFinancialTrxn['financial_trxn_id'],
4126 ]);
4127 $this->assertEquals($transaction['status_id'], $financialTrxn['status_id']);
4128 $this->assertEquals($transaction['amount'], $financialTrxn['total_amount']);
4129 $this->assertEquals($transaction['amount'], $financialTrxn['net_amount']);
4130 $this->assertEquals(0, $financialTrxn['fee_amount']);
4131 $this->assertEquals($transaction['payment_instrument_id'], $financialTrxn['payment_instrument_id']);
4132 $this->assertEquals($transaction['to_financial_account_id'], $financialTrxn['to_financial_account_id']);
4133
4134 // Generic checks.
4135 $this->assertEquals(1, $financialTrxn['is_payment']);
4136 $this->assertEquals('USD', $financialTrxn['currency']);
4137 $this->assertEquals(date('Y-m-d'), date('Y-m-d', strtotime($financialTrxn['trxn_date'])));
4138 }
4139 }
4140
4141 /**
4142 * Check financial transaction.
4143 *
4144 * @todo break this down into sensible functions - most calls to it only use a few lines out of the big if.
4145 *
4146 * @param array $contribution
4147 * @param string $context
4148 * @param int $instrumentId
4149 * @param array $extraParams
4150 */
4151 public function _checkFinancialTrxn($contribution, $context, $instrumentId = NULL, $extraParams = []) {
4152 $financialTrxns = $this->getFinancialTransactionsForContribution($contribution['id']);
4153 $trxn = array_pop($financialTrxns);
4154
4155 $params = [
4156 'id' => $trxn['financial_trxn_id'],
4157 ];
4158 if ($context === 'payLater') {
4159 $compareParams = [
4160 'status_id' => 1,
4161 'from_financial_account_id' => CRM_Contribute_PseudoConstant::getRelationalFinancialAccount($contribution['financial_type_id'], 'Accounts Receivable Account is'),
4162 ];
4163 }
4164 elseif ($context === 'refund') {
4165 $compareParams = [
4166 'to_financial_account_id' => 6,
4167 'total_amount' => -100,
4168 'status_id' => 7,
4169 'trxn_date' => '2015-01-01 09:00:00',
4170 'trxn_id' => 'the refund',
4171 ];
4172 }
4173 elseif ($context === 'cancelPending') {
4174 $compareParams = [
4175 'to_financial_account_id' => 7,
4176 'total_amount' => -100,
4177 'status_id' => 3,
4178 ];
4179 }
4180 elseif ($context === 'changeFinancial' || $context === 'paymentInstrument') {
4181 // @todo checkFinancialTrxnPaymentInstrumentChange instead for paymentInstrument.
4182 // It does the same thing with greater readability.
4183 // @todo remove handling for
4184
4185 $entityParams = [
4186 'entity_id' => $contribution['id'],
4187 'entity_table' => 'civicrm_contribution',
4188 'amount' => -100,
4189 ];
4190 $trxn = current(CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($entityParams));
4191 $trxnParams1 = [
4192 'id' => $trxn['financial_trxn_id'],
4193 ];
4194 if (empty($extraParams)) {
4195 $compareParams = [
4196 'total_amount' => -100,
4197 'status_id' => 1,
4198 ];
4199 }
4200 elseif ($context !== 'changeFinancial') {
4201 $compareParams = [
4202 'total_amount' => 100,
4203 'status_id' => 1,
4204 ];
4205 }
4206 if ($context === 'paymentInstrument') {
4207 $compareParams['to_financial_account_id'] = CRM_Financial_BAO_FinancialTypeAccount::getInstrumentFinancialAccount($instrumentId);
4208 $compareParams['payment_instrument_id'] = $instrumentId;
4209 }
4210 else {
4211 $compareParams['to_financial_account_id'] = 12;
4212 }
4213 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams1, array_merge($compareParams, $extraParams));
4214 $compareParams['total_amount'] = 100;
4215 // Reverse the extra params now that we will be checking the new positive transaction.
4216 if ($context === 'changeFinancial' && !empty($extraParams)) {
4217 foreach ($extraParams as $param => $value) {
4218 $extraParams[$param] = 0 - $value;
4219 }
4220 }
4221 }
4222
4223 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $params, array_merge($compareParams, $extraParams));
4224 }
4225
4226 /**
4227 * @return mixed
4228 */
4229 public function _addPaymentInstrument() {
4230 $gId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', 'payment_instrument', 'id', 'name');
4231 $optionParams = [
4232 'option_group_id' => $gId,
4233 'label' => 'Test Card',
4234 'name' => 'Test Card',
4235 'value' => '6',
4236 'weight' => '6',
4237 'is_active' => 1,
4238 ];
4239 $optionValue = $this->callAPISuccess('option_value', 'create', $optionParams);
4240 $relationTypeId = key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Asset Account is' "));
4241 $financialParams = [
4242 'entity_table' => 'civicrm_option_value',
4243 'entity_id' => $optionValue['id'],
4244 'account_relationship' => $relationTypeId,
4245 'financial_account_id' => 7,
4246 ];
4247 CRM_Financial_BAO_FinancialTypeAccount::add($financialParams);
4248 $this->assertNotEmpty($optionValue['values'][$optionValue['id']]['value']);
4249 return $optionValue['values'][$optionValue['id']]['value'];
4250 }
4251
4252 public function _deletedAddedPaymentInstrument() {
4253 $result = $this->callAPISuccess('OptionValue', 'get', [
4254 'option_group_id' => 'payment_instrument',
4255 'name' => 'Test Card',
4256 'value' => '6',
4257 'is_active' => 1,
4258 ]);
4259 if ($id = CRM_Utils_Array::value('id', $result)) {
4260 $this->callAPISuccess('OptionValue', 'delete', ['id' => $id]);
4261 }
4262 }
4263
4264 /**
4265 * Set up the basic recurring contribution for tests.
4266 *
4267 * @param array $generalParams
4268 * Parameters that can be merged into the recurring AND the contribution.
4269 *
4270 * @param array $recurParams
4271 * Parameters to merge into the recur only.
4272 *
4273 * @return array|int
4274 * @throws \CRM_Core_Exception
4275 */
4276 protected function setUpRecurringContribution($generalParams = [], $recurParams = []) {
4277 $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', array_merge([
4278 'contact_id' => $this->_individualId,
4279 'installments' => '12',
4280 'frequency_interval' => '1',
4281 'amount' => '100',
4282 'contribution_status_id' => 1,
4283 'start_date' => '2012-01-01 00:00:00',
4284 'currency' => 'USD',
4285 'frequency_unit' => 'month',
4286 'payment_processor_id' => $this->paymentProcessorID,
4287 ], $generalParams, $recurParams));
4288 $contributionParams = array_merge(
4289 $this->_params,
4290 [
4291 'contribution_recur_id' => $contributionRecur['id'],
4292 'contribution_status_id' => 'Pending',
4293 ], $generalParams);
4294 $contributionParams['api.Payment.create'] = ['total_amount' => $contributionParams['total_amount']];
4295 $originalContribution = $this->callAPISuccess('Order', 'create', $contributionParams);
4296 return $originalContribution;
4297 }
4298
4299 /**
4300 * Set up a basic auto-renew membership for tests.
4301 *
4302 * @param array $generalParams
4303 * Parameters that can be merged into the recurring AND the contribution.
4304 *
4305 * @param array $recurParams
4306 * Parameters to merge into the recur only.
4307 *
4308 * @return array|int
4309 * @throws \CRM_Core_Exception
4310 */
4311 protected function setUpAutoRenewMembership($generalParams = [], $recurParams = []) {
4312 $newContact = $this->callAPISuccess('Contact', 'create', [
4313 'contact_type' => 'Individual',
4314 'sort_name' => 'McTesterson, Testy',
4315 'display_name' => 'Testy McTesterson',
4316 'preferred_language' => 'en_US',
4317 'preferred_mail_format' => 'Both',
4318 'first_name' => 'Testy',
4319 'last_name' => 'McTesterson',
4320 'contact_is_deleted' => '0',
4321 'email_id' => '4',
4322 'email' => 'tmctesterson@example.com',
4323 'on_hold' => '0',
4324 ]);
4325 $membershipType = $this->callAPISuccess('MembershipType', 'create', [
4326 'domain_id' => 'Default Domain Name',
4327 'member_of_contact_id' => 1,
4328 'financial_type_id' => 'Member Dues',
4329 'duration_unit' => 'month',
4330 'duration_interval' => 1,
4331 'period_type' => 'rolling',
4332 'name' => 'Standard Member',
4333 'minimum_fee' => 100,
4334 ]);
4335 $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', array_merge([
4336 'contact_id' => $newContact['id'],
4337 'installments' => '12',
4338 'frequency_interval' => '1',
4339 'amount' => '100',
4340 'contribution_status_id' => 1,
4341 'start_date' => '2012-01-01 00:00:00',
4342 'currency' => 'USD',
4343 'frequency_unit' => 'month',
4344 'payment_processor_id' => $this->paymentProcessorID,
4345 ], $generalParams, $recurParams));
4346
4347 $this->callAPISuccess('membership', 'create', [
4348 'contact_id' => $newContact['id'],
4349 'contribution_recur_id' => $contributionRecur['id'],
4350 'financial_type_id' => 'Member Dues',
4351 'membership_type_id' => $membershipType['id'],
4352 'num_terms' => 1,
4353 'skipLineItem' => TRUE,
4354 ]);
4355
4356 CRM_Price_BAO_LineItem::getLineItemArray($this->_params, NULL, 'membership', $membershipType['id']);
4357 $originalContribution = $this->callAPISuccess('contribution', 'create', array_merge(
4358 $this->_params,
4359 [
4360 'contact_id' => $newContact['id'],
4361 'contribution_recur_id' => $contributionRecur['id'],
4362 'financial_type_id' => 'Member Dues',
4363 'contribution_status_id' => 1,
4364 'invoice_id' => 2345,
4365 ], $generalParams)
4366 );
4367 $lineItem = $this->callAPISuccess('LineItem', 'getsingle', []);
4368 $this->assertEquals('civicrm_membership', $lineItem['entity_table']);
4369 $membership = $this->callAPISuccess('Membership', 'getsingle', ['id' => $lineItem['entity_id']]);
4370 $this->callAPISuccess('LineItem', 'getsingle', []);
4371 $this->callAPISuccessGetCount('MembershipPayment', ['membership_id' => $membership['id']], 1);
4372
4373 return [$originalContribution, $membership];
4374 }
4375
4376 /**
4377 * Set up a repeat transaction.
4378 *
4379 * @param array $recurParams
4380 * @param mixed $flag
4381 * @param array $contributionParams
4382 *
4383 * @return array
4384 * @throws \CRM_Core_Exception
4385 */
4386 protected function setUpRepeatTransaction($recurParams, $flag, $contributionParams = []) {
4387 $paymentProcessorID = $this->paymentProcessorCreate();
4388 $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', array_merge([
4389 'contact_id' => $this->_individualId,
4390 'installments' => '12',
4391 'frequency_interval' => '1',
4392 'amount' => '500',
4393 'contribution_status_id' => 1,
4394 'start_date' => '2012-01-01 00:00:00',
4395 'currency' => 'USD',
4396 'frequency_unit' => 'month',
4397 'payment_processor_id' => $paymentProcessorID,
4398 ], $recurParams));
4399
4400 $originalContribution = '';
4401 if ($flag === 'multiple') {
4402 // CRM-19309 create a contribution + also add in line_items (plural):
4403 $params = array_merge($this->_params, $contributionParams);
4404 $originalContribution = $this->callAPISuccess('contribution', 'create', array_merge(
4405 $params,
4406 [
4407 'contribution_recur_id' => $contributionRecur['id'],
4408 'skipLineItem' => 1,
4409 'api.line_item.create' => [
4410 [
4411 'price_field_id' => 1,
4412 'qty' => 2,
4413 'line_total' => '20',
4414 'unit_price' => '10',
4415 'financial_type_id' => 1,
4416 ],
4417 [
4418 'price_field_id' => 1,
4419 'qty' => 1,
4420 'line_total' => '80',
4421 'unit_price' => '80',
4422 'financial_type_id' => 2,
4423 ],
4424 ],
4425 ]
4426 )
4427 );
4428 }
4429 elseif ($flag === 'single') {
4430 $params = array_merge($this->_params, ['contribution_recur_id' => $contributionRecur['id']]);
4431 $params = array_merge($params, $contributionParams);
4432 $originalContribution = $this->callAPISuccess('contribution', 'create', $params);
4433 }
4434 $originalContribution['contribution_recur_id'] = $contributionRecur['id'];
4435 $originalContribution['payment_processor_id'] = $paymentProcessorID;
4436 return $originalContribution;
4437 }
4438
4439 /**
4440 * Common set up routine.
4441 *
4442 * @return array
4443 * @throws \CRM_Core_Exception
4444 */
4445 protected function setUpForCompleteTransaction(): array {
4446 $this->mut = new CiviMailUtils($this, TRUE);
4447 $this->createLoggedInUser();
4448 $params = array_merge($this->_params, ['contribution_status_id' => 2, 'receipt_date' => 'now']);
4449 return $this->callAPISuccess('Contribution', 'create', $params);
4450 }
4451
4452 /**
4453 * Test repeat contribution uses the Payment Processor' payment_instrument setting.
4454 *
4455 * @throws \CRM_Core_Exception
4456 */
4457 public function testRepeatTransactionWithNonCreditCardDefault() {
4458 $contributionRecur = $this->callAPISuccess('ContributionRecur', 'create', [
4459 'contact_id' => $this->_individualId,
4460 'installments' => '12',
4461 'frequency_interval' => '1',
4462 'amount' => '100',
4463 'contribution_status_id' => 1,
4464 'start_date' => '2012-01-01 00:00:00',
4465 'currency' => 'USD',
4466 'frequency_unit' => 'month',
4467 'payment_processor_id' => $this->paymentProcessorID,
4468 ]);
4469 $contribution1 = $this->callAPISuccess('contribution', 'create', array_merge(
4470 $this->_params,
4471 ['contribution_recur_id' => $contributionRecur['id'], 'payment_instrument_id' => 2])
4472 );
4473 $contribution2 = $this->callAPISuccess('contribution', 'repeattransaction', [
4474 'contribution_status_id' => 'Completed',
4475 'trxn_id' => 'blah',
4476 'original_contribution_id' => $contribution1,
4477 ]);
4478 $this->assertEquals('Debit Card', CRM_Contribute_PseudoConstant::getLabel('CRM_Contribute_BAO_Contribution', 'payment_instrument_id', $contribution2['values'][$contribution2['id']]['payment_instrument_id']));
4479 }
4480
4481 /**
4482 * CRM-20008 Tests repeattransaction creates pending membership.
4483 *
4484 * @throws \CRM_Core_Exception
4485 */
4486 public function testRepeatTransactionMembershipCreatePendingContribution(): void {
4487 [$originalContribution, $membership] = $this->setUpAutoRenewMembership();
4488 $this->callAPISuccess('membership', 'create', [
4489 'id' => $membership['id'],
4490 'end_date' => 'yesterday',
4491 'status_id' => 'Expired',
4492 ]);
4493 $repeatedContribution = $this->callAPISuccess('contribution', 'repeattransaction', [
4494 'contribution_recur_id' => $originalContribution['values'][1]['contribution_recur_id'],
4495 'contribution_status_id' => 'Pending',
4496 'trxn_id' => 1234,
4497 ]);
4498 $membershipStatusId = $this->callAPISuccess('membership', 'getvalue', [
4499 'id' => $membership['id'],
4500 'return' => 'status_id',
4501 ]);
4502
4503 // Let's see if the membership payments got created while we're at it.
4504 $membershipPayments = $this->callAPISuccess('MembershipPayment', 'get', [
4505 'membership_id' => $membership['id'],
4506 ]);
4507 $this->assertEquals(2, $membershipPayments['count']);
4508
4509 $this->assertEquals('Expired', CRM_Core_PseudoConstant::getLabel('CRM_Member_BAO_Membership', 'status_id', $membershipStatusId));
4510 $this->callAPISuccess('Contribution', 'completetransaction', ['id' => $repeatedContribution['id']]);
4511 $membership = $this->callAPISuccessGetSingle('membership', [
4512 'id' => $membership['id'],
4513 'return' => 'status_id, end_date',
4514 ]);
4515 $this->assertEquals('New', CRM_Core_PseudoConstant::getName('CRM_Member_BAO_Membership', 'status_id', $membership['status_id']));
4516 $this->assertEquals(date('Y-m-d', strtotime('yesterday + 1 month')), $membership['end_date']);
4517 }
4518
4519 /**
4520 * Test sending a mail via the API.
4521 *
4522 * @throws \CRM_Core_Exception
4523 */
4524 public function testSendMailWithAPISetFromDetails() {
4525 $mut = new CiviMailUtils($this, TRUE);
4526 $contribution = $this->callAPISuccess('contribution', 'create', $this->_params);
4527 $this->callAPISuccess('contribution', 'sendconfirmation', [
4528 'id' => $contribution['id'],
4529 'receipt_from_email' => 'api@civicrm.org',
4530 'receipt_from_name' => 'CiviCRM LLC',
4531 ]);
4532 $mut->checkMailLog([
4533 'From: CiviCRM LLC <api@civicrm.org>',
4534 'Contribution Information',
4535 ], [
4536 'Event',
4537 ]);
4538 $mut->stop();
4539 }
4540
4541 /**
4542 * Test sending a mail via the API.
4543 */
4544 public function testSendMailWithNoFromSetFallToDomain() {
4545 $this->createLoggedInUser();
4546 $mut = new CiviMailUtils($this, TRUE);
4547 $contribution = $this->callAPISuccess('contribution', 'create', $this->_params);
4548 $this->callAPISuccess('contribution', 'sendconfirmation', [
4549 'id' => $contribution['id'],
4550 ]);
4551 $domain = $this->callAPISuccess('domain', 'getsingle', ['id' => 1]);
4552 $mut->checkMailLog([
4553 'From: ' . $domain['from_name'] . ' <' . $domain['from_email'] . '>',
4554 'Contribution Information',
4555 ], [
4556 'Event',
4557 ]);
4558 $mut->stop();
4559 }
4560
4561 /**
4562 * Test sending a mail via the API.
4563 *
4564 * @throws \CRM_Core_Exception
4565 */
4566 public function testSendMailWithRepeatTransactionAPIFalltoDomain() {
4567 $this->createLoggedInUser();
4568 $mut = new CiviMailUtils($this, TRUE);
4569 $contribution = $this->setUpRepeatTransaction([], 'single');
4570 $this->callAPISuccess('contribution', 'repeattransaction', [
4571 'contribution_status_id' => 'Completed',
4572 'trxn_id' => 7890,
4573 'original_contribution_id' => $contribution,
4574 ]);
4575 $domain = $this->callAPISuccess('domain', 'getsingle', ['id' => 1]);
4576 $mut->checkMailLog([
4577 'From: ' . $domain['from_name'] . ' <' . $domain['from_email'] . '>',
4578 'Contribution Information',
4579 ], [
4580 'Event',
4581 ]
4582 );
4583 $mut->stop();
4584 }
4585
4586 /**
4587 * Test sending a mail via the API.
4588 *
4589 * @throws \CRM_Core_Exception
4590 */
4591 public function testSendMailWithRepeatTransactionAPIFalltoContributionPage() {
4592 $mut = new CiviMailUtils($this, TRUE);
4593 $contributionPage = $this->contributionPageCreate(['receipt_from_name' => 'CiviCRM LLC', 'receipt_from_email' => 'contributionpage@civicrm.org', 'is_email_receipt' => 1]);
4594 $paymentProcessorID = $this->paymentProcessorCreate();
4595 $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', [
4596 'contact_id' => $this->_individualId,
4597 'installments' => '12',
4598 'frequency_interval' => '1',
4599 'amount' => '500',
4600 'contribution_status_id' => 1,
4601 'start_date' => '2012-01-01 00:00:00',
4602 'currency' => 'USD',
4603 'frequency_unit' => 'month',
4604 'payment_processor_id' => $paymentProcessorID,
4605 ]);
4606 $originalContribution = $this->callAPISuccess('contribution', 'create', array_merge(
4607 $this->_params,
4608 [
4609 'contribution_recur_id' => $contributionRecur['id'],
4610 'contribution_page_id' => $contributionPage['id'],
4611 ])
4612 );
4613 $this->callAPISuccess('Contribution', 'repeattransaction', [
4614 'contribution_status_id' => 'Completed',
4615 'trxn_id' => 5678,
4616 'original_contribution_id' => $originalContribution,
4617 ]
4618 );
4619 $mut->checkMailLog([
4620 'From: CiviCRM LLC <contributionpage@civicrm.org>',
4621 'Contribution Information',
4622 ], [
4623 'Event',
4624 ]);
4625 $mut->stop();
4626 }
4627
4628 /**
4629 * Test sending a mail via the API.
4630 *
4631 * @throws \CRM_Core_Exception
4632 */
4633 public function testSendMailWithRepeatTransactionAPIFalltoSystemFromNoDefaultFrom(): void {
4634 $mut = new CiviMailUtils($this, TRUE);
4635 $originalContribution = $this->setUpRepeatTransaction([], 'single');
4636 $fromEmail = $this->callAPISuccess('optionValue', 'get', ['is_default' => 1, 'option_group_id' => 'from_email_address', 'sequential' => 1]);
4637 foreach ($fromEmail['values'] as $from) {
4638 $this->callAPISuccess('optionValue', 'create', ['is_default' => 0, 'id' => $from['id']]);
4639 }
4640 $domain = $this->callAPISuccess('domain', 'getsingle', ['id' => CRM_Core_Config::domainID()]);
4641 $this->callAPISuccess('contribution', 'repeattransaction', [
4642 'contribution_status_id' => 'Completed',
4643 'trxn_id' => 4567,
4644 'original_contribution_id' => $originalContribution,
4645 ]);
4646 $mut->checkMailLog([
4647 'From: ' . $domain['name'] . ' <' . $domain['domain_email'] . '>',
4648 'Contribution Information',
4649 ], [
4650 'Event',
4651 ]);
4652 $mut->stop();
4653 }
4654
4655 /**
4656 * Create a Contribution Page with is_email_receipt = TRUE.
4657 *
4658 * @param array $params
4659 * Params to overwrite with.
4660 *
4661 * @return array|int
4662 */
4663 protected function createReceiptableContributionPage($params = []) {
4664 $contributionPage = $this->callAPISuccess('ContributionPage', 'create', array_merge([
4665 'receipt_from_name' => 'Mickey Mouse',
4666 'receipt_from_email' => 'mickey@mouse.com',
4667 'title' => "Test Contribution Page",
4668 'financial_type_id' => 1,
4669 'currency' => 'CAD',
4670 'is_monetary' => TRUE,
4671 'is_email_receipt' => TRUE,
4672 ], $params));
4673 return $contributionPage;
4674 }
4675
4676 /**
4677 * function to test card_type and pan truncation.
4678 *
4679 * @throws \CRM_Core_Exception
4680 */
4681 public function testCardTypeAndPanTruncation() {
4682 $creditCardTypeIDs = array_flip(CRM_Financial_DAO_FinancialTrxn::buildOptions('card_type_id'));
4683 $contactId = $this->individualCreate();
4684 $params = [
4685 'contact_id' => $contactId,
4686 'receive_date' => '2016-01-20',
4687 'total_amount' => 100,
4688 'financial_type_id' => 1,
4689 'payment_instrument' => 'Credit Card',
4690 'card_type_id' => $creditCardTypeIDs['Visa'],
4691 'pan_truncation' => 4567,
4692 ];
4693 $contribution = $this->callAPISuccess('contribution', 'create', $params);
4694 $lastFinancialTrxnId = CRM_Core_BAO_FinancialTrxn::getFinancialTrxnId($contribution['id'], 'DESC');
4695 $financialTrxn = $this->callAPISuccessGetSingle(
4696 'FinancialTrxn',
4697 [
4698 'id' => $lastFinancialTrxnId['financialTrxnId'],
4699 'return' => ['card_type_id', 'pan_truncation'],
4700 ]
4701 );
4702 $this->assertEquals(CRM_Utils_Array::value('card_type_id', $financialTrxn), $creditCardTypeIDs['Visa']);
4703 $this->assertEquals(CRM_Utils_Array::value('pan_truncation', $financialTrxn), 4567);
4704 $params = [
4705 'id' => $contribution['id'],
4706 'pan_truncation' => 2345,
4707 'card_type_id' => $creditCardTypeIDs['Amex'],
4708 ];
4709 $this->callAPISuccess('contribution', 'create', $params);
4710 $financialTrxn = $this->callAPISuccessGetSingle(
4711 'FinancialTrxn',
4712 [
4713 'id' => $lastFinancialTrxnId['financialTrxnId'],
4714 'return' => ['card_type_id', 'pan_truncation'],
4715 ]
4716 );
4717 $this->assertEquals(CRM_Utils_Array::value('card_type_id', $financialTrxn), $creditCardTypeIDs['Amex']);
4718 $this->assertEquals(CRM_Utils_Array::value('pan_truncation', $financialTrxn), 2345);
4719 }
4720
4721 /**
4722 * Test repeat contribution uses non default currency
4723 *
4724 * @see https://issues.civicrm.org/jira/projects/CRM/issues/CRM-20678
4725 * @throws \CRM_Core_Exception
4726 */
4727 public function testRepeatTransactionWithDifferenceCurrency() {
4728 $originalContribution = $this->setUpRepeatTransaction(['currency' => 'AUD'], 'single', ['currency' => 'AUD']);
4729 $contribution = $this->callAPISuccess('Contribution', 'repeattransaction', [
4730 'original_contribution_id' => $originalContribution['id'],
4731 'contribution_status_id' => 'Completed',
4732 'trxn_id' => 3456,
4733 ]);
4734 $this->assertEquals('AUD', $contribution['values'][$contribution['id']]['currency']);
4735 }
4736
4737 /**
4738 * Get the financial items for the contribution.
4739 *
4740 * @param int $contributionID
4741 *
4742 * @return array
4743 * Array of associated financial items.
4744 */
4745 protected function getFinancialTransactionsForContribution($contributionID) {
4746 $trxnParams = [
4747 'entity_id' => $contributionID,
4748 'entity_table' => 'civicrm_contribution',
4749 ];
4750 // @todo the following function has naming errors & has a weird signature & appears to
4751 // only be called from test classes. Move into test suite & maybe just use api
4752 // from this function.
4753 return array_merge(CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($trxnParams));
4754 }
4755
4756 /**
4757 * Test getunique api call for Contribution entity
4758 */
4759 public function testContributionGetUnique() {
4760 $result = $this->callAPIAndDocument($this->entity, 'getunique', [], __FUNCTION__, __FILE__);
4761 $this->assertEquals(2, $result['count']);
4762 $this->assertEquals(['trxn_id'], $result['values']['UI_contrib_trxn_id']);
4763 $this->assertEquals(['invoice_id'], $result['values']['UI_contrib_invoice_id']);
4764 }
4765
4766 /**
4767 * Test Repeat Transaction Contribution with Tax amount.
4768 * https://lab.civicrm.org/dev/core/issues/806
4769 *
4770 * @throws \CRM_Core_Exception
4771 */
4772 public function testRepeatContributionWithTaxAmount(): void {
4773 $this->enableTaxAndInvoicing();
4774 $financialType = $this->callAPISuccess('financial_type', 'create', [
4775 'name' => 'Test taxable financial Type',
4776 'is_reserved' => 0,
4777 'is_active' => 1,
4778 ]);
4779 $this->addTaxAccountToFinancialType($financialType['id']);
4780 $contribution = $this->setUpRepeatTransaction(
4781 [],
4782 'single',
4783 [
4784 'financial_type_id' => $financialType['id'],
4785 ]
4786 );
4787 $this->callAPISuccess('contribution', 'repeattransaction', [
4788 'original_contribution_id' => $contribution['id'],
4789 'contribution_status_id' => 'Completed',
4790 'trxn_id' => 'test',
4791 ]);
4792 $payments = $this->callAPISuccess('Contribution', 'get', ['sequential' => 1])['values'];
4793 //Assert if first payment and repeated payment has the same contribution amount.
4794 $this->assertEquals($payments[0]['total_amount'], $payments[1]['total_amount']);
4795 $this->callAPISuccessGetCount('Contribution', [], 2);
4796
4797 //Assert line item records.
4798 $lineItems = $this->callAPISuccess('LineItem', 'get', ['sequential' => 1])['values'];
4799 foreach ($lineItems as $lineItem) {
4800 $this->assertEquals($lineItem['unit_price'], $this->_params['total_amount']);
4801 $this->assertEquals($lineItem['line_total'], $this->_params['total_amount']);
4802 }
4803 $this->callAPISuccessGetCount('Contribution', [], 2);
4804 }
4805
4806 public function testGetCurrencyOptions() {
4807 $result = $this->callAPISuccess('Contribution', 'getoptions', [
4808 'field' => 'currency',
4809 ]);
4810 $this->assertEquals('US Dollar', $result['values']['USD']);
4811 $this->assertNotContains('$', $result['values']);
4812 $result = $this->callAPISuccess('Contribution', 'getoptions', [
4813 'field' => 'currency',
4814 'context' => "abbreviate",
4815 ]);
4816 $this->assertEquals('$', $result['values']['USD']);
4817 $this->assertNotContains('US Dollar', $result['values']);
4818 }
4819
4820 /**
4821 * @throws \API_Exception
4822 * @throws \CRM_Core_Exception
4823 * @throws \Civi\API\Exception\UnauthorizedException
4824 */
4825 public function testSetCustomDataInCreateAndHook() {
4826 $this->createCustomGroupWithFieldOfType([], 'int');
4827 $this->ids['CustomField']['text'] = (int) $this->createTextCustomField(['custom_group_id' => $this->ids['CustomGroup']['Custom Group']])['id'];
4828 $this->hookClass->setHook('civicrm_post', [
4829 $this,
4830 'civicrmPostContributionCustom',
4831 ]);
4832 $params = $this->_params;
4833 $params['custom_' . $this->ids['CustomField']['text']] = 'Some Text';
4834 $contribution = $this->callAPISuccess('Contribution', 'create', $params);
4835 $getContribution = $this->callAPISuccess('Contribution', 'get', [
4836 'id' => $contribution['id'],
4837 'return' => ['id', 'custom_' . $this->ids['CustomField']['text'], 'custom_' . $this->ids['CustomField']['int']],
4838 ]);
4839 $this->assertEquals(5, $getContribution['values'][$contribution['id']][$this->getCustomFieldName('int')]);
4840 $this->assertEquals('Some Text', $getContribution['values'][$contribution['id']]['custom_' . $this->ids['CustomField']['text']]);
4841 $this->callAPISuccess('CustomField', 'delete', ['id' => $this->ids['CustomField']['text']]);
4842 $this->callAPISuccess('CustomField', 'delete', ['id' => $this->ids['CustomField']['int']]);
4843 $this->callAPISuccess('CustomGroup', 'delete', ['id' => $this->ids['CustomGroup']['Custom Group']]);
4844 }
4845
4846 /**
4847 * Implement post hook.
4848 *
4849 * @param string $op
4850 * @param string $objectName
4851 * @param int|null $objectId
4852 *
4853 * @throws \CRM_Core_Exception
4854 */
4855 public function civicrmPostContributionCustom(string $op, string $objectName, ?int $objectId): void {
4856 if ($objectName === 'Contribution' && $op === 'create') {
4857 $this->callAPISuccess('Contribution', 'create', [
4858 'id' => $objectId,
4859 'custom_' . $this->ids['CustomField']['int'] => 5,
4860 ]);
4861 }
4862 }
4863
4864 /**
4865 * Test that passing in label for an option value linked to a custom field
4866 * works
4867 *
4868 * @see dev/core#1816
4869 *
4870 * @throws \API_Exception
4871 * @throws \CRM_Core_Exception
4872 */
4873 public function testCustomValueOptionLabelTest(): void {
4874 $this->createCustomGroupWithFieldOfType([], 'radio');
4875 $params = $this->_params;
4876 $params['custom_' . $this->ids['CustomField']['radio']] = 'Red Testing';
4877 $this->callAPISuccess('Contribution', 'Create', $params);
4878 }
4879
4880 /**
4881 * Test repeatTransaction with installments and next_sched_contribution_date
4882 *
4883 * @dataProvider getRepeatTransactionNextSchedData
4884 *
4885 * @param array $dataSet
4886 *
4887 * @throws \CRM_Core_Exception
4888 */
4889 public function testRepeatTransactionUpdateNextSchedContributionDate(array $dataSet): void {
4890 $paymentProcessorID = $this->paymentProcessorCreate();
4891 // Create the contribution before the recur so it doesn't trigger the update of next_sched_contribution_date
4892 $contribution = $this->callAPISuccess('contribution', 'create', array_merge(
4893 $this->_params,
4894 [
4895 'contribution_status_id' => 'Completed',
4896 'receive_date' => $dataSet['repeat'][0]['receive_date'],
4897 ])
4898 );
4899 $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', array_merge([
4900 'contact_id' => $this->_individualId,
4901 'frequency_interval' => '1',
4902 'amount' => '500',
4903 'contribution_status_id' => 'Pending',
4904 'start_date' => '2012-01-01 00:00:00',
4905 'currency' => 'USD',
4906 'frequency_unit' => 'month',
4907 'payment_processor_id' => $paymentProcessorID,
4908 ], $dataSet['recur']));
4909 // Link the existing contribution to the recur *after* creating the recur.
4910 // If we just created the contribution now the next_sched_contribution_date would be automatically set
4911 // and we want to test the case when it is empty.
4912 $this->callAPISuccess('contribution', 'create', [
4913 'id' => $contribution['id'],
4914 'contribution_recur_id' => $contributionRecur['id'],
4915 ]);
4916
4917 $contributionRecur = $this->callAPISuccessGetSingle('ContributionRecur', [
4918 'id' => $contributionRecur['id'],
4919 'return' => ['next_sched_contribution_date', 'contribution_status_id'],
4920 ]);
4921 // Check that next_sched_contribution_date is empty
4922 $this->assertEquals('', $contributionRecur['next_sched_contribution_date'] ?? '');
4923
4924 $this->callAPISuccess('Contribution', 'repeattransaction', [
4925 'contribution_status_id' => 'Completed',
4926 'contribution_recur_id' => $contributionRecur['id'],
4927 'receive_date' => $dataSet['repeat'][0]['receive_date'],
4928 ]);
4929 $contributionRecur = $this->callAPISuccessGetSingle('ContributionRecur', [
4930 'id' => $contributionRecur['id'],
4931 'return' => ['next_sched_contribution_date', 'contribution_status_id'],
4932 ]);
4933 // Check that recur has status "In Progress"
4934 $this->assertEquals(
4935 (string) CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_ContributionRecur', 'contribution_status_id', $dataSet['repeat'][0]['expectedRecurStatus']),
4936 $contributionRecur['contribution_status_id']
4937 );
4938 // Check that next_sched_contribution_date has been set to 1 period after the contribution receive date (ie. 1 month)
4939 $this->assertEquals($dataSet['repeat'][0]['expectedNextSched'], $contributionRecur['next_sched_contribution_date']);
4940
4941 // Now call Contribution.repeattransaction again and check that the next_sched_contribution_date has moved forward by 1 period again
4942 $this->callAPISuccess('Contribution', 'repeattransaction', [
4943 'contribution_status_id' => 'Completed',
4944 'contribution_recur_id' => $contributionRecur['id'],
4945 'receive_date' => $dataSet['repeat'][1]['receive_date'],
4946 ]);
4947 $contributionRecur = $this->callAPISuccessGetSingle('ContributionRecur', [
4948 'id' => $contributionRecur['id'],
4949 'return' => ['next_sched_contribution_date', 'contribution_status_id'],
4950 ]);
4951 // Check that recur has status "In Progress" or "Completed" depending on whether number of installments has been reached
4952 $this->assertEquals(
4953 (string) CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_ContributionRecur', 'contribution_status_id', $dataSet['repeat'][1]['expectedRecurStatus']),
4954 $contributionRecur['contribution_status_id']
4955 );
4956 // Check that next_sched_contribution_date has been set to 1 period after the contribution receive date (ie. 1 month)
4957 $this->assertEquals($dataSet['repeat'][1]['expectedNextSched'], $contributionRecur['next_sched_contribution_date'] ?? '');
4958 }
4959
4960 /**
4961 * Get dates for testing.
4962 *
4963 * @return array
4964 */
4965 public function getRepeatTransactionNextSchedData(): array {
4966 // Both these tests handle/test the case that next_sched_contribution_date is empty when Contribution.repeattransaction
4967 // is called for the first time. Historically setting it was inconsistent but on new updates it should always be set.
4968 /*
4969 * This tests that calling Contribution.repeattransaction with installments does the following:
4970 * - For the first call to repeattransaction the recur status is In Progress and next_sched_contribution_date is updated
4971 * to match next expected receive_date.
4972 * - Once the 3rd contribution is created contributionRecur status = completed and next_sched_contribution_date = ''.
4973 */
4974 $result['receive_date_includes_time_with_installments']['2012-01-01-1-month'] = [
4975 'recur' => [
4976 'start_date' => '2012-01-01',
4977 'frequency_interval' => 1,
4978 'installments' => '3',
4979 'frequency_unit' => 'month',
4980 ],
4981 'repeat' => [
4982 [
4983 'receive_date' => '2012-02-29 16:00:00',
4984 'expectedNextSched' => '2012-03-29 00:00:00',
4985 'expectedRecurStatus' => 'In Progress',
4986 ],
4987 [
4988 'receive_date' => '2012-03-29 16:00:00',
4989 'expectedNextSched' => '',
4990 'expectedRecurStatus' => 'Completed',
4991 ],
4992 ],
4993 ];
4994 /*
4995 * This tests that calling Contribution.repeattransaction with no installments does the following:
4996 * - For the each call to repeattransaction the recur status is In Progress and next_sched_contribution_date is updated
4997 * to match next expected receive_date.
4998 */
4999 $result['receive_date_includes_time_no_installments']['2012-01-01-1-month'] = [
5000 'recur' => [
5001 'start_date' => '2012-01-01',
5002 'frequency_interval' => 1,
5003 'frequency_unit' => 'month',
5004 ],
5005 'repeat' => [
5006 [
5007 'receive_date' => '2012-02-29 16:00:00',
5008 'expectedNextSched' => '2012-03-29 00:00:00',
5009 'expectedRecurStatus' => 'In Progress',
5010 ],
5011 [
5012 'receive_date' => '2012-03-29 16:00:00',
5013 'expectedNextSched' => '2012-04-29 00:00:00',
5014 'expectedRecurStatus' => 'In Progress',
5015 ],
5016 ],
5017 ];
5018 return $result;
5019 }
5020
5021 /**
5022 * Make sure that recording a payment doesn't alter the receive_date of a
5023 * pending contribution.
5024 */
5025 public function testPaymentDontChangeReceiveDate(): void {
5026 $params = [
5027 'contact_id' => $this->_individualId,
5028 'total_amount' => 100,
5029 'receive_date' => '2020-02-02',
5030 'contribution_status_id' => 'Pending',
5031 ];
5032 $contributionID = $this->contributionCreate($params);
5033 $paymentParams = [
5034 'contribution_id' => $contributionID,
5035 'total_amount' => 100,
5036 'trxn_date' => '2020-03-04',
5037 ];
5038 $this->callAPISuccess('payment', 'create', $paymentParams);
5039
5040 //check if contribution status is set to "Completed".
5041 $contribution = $this->callAPISuccess('Contribution', 'getSingle', [
5042 'id' => $contributionID,
5043 ]);
5044 $this->assertEquals('2020-02-02 00:00:00', $contribution['receive_date']);
5045 }
5046
5047 /**
5048 * Make sure that recording a payment with Different Payment Instrument update main contribution record payment
5049 * instrument too. If multiple Payment Recorded, last payment record payment (when No more due) instrument set to main
5050 * payment
5051 */
5052 public function testPaymentVerifyPaymentInstrumentChange() {
5053 // Create Pending contribution with pay later mode, with payment instrument Check
5054 $params = [
5055 'contact_id' => $this->_individualId,
5056 'total_amount' => 100,
5057 'receive_date' => '2020-02-02',
5058 'contribution_status_id' => 'Pending',
5059 'is_pay_later' => 1,
5060 'payment_instrument_id' => 'Check',
5061 ];
5062 $contributionID = $this->contributionCreate($params);
5063
5064 // Record the the Payment with instrument other than Check, e.g EFT
5065 $paymentParams = [
5066 'contribution_id' => $contributionID,
5067 'total_amount' => 50,
5068 'trxn_date' => '2020-03-04',
5069 'payment_instrument_id' => 'EFT',
5070 ];
5071 $this->callAPISuccess('payment', 'create', $paymentParams);
5072
5073 $contribution = $this->callAPISuccess('Contribution', 'getSingle', [
5074 'id' => $contributionID,
5075 ]);
5076 // payment status should be 'Partially paid'
5077 $this->assertEquals('Partially paid', $contribution['contribution_status']);
5078
5079 // Record the the Payment with instrument other than Check, e.g Cash (pay all remaining amount)
5080 $paymentParams = [
5081 'contribution_id' => $contributionID,
5082 'total_amount' => 50,
5083 'trxn_date' => '2020-03-04',
5084 'payment_instrument_id' => 'Cash',
5085 ];
5086 $this->callAPISuccess('payment', 'create', $paymentParams);
5087
5088 //check if contribution Payment Instrument (Payment Method) is is set to "Cash".
5089 $contribution = $this->callAPISuccess('Contribution', 'getSingle', [
5090 'id' => $contributionID,
5091 ]);
5092 $this->assertEquals('Cash', $contribution['payment_instrument']);
5093 $this->assertEquals('Completed', $contribution['contribution_status']);
5094 }
5095
5096 /**
5097 * Test the "clean money" functionality.
5098 */
5099 public function testCleanMoney() {
5100 $params = [
5101 'contact_id' => $this->_individualId,
5102 'financial_type_id' => 1,
5103 'total_amount' => '$100',
5104 'fee_amount' => '$20',
5105 'net_amount' => '$80',
5106 'non_deductible_amount' => '$80',
5107 'sequential' => 1,
5108 ];
5109 $id = $this->callAPISuccess('Contribution', 'create', $params)['id'];
5110 // Reading the return values of the API isn't reliable here; get the data from the db.
5111 $contribution = $this->callAPISuccess('Contribution', 'getsingle', ['id' => $id]);
5112 $this->assertEquals('100.00', $contribution['total_amount']);
5113 $this->assertEquals('20.00', $contribution['fee_amount']);
5114 $this->assertEquals('80.00', $contribution['net_amount']);
5115 $this->assertEquals('80.00', $contribution['non_deductible_amount']);
5116 }
5117
5118 /**
5119 * Create a price set with a quick config price set.
5120 *
5121 * The params to use this look like
5122 *
5123 * ['price_' . $this->ids['PriceField']['basic'] => $this->ids['PriceFieldValue']['basic']]
5124 *
5125 * @param array $contributionPageParams
5126 *
5127 * @return int
5128 *
5129 * @throws \API_Exception
5130 * @throws \CRM_Core_Exception
5131 * @throws \Civi\API\Exception\UnauthorizedException
5132 */
5133 private function createQuickConfigContributionPage(array $contributionPageParams = []): int {
5134 $contributionPageID = $this->callAPISuccess('ContributionPage', 'create', array_merge([
5135 'receipt_from_name' => 'Mickey Mouse',
5136 'receipt_from_email' => 'mickey@mouse.com',
5137 'title' => 'Test Contribution Page',
5138 'financial_type_id' => 'Member Dues',
5139 'currency' => 'CAD',
5140 'is_pay_later' => 1,
5141 'is_quick_config' => TRUE,
5142 'pay_later_text' => 'I will send payment by check',
5143 'pay_later_receipt' => 'This is a pay later receipt',
5144 'is_allow_other_amount' => 1,
5145 'min_amount' => 10.00,
5146 'max_amount' => 10000.00,
5147 'goal_amount' => 100000.00,
5148 'is_email_receipt' => 1,
5149 'is_active' => 1,
5150 'amount_block_is_active' => 1,
5151 'is_billing_required' => 0,
5152 ], $contributionPageParams))['id'];
5153
5154 $priceSetID = PriceSet::create()->setValues([
5155 'name' => 'quick config set',
5156 'title' => 'basic price set',
5157 'is_quick_config' => TRUE,
5158 'extends' => 2,
5159 ])->execute()->first()['id'];
5160
5161 $priceFieldID = PriceField::create()->setValues([
5162 'price_set_id' => $priceSetID,
5163 'name' => 'quick config field name',
5164 'label' => 'quick config field name',
5165 'html_type' => 'Radio',
5166 ])->execute()->first()['id'];
5167 $this->ids['PriceSet']['basic'] = $priceSetID;
5168 $this->ids['PriceField']['basic'] = $priceFieldID;
5169 $this->ids['PriceFieldValue']['basic'] = PriceFieldValue::create()->setValues([
5170 'price_field_id' => $priceFieldID,
5171 'name' => 'quick config price field',
5172 'label' => 'quick config price field',
5173 'amount' => 100,
5174 'financial_type_id:name' => 'Member Dues',
5175 ])->execute()->first()['id'];
5176 CRM_Price_BAO_PriceSet::addTo('civicrm_contribution_page', $contributionPageID, $priceSetID);
5177 return $contributionPageID;
5178 }
5179
5180 }