Merge pull request #16794 from civicrm/5.24
[civicrm-core.git] / tests / phpunit / api / v3 / TaxContributionPageTest.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 * Class api_v3_TaxContributionPageTest
14 * @group headless
15 */
16 class api_v3_TaxContributionPageTest extends CiviUnitTestCase {
17 protected $_apiversion = 3;
18 protected $params;
19 protected $financialtypeID;
20 protected $financialAccountId;
21 protected $_entity = 'contribution_page';
22 protected $_priceSetParams = [];
23 protected $_paymentProcessorType;
24 protected $payParams = [];
25 protected $paymentProceParams = [];
26 protected $settingValue = [];
27 protected $setInvoiceSettings;
28 protected $_ids = [];
29 protected $_individualId;
30 protected $financialAccHalftax;
31 protected $financialtypeHalftax;
32 protected $financialRelationHalftax;
33 protected $halfFinancialAccId;
34 protected $halfFinancialTypeId;
35 public $DBResetRequired = TRUE;
36
37 public function setUp() {
38 parent::setUp();
39 $this->_individualId = $this->individualCreate();
40 $this->_orgId = $this->organizationCreate(NULL);
41
42 $this->params = [
43 'title' => "Test Contribution Page" . substr(sha1(rand()), 0, 7),
44 'financial_type_id' => 1,
45 'payment_processor' => 1,
46 'currency' => 'NZD',
47 'goal_amount' => 350,
48 'is_pay_later' => 1,
49 'pay_later_text' => 'I will pay later',
50 'pay_later_receipt' => "I will pay later",
51 'is_monetary' => TRUE,
52 'is_billing_required' => TRUE,
53 ];
54
55 $this->_priceSetParams = [
56 'name' => 'tax_contribution' . substr(sha1(rand()), 0, 7),
57 'title' => 'contributiontax' . substr(sha1(rand()), 0, 7),
58 'is_active' => 1,
59 'help_pre' => "Where does your goat sleep",
60 'help_post' => "thank you for your time",
61 'extends' => 2,
62 'financial_type_id' => 3,
63 'is_quick_config' => 0,
64 'is_reserved' => 0,
65 ];
66 // Financial Account with 20% tax rate
67 $financialAccountSetparams = [
68 #[domain_id] =>
69 'name' => 'vat full taxrate account' . substr(sha1(rand()), 0, 7),
70 'contact_id' => $this->_orgId,
71 'financial_account_type_id' => 2,
72 'is_tax' => 1,
73 'tax_rate' => 20.00,
74 'is_reserved' => 0,
75 'is_active' => 1,
76 'is_default' => 0,
77 ];
78
79 $financialAccount = $this->callAPISuccess('financial_account', 'create', $financialAccountSetparams);
80 $this->financialAccountId = $financialAccount['id'];
81
82 // Financial type having 'Sales Tax Account is' with liability financail account
83 $financialType = [
84 'name' => 'grassvariety1' . substr(sha1(rand()), 0, 7),
85 'is_reserved' => 0,
86 'is_active' => 1,
87 ];
88 $priceField = $this->callAPISuccess('financial_type', 'create', $financialType);
89 $this->financialtypeID = $priceField['id'];
90 $financialRelationParams = [
91 'entity_table' => 'civicrm_financial_type',
92 'entity_id' => $this->financialtypeID,
93 'account_relationship' => 10,
94 'financial_account_id' => $this->financialAccountId,
95 ];
96 $financialRelation = CRM_Financial_BAO_FinancialTypeAccount::add($financialRelationParams);
97
98 // Financial type with 5% tax rate
99 $financialAccHalftax = [
100 'name' => 'vat half taxrate account' . substr(sha1(rand()), 0, 7),
101 'contact_id' => $this->_orgId,
102 'financial_account_type_id' => 2,
103 'is_tax' => 1,
104 'tax_rate' => 5.00,
105 'is_reserved' => 0,
106 'is_active' => 1,
107 'is_default' => 0,
108 ];
109 $halfFinancialAccount = CRM_Financial_BAO_FinancialAccount::add($financialAccHalftax);
110 $this->halfFinancialAccId = $halfFinancialAccount->id;
111 $halfFinancialtypeHalftax = [
112 'name' => 'grassvariety2' . substr(sha1(rand()), 0, 7),
113 'is_reserved' => 0,
114 'is_active' => 1,
115 ];
116
117 $halfFinancialType = CRM_Financial_BAO_FinancialType::add($halfFinancialtypeHalftax);
118 $this->halfFinancialTypeId = $halfFinancialType->id;
119 $financialRelationHalftax = [
120 'entity_table' => 'civicrm_financial_type',
121 'entity_id' => $this->halfFinancialTypeId,
122 'account_relationship' => 10,
123 'financial_account_id' => $this->halfFinancialAccId,
124 ];
125
126 $halfFinancialRelation = CRM_Financial_BAO_FinancialTypeAccount::add($financialRelationHalftax);
127
128 // Enable component contribute setting
129 $setInvoiceSettings = $this->enableTaxAndInvoicing();
130
131 // Payment Processor
132 $paymentProceParams = [
133 'domain_id' => 1,
134 'name' => 'dummy' . substr(sha1(rand()), 0, 7),
135 'payment_processor_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Financial_BAO_PaymentProcessor', 'payment_processor_type_id', 'Dummy'),
136 'financial_account_id' => 12,
137 'is_active' => 1,
138 'is_default' => 1,
139 'user_name' => 'dummy',
140 'url_site' => 'http://dummy.com',
141 'url_recur' => 'http://dummyrecur.com',
142 'class_name' => 'Payment_Dummy',
143 'billing_mode' => 1,
144 'is_recur' => 1,
145 'payment_type' => 1,
146 ];
147 $result = $this->callAPISuccess('payment_processor', 'create', $paymentProceParams);
148 $this->_ids['paymentProcessID'] = $result['id'];
149 require_once 'api/v3/examples/PaymentProcessor/Create.ex.php';
150 $this->assertAPISuccess($result);
151 }
152
153 /**
154 * Cleanup after function.
155 */
156 public function tearDown() {
157 $this->quickCleanUpFinancialEntities();
158 parent::tearDown();
159 }
160
161 public function setUpContributionPage() {
162 $contributionPageResult = $this->callAPISuccess($this->_entity, 'create', $this->params);
163 if (empty($this->_ids['price_set'])) {
164 $priceSet = $this->callAPISuccess('price_set', 'create', $this->_priceSetParams);
165 $this->_ids['price_set'][] = $priceSet['id'];
166 }
167 $priceSetID = $this->_price = reset($this->_ids['price_set']);
168 CRM_Price_BAO_PriceSet::addTo('civicrm_contribution_page', $contributionPageResult['id'], $priceSetID);
169
170 if (empty($this->_ids['price_field'])) {
171 $priceField = $this->callAPISuccess('price_field', 'create', [
172 'price_set_id' => $priceSetID,
173 'label' => 'Goat Breed',
174 'html_type' => 'Radio',
175 ]);
176 $this->_ids['price_field'] = [$priceField['id']];
177 }
178 if (empty($this->_ids['price_field_value'])) {
179 $this->callAPISuccess('price_field_value', 'create', [
180 'price_set_id' => $priceSetID,
181 'price_field_id' => $priceField['id'],
182 'label' => 'Long Haired Goat',
183 'amount' => 100,
184 'financial_type_id' => $this->financialtypeID,
185 ]);
186 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
187 'price_set_id' => $priceSetID,
188 'price_field_id' => $priceField['id'],
189 'label' => 'Shoe-eating Goat',
190 'amount' => 300,
191 'financial_type_id' => $this->halfFinancialTypeId,
192 ]);
193 $this->_ids['price_field_value'] = [$priceFieldValue['id']];
194 }
195 $this->_ids['contribution_page'] = $contributionPageResult['id'];
196 }
197
198 /**
199 * Online and offline contrbution from above created contribution page.
200 *
201 * @param string $thousandSeparator
202 * punctuation used to refer to thousands.
203 *
204 * @dataProvider getThousandSeparators
205 */
206 public function testCreateContributionOnline($thousandSeparator) {
207 $this->setCurrencySeparators($thousandSeparator);
208 $this->setUpContributionPage();
209 $params = [
210 'contact_id' => $this->_individualId,
211 'receive_date' => '20120511',
212 'total_amount' => $this->formatMoneyInput(100.00),
213 'financial_type_id' => $this->financialtypeID,
214 'contribution_page_id' => $this->_ids['contribution_page'],
215 'payment_processor' => $this->_ids['paymentProcessID'],
216 'trxn_id' => 12345,
217 'invoice_id' => 67890,
218 'source' => 'SSF',
219 'contribution_status_id' => 1,
220 ];
221
222 $contribution = $this->callAPISuccess('contribution', 'create', $params);
223 $this->_ids['contributionId'] = $contribution['id'];
224 $this->assertEquals($contribution['values'][$contribution['id']]['contact_id'], $this->_individualId);
225 $this->assertEquals($contribution['values'][$contribution['id']]['total_amount'], 120.00);
226 $this->assertEquals($contribution['values'][$contribution['id']]['financial_type_id'], $this->financialtypeID);
227 $this->assertEquals($contribution['values'][$contribution['id']]['trxn_id'], 12345);
228 $this->assertEquals($contribution['values'][$contribution['id']]['invoice_id'], 67890);
229 $this->assertEquals($contribution['values'][$contribution['id']]['source'], 'SSF');
230 $this->assertEquals($contribution['values'][$contribution['id']]['tax_amount'], 20);
231 $this->assertEquals($contribution['values'][$contribution['id']]['contribution_status_id'], 1);
232 $this->_checkFinancialRecords($contribution, 'online');
233 }
234
235 /**
236 * Create contribution with chained line items.
237 *
238 * @param string $thousandSeparator
239 * punctuation used to refer to thousands.
240 *
241 * @dataProvider getThousandSeparators
242 */
243 public function testCreateContributionChainedLineItems($thousandSeparator) {
244 $this->setCurrencySeparators($thousandSeparator);
245 $this->setUpContributionPage();
246 $params = [
247 'contact_id' => $this->_individualId,
248 'receive_date' => '20120511',
249 'total_amount' => 400.00,
250 'financial_type_id' => $this->financialtypeID,
251 'trxn_id' => 12345,
252 'invoice_id' => 67890,
253 'source' => 'SSF',
254 'contribution_status_id' => 1,
255 'skipLineItem' => 1,
256 'api.line_item.create' => [
257 [
258 'price_field_id' => $this->_ids['price_field'],
259 'qty' => 1,
260 'line_total' => '100',
261 'unit_price' => '100',
262 'financial_type_id' => $this->financialtypeID,
263 ],
264 [
265 'price_field_id' => $this->_ids['price_field'],
266 'qty' => 1,
267 'line_total' => '300',
268 'unit_price' => '300',
269 'financial_type_id' => $this->halfFinancialTypeId,
270 ],
271 ],
272 ];
273
274 $contribution = $this->callAPISuccess('contribution', 'create', $params);
275
276 $lineItems = $this->callAPISuccess('line_item', 'get', [
277 'entity_id' => $contribution['id'],
278 'contribution_id' => $contribution['id'],
279 'entity_table' => 'civicrm_contribution',
280 'sequential' => 1,
281 ]);
282 $this->assertEquals(2, $lineItems['count']);
283 }
284
285 public function testCreateContributionPayLaterOnline() {
286 $this->setUpContributionPage();
287 $params = [
288 'contact_id' => $this->_individualId,
289 'receive_date' => '20120511',
290 'total_amount' => 100.00,
291 'financial_type_id' => $this->financialtypeID,
292 'contribution_page_id' => $this->_ids['contribution_page'],
293 'trxn_id' => 12345,
294 'is_pay_later' => 1,
295 'invoice_id' => 67890,
296 'source' => 'SSF',
297 'contribution_status_id' => 2,
298 ];
299 $contribution = $this->callAPISuccess('contribution', 'create', $params, __FUNCTION__, __FILE__);
300 $this->assertEquals($contribution['values'][$contribution['id']]['contact_id'], $this->_individualId);
301 $this->assertEquals($contribution['values'][$contribution['id']]['total_amount'], 120.00);
302 $this->assertEquals($contribution['values'][$contribution['id']]['financial_type_id'], $this->financialtypeID);
303 $this->assertEquals($contribution['values'][$contribution['id']]['trxn_id'], 12345);
304 $this->assertEquals($contribution['values'][$contribution['id']]['invoice_id'], 67890);
305 $this->assertEquals($contribution['values'][$contribution['id']]['source'], 'SSF');
306 $this->assertEquals($contribution['values'][$contribution['id']]['tax_amount'], 20);
307 $this->assertEquals($contribution['values'][$contribution['id']]['contribution_status_id'], 2);
308 $this->_checkFinancialRecords($contribution, 'payLater');
309 }
310
311 /**
312 * Test online pending contributions.
313 *
314 * @param string $thousandSeparator
315 * punctuation used to refer to thousands.
316 *
317 * @dataProvider getThousandSeparators
318 */
319 public function testCreateContributionPendingOnline($thousandSeparator) {
320 $this->setCurrencySeparators($thousandSeparator);
321 $this->setUpContributionPage();
322 $params = [
323 'contact_id' => $this->_individualId,
324 'receive_date' => '20120511',
325 'total_amount' => $this->formatMoneyInput(100.00),
326 'financial_type_id' => $this->financialtypeID,
327 'contribution_page_id' => $this->_ids['contribution_page'],
328 'trxn_id' => 12345,
329 'invoice_id' => 67890,
330 'source' => 'SSF',
331 'contribution_status_id' => 2,
332 ];
333
334 $contribution = $this->callAPISuccess('contribution', 'create', $params, __FUNCTION__, __FILE__);
335 $this->assertEquals($contribution['values'][$contribution['id']]['contact_id'], $this->_individualId);
336 $this->assertEquals($contribution['values'][$contribution['id']]['total_amount'], 120.00);
337 $this->assertEquals($contribution['values'][$contribution['id']]['financial_type_id'], $this->financialtypeID);
338 $this->assertEquals($contribution['values'][$contribution['id']]['trxn_id'], 12345);
339 $this->assertEquals($contribution['values'][$contribution['id']]['invoice_id'], 67890);
340 $this->assertEquals($contribution['values'][$contribution['id']]['source'], 'SSF');
341 $this->assertEquals($contribution['values'][$contribution['id']]['tax_amount'], 20);
342 $this->assertEquals($contribution['values'][$contribution['id']]['contribution_status_id'], 2);
343 $this->_checkFinancialRecords($contribution, 'pending');
344 $this->setCurrencySeparators($thousandSeparator);
345 }
346
347 /**
348 * Update a contribution.
349 *
350 * Function tests that line items, financial records are updated when contribution amount is changed
351 */
352 public function testCreateUpdateContributionChangeTotal() {
353 $this->setUpContributionPage();
354 $this->contributionParams = [
355 'contact_id' => $this->_individualId,
356 'receive_date' => '20120511',
357 'total_amount' => 100.00,
358 'financial_type_id' => $this->financialtypeID,
359 'source' => 'SSF',
360 'contribution_status_id' => 1,
361 ];
362 $contribution = $this->callAPISuccess('contribution', 'create', $this->contributionParams);
363 $lineItems = $this->callAPISuccess('line_item', 'getvalue', [
364 'entity_id' => $contribution['id'],
365 'entity_table' => 'civicrm_contribution',
366 'sequential' => 1,
367 'return' => 'line_total',
368 ]);
369 $this->assertEquals('100.00', $lineItems);
370 $trxnAmount = $this->_getFinancialTrxnAmount($contribution['id']);
371 $this->assertEquals('120.00', $trxnAmount);
372 $newParams = [
373 'id' => $contribution['id'],
374 // without tax rate i.e Donation
375 'financial_type_id' => 1,
376 'total_amount' => '300',
377 ];
378 $contribution = $this->callAPISuccess('contribution', 'create', $newParams);
379
380 $lineItems = $this->callAPISuccess('line_item', 'getvalue', [
381 'entity_id' => $contribution['id'],
382 'entity_table' => 'civicrm_contribution',
383 'sequential' => 1,
384 'return' => 'line_total',
385 ]);
386
387 $this->assertEquals('300.00', $lineItems);
388 $trxnAmount = $this->_getFinancialTrxnAmount($contribution['id']);
389 $fitemAmount = $this->_getFinancialItemAmount($contribution['id']);
390 $this->assertEquals('300.00', $trxnAmount);
391 $this->assertEquals('300.00', $fitemAmount);
392 }
393
394 /**
395 * @param int $contId
396 *
397 * @return null|string
398 */
399 public function _getFinancialTrxnAmount($contId) {
400 $query = "SELECT
401 SUM( ft.total_amount ) AS total
402 FROM civicrm_financial_trxn AS ft
403 LEFT JOIN civicrm_entity_financial_trxn AS ceft ON ft.id = ceft.financial_trxn_id
404 WHERE ceft.entity_table = 'civicrm_contribution'
405 AND ceft.entity_id = {$contId}";
406 $result = CRM_Core_DAO::singleValueQuery($query);
407 return $result;
408 }
409
410 /**
411 * @param int $contId
412 *
413 * @return null|string
414 */
415 public function _getFinancialItemAmount($contId) {
416 $lineItem = key(CRM_Price_BAO_LineItem::getLineItems($contId, 'contribution'));
417 $query = "SELECT
418 SUM(amount)
419 FROM civicrm_financial_item
420 WHERE entity_table = 'civicrm_line_item'
421 AND entity_id = {$lineItem}";
422 $result = CRM_Core_DAO::singleValueQuery($query);
423 return $result;
424 }
425
426 /**
427 * @param array $params
428 * @param $context
429 */
430 public function _checkFinancialRecords($params, $context) {
431 $entityParams = [
432 'entity_id' => $params['id'],
433 'entity_table' => 'civicrm_contribution',
434 ];
435 if ($context == 'pending') {
436 $trxn = CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($entityParams);
437 $this->assertNull($trxn, 'No Trxn to be created until IPN callback');
438 return;
439 }
440 $trxn = current(CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($entityParams));
441 $trxnParams = [
442 'id' => $trxn['financial_trxn_id'],
443 ];
444 if ($context != 'online' && $context != 'payLater') {
445 $compareParams = [
446 'to_financial_account_id' => 6,
447 'total_amount' => 120,
448 'status_id' => 1,
449 ];
450 }
451 if ($context == 'online') {
452 $compareParams = [
453 'to_financial_account_id' => 12,
454 'total_amount' => 120,
455 'status_id' => 1,
456 ];
457 }
458 elseif ($context == 'payLater') {
459 $compareParams = [
460 'to_financial_account_id' => 7,
461 'total_amount' => 120,
462 'status_id' => 2,
463 ];
464 }
465 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
466 $entityParams = [
467 'financial_trxn_id' => $trxn['financial_trxn_id'],
468 'entity_table' => 'civicrm_financial_item',
469 ];
470 $entityTrxn = current(CRM_Financial_BAO_FinancialItem::retrieveEntityFinancialTrxn($entityParams));
471 $fitemParams = [
472 'id' => $entityTrxn['entity_id'],
473 ];
474 $compareParams = [
475 'amount' => 100,
476 'status_id' => 1,
477 'financial_account_id' => $this->_getFinancialAccountId($this->financialtypeID),
478 ];
479 if ($context == 'payLater') {
480 $compareParams = [
481 'amount' => 100,
482 'status_id' => 3,
483 'financial_account_id' => $this->_getFinancialAccountId($this->financialtypeID),
484 ];
485 }
486 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', $fitemParams, $compareParams);
487 }
488
489 /**
490 * @param int $financialTypeId
491 * @return int
492 */
493 public function _getFinancialAccountId($financialTypeId) {
494 $accountRel = key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Income Account is' "));
495
496 $searchParams = [
497 'entity_table' => 'civicrm_financial_type',
498 'entity_id' => $financialTypeId,
499 'account_relationship' => $accountRel,
500 ];
501
502 $result = [];
503 CRM_Financial_BAO_FinancialTypeAccount::retrieve($searchParams, $result);
504 return $result['financial_account_id'] ?? NULL;
505 }
506
507 /**
508 * Test deleting a contribution.
509 *
510 * (It is unclear why this is in this class - it seems like maybe it doesn't test anything not
511 * on the contribution test class & might be copy and paste....).
512 */
513 public function testDeleteContribution() {
514 $contributionID = $this->contributionCreate([
515 'contact_id' => $this->_individualId,
516 'trxn_id' => 12389,
517 'financial_type_id' => $this->financialtypeID,
518 'invoice_id' => 'dfsdf',
519 ]);
520 $this->callAPISuccess('contribution', 'delete', ['id' => $contributionID]);
521 }
522
523 }