3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
12 use Civi\Api4\EntityFinancialTrxn
;
15 * Class api_v3_TaxContributionPageTest
18 class api_v3_TaxContributionPageTest
extends CiviUnitTestCase
{
20 protected $financialTypeID;
21 protected $financialAccountId;
22 protected $_entity = 'contribution_page';
23 protected $_priceSetParams = [];
24 protected $_paymentProcessorType;
25 protected $payParams = [];
26 protected $settingValue = [];
27 protected $setInvoiceSettings;
28 protected $_individualId;
29 protected $financialAccHalftax;
30 protected $financialtypeHalftax;
31 protected $financialRelationHalftax;
32 protected $halfFinancialAccId;
33 protected $halfFinancialTypeId;
35 protected $isValidateFinancialsOnPostAssert = FALSE;
38 * @throws \CRM_Core_Exception
39 * @throws \CiviCRM_API3_Exception
41 public function setUp(): void
{
43 $this->_individualId
= $this->individualCreate();
44 $this->_orgId
= $this->organizationCreate(NULL);
46 $this->ids
['PaymentProcessor'] = $this->paymentProcessorCreate();
48 'title' => 'Test Contribution Page',
49 'financial_type_id' => 1,
50 'payment_processor' => $this->ids
['PaymentProcessor'],
54 'pay_later_text' => 'I will pay later',
55 'pay_later_receipt' => 'I will pay later',
56 'is_monetary' => TRUE,
57 'is_billing_required' => TRUE,
60 $this->_priceSetParams
= [
61 'name' => 'tax_contribution',
62 'title' => 'contribution tax',
64 'help_pre' => 'Where does your goat sleep',
65 'help_post' => 'thank you for your time',
67 'financial_type_id' => 3,
68 'is_quick_config' => 0,
71 // Financial Account with 20% tax rate
72 $financialAccountSetparams = [
73 'name' => 'vat full tax rate account',
74 'contact_id' => $this->_orgId
,
75 'financial_account_type_id' => 2,
83 $financialAccount = $this->callAPISuccess('financial_account', 'create', $financialAccountSetparams);
84 $this->financialAccountId
= $financialAccount['id'];
86 // Financial type having 'Sales Tax Account is' with liability financial account
87 $this->financialTypeID
= $this->callAPISuccess('FinancialType', 'create', [
88 'name' => 'grass_variety_1',
92 $financialRelationParams = [
93 'entity_table' => 'civicrm_financial_type',
94 'entity_id' => $this->financialTypeID
,
95 'account_relationship' => 10,
96 'financial_account_id' => $this->financialAccountId
,
98 CRM_Financial_BAO_FinancialTypeAccount
::add($financialRelationParams);
100 // Financial type with 5% tax rate
101 $financialAccountHalfTax = [
102 'name' => 'vat half tax_rate account',
103 'contact_id' => $this->_orgId
,
104 'financial_account_type_id' => 2,
111 $halfFinancialAccount = CRM_Financial_BAO_FinancialAccount
::add($financialAccountHalfTax);
112 $this->halfFinancialAccId
= $halfFinancialAccount->id
;
113 $halfFinancialTypeHalfTax = [
114 'name' => 'grass_variety_2',
119 $halfFinancialType = CRM_Financial_BAO_FinancialType
::add($halfFinancialTypeHalfTax);
120 $this->halfFinancialTypeId
= $halfFinancialType->id
;
121 $financialRelationHalftax = [
122 'entity_table' => 'civicrm_financial_type',
123 'entity_id' => $this->halfFinancialTypeId
,
124 'account_relationship' => 10,
125 'financial_account_id' => $this->halfFinancialAccId
,
128 CRM_Financial_BAO_FinancialTypeAccount
::add($financialRelationHalftax);
130 // Enable component contribute setting
131 $this->enableTaxAndInvoicing();
135 * Cleanup after function.
137 * @throws \CRM_Core_Exception
138 * @throws \CiviCRM_API3_Exception
140 public function tearDown(): void
{
141 $this->quickCleanUpFinancialEntities();
146 * @throws \CRM_Core_Exception
148 public function setUpContributionPage(): void
{
149 $contributionPageResult = $this->callAPISuccess($this->_entity
, 'create', $this->params
);
150 if (empty($this->ids
['price_set'])) {
151 $priceSet = $this->callAPISuccess('price_set', 'create', $this->_priceSetParams
);
152 $this->ids
['price_set'][] = $priceSet['id'];
154 $priceSetID = $this->_price
= reset($this->ids
['price_set']);
155 CRM_Price_BAO_PriceSet
::addTo('civicrm_contribution_page', $contributionPageResult['id'], $priceSetID);
157 if (empty($this->ids
['price_field'])) {
158 $priceField = $this->callAPISuccess('price_field', 'create', [
159 'price_set_id' => $priceSetID,
160 'label' => 'Goat Breed',
161 'html_type' => 'Radio',
163 $this->ids
['price_field'] = [$priceField['id']];
164 if (empty($this->ids
['price_field_value'])) {
165 $this->callAPISuccess('price_field_value', 'create', [
166 'price_set_id' => $priceSetID,
167 'price_field_id' => $priceField['id'],
168 'label' => 'Long Haired Goat',
170 'financial_type_id' => $this->financialTypeID
,
172 $priceFieldValue = $this->callAPISuccess('price_field_value', 'create', [
173 'price_set_id' => $priceSetID,
174 'price_field_id' => $priceField['id'],
175 'label' => 'Shoe-eating Goat',
177 'financial_type_id' => $this->halfFinancialTypeId
,
179 $this->ids
['price_field_value'] = [$priceFieldValue['id']];
182 $this->ids
['contribution_page'] = $contributionPageResult['id'];
186 * Online and offline contribution from above created contribution page.
188 * @param string $thousandSeparator
189 * punctuation used to refer to thousands.
191 * @throws \API_Exception
192 * @throws \CRM_Core_Exception
193 * @dataProvider getThousandSeparators
196 public function testCreateContributionOnline(string $thousandSeparator): void
{
197 $this->setCurrencySeparators($thousandSeparator);
198 $this->setUpContributionPage();
200 'contact_id' => $this->_individualId
,
201 'receive_date' => '20120511',
202 'total_amount' => $this->formatMoneyInput(100.00),
203 'financial_type_id' => $this->financialTypeID
,
204 'contribution_page_id' => $this->ids
['contribution_page'],
205 'payment_processor' => $this->ids
['PaymentProcessor'],
207 'invoice_id' => 67890,
209 'contribution_status_id' => 1,
213 $contribution = $this->callAPISuccess('Contribution', 'create', $params)['values'][0];
214 $this->assertEquals($this->_individualId
, $contribution['contact_id']);
215 $this->assertEquals(120.00, $contribution['total_amount']);
216 $this->assertEquals($this->financialTypeID
, $contribution['financial_type_id']);
217 $this->assertEquals(12345, $contribution['trxn_id']);
218 $this->assertEquals(67890, $contribution['invoice_id']);
219 $this->assertEquals('SSF', $contribution['source']);
220 $this->assertEquals(20, $contribution['tax_amount']);
221 $this->assertEquals(1, $contribution['contribution_status_id']);
222 $this->_checkFinancialRecords($contribution, 'online');
226 * Create contribution with chained line items.
228 * @param string $thousandSeparator
229 * punctuation used to refer to thousands.
231 * @dataProvider getThousandSeparators
233 * @throws \CRM_Core_Exception
235 public function testCreateContributionChainedLineItems(string $thousandSeparator): void
{
236 $this->setCurrencySeparators($thousandSeparator);
237 $this->setUpContributionPage();
239 'contact_id' => $this->_individualId
,
240 'receive_date' => '20120511',
241 'total_amount' => 400.00,
242 'financial_type_id' => $this->financialTypeID
,
244 'invoice_id' => 67890,
246 'contribution_status_id' => 1,
248 'api.line_item.create' => [
250 'price_field_id' => $this->ids
['price_field'],
252 'line_total' => '100',
253 'unit_price' => '100',
254 'financial_type_id' => $this->financialTypeID
,
257 'price_field_id' => $this->ids
['price_field'],
259 'line_total' => '300',
260 'unit_price' => '300',
261 'financial_type_id' => $this->halfFinancialTypeId
,
266 $contribution = $this->callAPISuccess('contribution', 'create', $params);
268 $lineItems = $this->callAPISuccess('line_item', 'get', [
269 'entity_id' => $contribution['id'],
270 'contribution_id' => $contribution['id'],
271 'entity_table' => 'civicrm_contribution',
274 $this->assertEquals(2, $lineItems['count']);
278 * @throws \API_Exception
279 * @throws \CRM_Core_Exception
281 public function testCreateContributionPayLaterOnline(): void
{
282 $this->setUpContributionPage();
284 'contact_id' => $this->_individualId
,
285 'receive_date' => '20120511',
286 'total_amount' => 100.00,
287 'financial_type_id' => $this->financialTypeID
,
288 'contribution_page_id' => $this->ids
['contribution_page'],
291 'invoice_id' => 67890,
293 'contribution_status_id' => 2,
296 $contribution = $this->callAPISuccess('Contribution', 'create', $params)['values'][0];
297 $this->assertEquals($contribution['contact_id'], $this->_individualId
);
298 $this->assertEquals(120.00, $contribution['total_amount']);
299 $this->assertEquals($this->financialTypeID
, $contribution['financial_type_id']);
300 $this->assertEquals(12345, $contribution['trxn_id']);
301 $this->assertEquals(67890, $contribution['invoice_id']);
302 $this->assertEquals('SSF', $contribution['source']);
303 $this->assertEquals(20, $contribution['tax_amount']);
304 $this->assertEquals(2, $contribution['contribution_status_id']);
305 $this->_checkFinancialRecords($contribution, 'payLater');
309 * Test online pending contributions.
311 * @param string $thousandSeparator
312 * punctuation used to refer to thousands.
314 * @throws \API_Exception
315 * @throws \CRM_Core_Exception
316 * @throws \Civi\API\Exception\UnauthorizedException
317 * @dataProvider getThousandSeparators
320 public function testCreateContributionPendingOnline(string $thousandSeparator): void
{
321 $this->setCurrencySeparators($thousandSeparator);
322 $this->setUpContributionPage();
324 'contact_id' => $this->_individualId
,
325 'receive_date' => '20120511',
326 'total_amount' => $this->formatMoneyInput(100.00),
327 'financial_type_id' => $this->financialTypeID
,
328 'contribution_page_id' => $this->ids
['contribution_page'],
330 'invoice_id' => 67890,
332 'contribution_status_id' => 2,
336 $contribution = $this->callAPISuccess('Contribution', 'create', $params)['values'][0];
337 $this->assertEquals($this->_individualId
, $contribution['contact_id']);
338 $this->assertEquals(120.00, $contribution['total_amount']);
339 $this->assertEquals($this->financialTypeID
, $contribution['financial_type_id']);
340 $this->assertEquals(12345, $contribution['trxn_id']);
341 $this->assertEquals(67890, $contribution['invoice_id']);
342 $this->assertEquals('SSF', $contribution['source']);
343 $this->assertEquals(20, $contribution['tax_amount']);
344 $this->assertEquals(2, $contribution['contribution_status_id']);
345 $trxn = $this->getFinancialTransactionsForContribution($contribution['id']);
346 $this->assertCount(0, $trxn, 'No Trxn to be created until IPN callback');
348 $this->setCurrencySeparators($thousandSeparator);
352 * Update a contribution.
354 * Function tests that line items, financial records are updated when
355 * contribution amount is changed
357 * @throws \CRM_Core_Exception
359 public function testCreateUpdateContributionChangeTotal(): void
{
360 $this->setUpContributionPage();
361 $contributionParams = [
362 'contact_id' => $this->_individualId
,
363 'receive_date' => '20120511',
364 'total_amount' => 100.00,
365 'financial_type_id' => $this->financialTypeID
,
367 'contribution_status_id' => 1,
369 $contribution = $this->callAPISuccess('contribution', 'create', $contributionParams);
370 $lineItems = $this->callAPISuccess('line_item', 'getvalue', [
371 'entity_id' => $contribution['id'],
372 'entity_table' => 'civicrm_contribution',
374 'return' => 'line_total',
376 $this->assertEquals('100.00', $lineItems);
377 $trxnAmount = $this->_getFinancialTrxnAmount($contribution['id']);
378 $this->assertEquals('120.00', $trxnAmount);
380 'id' => $contribution['id'],
381 // without tax rate i.e Donation
382 'financial_type_id' => 1,
383 'total_amount' => '300',
385 $contribution = $this->callAPISuccess('contribution', 'create', $newParams);
387 $lineItems = $this->callAPISuccess('line_item', 'getvalue', [
388 'entity_id' => $contribution['id'],
389 'entity_table' => 'civicrm_contribution',
391 'return' => 'line_total',
394 $this->assertEquals('300.00', $lineItems);
395 $this->assertEquals('320.00', $this->_getFinancialTrxnAmount($contribution['id']));
396 $this->assertEquals('320.00', $this->_getFinancialItemAmount($contribution['id']));
402 * @return null|string
404 public function _getFinancialTrxnAmount(int $contId): ?
string {
406 SUM( ft.total_amount ) AS total
407 FROM civicrm_financial_trxn AS ft
408 LEFT JOIN civicrm_entity_financial_trxn AS ceft ON ft.id = ceft.financial_trxn_id
409 WHERE ceft.entity_table = 'civicrm_contribution'
410 AND ceft.entity_id = {$contId}";
411 return CRM_Core_DAO
::singleValueQuery($query);
417 * @return null|string
419 public function _getFinancialItemAmount(int $contId): ?
string {
420 $lineItem = key(CRM_Price_BAO_LineItem
::getLineItems($contId, 'contribution'));
423 FROM civicrm_financial_item
424 WHERE entity_table = 'civicrm_line_item'
425 AND entity_id = {$lineItem}";
426 return CRM_Core_DAO
::singleValueQuery($query);
430 * @param array $params
431 * @param string $context
433 * @throws \API_Exception
435 public function _checkFinancialRecords($params, $context): void
{
436 $contributionID = $params['id'];
437 $trxn = $this->getFinancialTransactionsForContribution($contributionID);
440 'id' => $trxn->first()['financial_trxn_id'],
442 if ($context !== 'online' && $context !== 'payLater') {
444 'to_financial_account_id' => 6,
445 'total_amount' => 120,
449 if ($context === 'online') {
451 'to_financial_account_id' => 12,
452 'total_amount' => 120,
456 elseif ($context === 'payLater') {
458 'to_financial_account_id' => 7,
459 'total_amount' => 120,
463 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialTrxn', $trxnParams, $compareParams);
465 'financial_trxn_id' => $trxn->first()['financial_trxn_id'],
466 'entity_table' => 'civicrm_financial_item',
468 $entityTrxn = current(CRM_Financial_BAO_FinancialItem
::retrieveEntityFinancialTrxn($entityParams));
472 'financial_account_id' => $this->_getFinancialAccountId($this->financialTypeID
),
474 if ($context === 'payLater') {
478 'financial_account_id' => $this->_getFinancialAccountId($this->financialTypeID
),
481 $this->assertDBCompareValues('CRM_Financial_DAO_FinancialItem', [
482 'id' => $entityTrxn['entity_id'],
487 * @param int $financialTypeId
491 public function _getFinancialAccountId(int $financialTypeId): ?
int {
492 $accountRel = key(CRM_Core_PseudoConstant
::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Income Account is' "));
495 'entity_table' => 'civicrm_financial_type',
496 'entity_id' => $financialTypeId,
497 'account_relationship' => $accountRel,
501 CRM_Financial_BAO_FinancialTypeAccount
::retrieve($searchParams, $result);
502 return $result['financial_account_id'] ??
NULL;
506 * Test deleting a contribution.
508 * (It is unclear why this is in this class - it seems like maybe it doesn't
509 * test anything not on the contribution test class & might be copy and
512 * @throws \CRM_Core_Exception
514 public function testDeleteContribution(): void
{
515 $contributionID = $this->contributionCreate([
516 'contact_id' => $this->_individualId
,
518 'financial_type_id' => $this->financialTypeID
,
519 'invoice_id' => 'abc',
521 $this->callAPISuccess('contribution', 'delete', ['id' => $contributionID]);
525 * @param $contributionID
527 * @return \Civi\Api4\Generic\Result
528 * @throws \API_Exception
529 * @throws \Civi\API\Exception\UnauthorizedException
531 protected function getFinancialTransactionsForContribution($contributionID): \Civi\Api4\Generic\Result
{
532 return EntityFinancialTrxn
::get()
533 ->addWhere('id', '=', $contributionID)
534 ->addWhere('entity_table', '=', 'civicrm_contribution')
535 ->addSelect('*')->execute();