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