From aac3be0fb3812930fd174c2f91aa9a61dc0942f7 Mon Sep 17 00:00:00 2001 From: Eileen McNaughton Date: Tue, 14 Nov 2023 07:19:51 +1300 Subject: [PATCH] Move set up of complicated price set to the ContributionPageTestTrait & test to the confirmTest We need to add some more testing but most of our existing tests are within the apiv3 test harness which is not a great location. This moves one test over to the full form test flow and also pimps the helper trait to support this complex price scenario (with fields of a mixture of types & financial types. I want to add some checks on the email output but this specific test is pay later so I won't do it in this PR --- Civi/Test/ContributionPageTestTrait.php | 144 ++++++++++++++++++ .../Form/Contribution/ConfirmTest.php | 64 ++++++++ tests/phpunit/api/v3/ContributionPageTest.php | 115 -------------- 3 files changed, 208 insertions(+), 115 deletions(-) diff --git a/Civi/Test/ContributionPageTestTrait.php b/Civi/Test/ContributionPageTestTrait.php index d20bc0863f..c1003e74b6 100644 --- a/Civi/Test/ContributionPageTestTrait.php +++ b/Civi/Test/ContributionPageTestTrait.php @@ -56,6 +56,12 @@ trait ContributionPageTestTrait { } /** + * Create a paid contribution page. + * + * This function ensures that the page has pay later configured, unless specified as false + * and that there is a payment_processor configured, unless the key payment_processor + * is already set. + * * @param array $contributionPageValues * @param array $priceSetParameters * Currently if 'id' is passed in then no update is made, but this could change @@ -65,6 +71,23 @@ trait ContributionPageTestTrait { */ public function contributionPageCreatePaid(array $contributionPageValues, array $priceSetParameters = [], string $identifier = 'ContributionPage'): array { $contributionPageValues['is_monetary'] = TRUE; + $contributionPageValues += ['is_pay_later' => TRUE, 'pay_later_text' => 'Send check by mail']; + if (!array_key_exists('payment_processor', $contributionPageValues)) { + $this->createTestEntity('PaymentProcessor', [ + 'name' => 'dummy', + 'label' => 'Dummy', + 'payment_processor_type_id:name' => 'Dummy', + 'frontend_title' => 'Dummy Front end', + ], 'dummy'); + $this->createTestEntity('PaymentProcessor', [ + 'name' => 'dummy', + 'label' => 'Dummy Test', + 'payment_processor_type_id:name' => 'Dummy', + 'frontend_title' => 'Dummy Front end (test)', + 'is_test' => TRUE, + ], 'dummy_test'); + $contributionPageValues['payment_processor'] = [$this->ids['PaymentProcessor']['dummy']]; + } $contributionPageResult = $this->contributionPageCreate($contributionPageValues, $identifier); $priceSetParameters += [ 'title' => 'Price Set', @@ -110,4 +133,125 @@ trait ContributionPageTestTrait { return 0; } + /** + * Set up a contribution page with a complex price set. + * + * The created price set has 5 fields using a mixture of financial type 1 & 2, 3 which are created. + * + * More fields may be added. + * + * - Radio field (key= 'radio_field') with 3 options ('20_dollars','10_dollars','free'), financial type ID is 'first' + * - Select field (key= 'select_field') with 2 options ('40_dollars','30_dollars'), financial type ID is 'second' + * - Text field ('text_field_16.95') with amount = 16.95 - ie if qty is 2 then amount is 33.90, financial type ID is 'second' + * - Text field ('text_field_2.95') with amount = 2.95 - ie if qty is 2 then amount is 5.90, financial type ID is 'second' + * - Text field ('text_field') with amount = 1 - ie if qty is 2 then amount is 2, financial type ID is 'first' + * - CheckBox field ('check_box') with amount = 55, financial type ID is 'third' + * + * @param array $contributionPageParameters + * @param array $priceSetParameters + */ + public function contributionPageWithPriceSetCreate(array $contributionPageParameters = [], array $priceSetParameters = []): void { + $this->contributionPageCreatePaid($contributionPageParameters, $priceSetParameters); + $priceSetID = $this->ids['PriceSet']['ContributionPage']; + $this->createTestEntity('FinancialType', ['name' => 'Financial Type 1'], 'first'); + $this->createTestEntity('FinancialType', ['name' => 'Financial Type 2'], 'second'); + $this->createTestEntity('FinancialType', ['name' => 'Financial Type 3'], 'third'); + $priceField = $this->createTestEntity('PriceField', [ + 'price_set_id' => $priceSetID, + 'label' => 'Financial Type 1, radio field', + 'html_type' => 'Radio', + 'name' => 'radio_field', + ], 'radio_field'); + $this->createTestEntity('PriceFieldValue', [ + 'price_field_id' => $this->ids['PriceField']['radio_field'], + 'label' => 'Twenty dollars', + 'financial_type_id:name' => 'Financial Type 1', + 'amount' => 20, + 'non_deductible_amount' => 15, + ], '20_dollars'); + $this->createTestEntity('PriceFieldValue', [ + 'price_field_id' => $priceField['id'], + 'label' => '10 dollars', + 'financial_type_id:name' => 'Financial Type 1', + 'amount' => 10, + 'non_deductible_amount' => 5, + ], '10_dollars'); + + $this->createTestEntity('PriceFieldValue', [ + 'price_field_id' => $priceField['id'], + 'label' => 'Free', + 'financial_type_id:name' => 'Financial Type 1', + 'amount' => 0, + 'non_deductible_amount' => 0, + 'name' => 'free', + ], 'free')['id']; + + $this->createTestEntity('PriceField', [ + 'price_set_id' => $priceSetID, + 'label' => 'Financial Type 2, select field', + 'html_type' => 'Select', + 'name' => 'select_field', + ], 'select_field'); + + $this->createTestEntity('PriceFieldValue', [ + 'price_field_id' => $this->ids['PriceField']['select_field'], + 'label' => 'Forty dollars', + 'financial_type_id:name' => 'Financial Type 2', + 'amount' => 40, + 'non_deductible_amount' => 5, + ], '40_dollars'); + + $this->createTestEntity('PriceFieldValue', [ + 'price_field_id' => $this->ids['PriceField']['select_field'], + 'label' => 'Thirty dollars', + 'financial_type_id:name' => 'Financial Type 2', + 'amount' => 30, + 'non_deductible_amount' => 5, + ], '30_dollars'); + + $this->createTestEntity('PriceField', [ + 'price_set_id' => $priceSetID, + 'label' => 'Quantity * 16.95', + 'html_type' => 'Text', + 'name' => 'text_field_16.95', + ], 'text_field_16.95'); + + $this->createTestEntity('PriceFieldValue', [ + 'price_field_id' => $this->ids['PriceField']['text_field_16.95'], + 'label' => 'Quantity * 16.95', + 'financial_type_id:name' => 'Financial Type 2', + 'amount' => '16.95', + 'name' => 'text_field_16.95', + ], 'text_field_16.95'); + + $this->createTestEntity('PriceField', [ + 'price_set_id' => $priceSetID, + 'label' => '2.95 text field', + 'name' => 'text_field_2.95', + 'html_type' => 'Text', + ], 'text_field_2.95'); + + $this->createTestEntity('PriceFieldValue', [ + 'price_field_id' => $this->ids['PriceField']['text_field_2.95'], + 'label' => 'Quantity * 2.95', + 'name' => 'text_field_2.95', + 'financial_type_id:name' => 'Financial Type 2', + 'amount' => '2.95', + ], 'text_field_2.95'); + + $this->createTestEntity('PriceField', [ + 'price_set_id' => $priceSetID, + 'label' => 'Checkbox', + 'name' => 'check_box', + 'html_type' => 'CheckBox', + ], 'check_box'); + $this->createTestEntity('PriceFieldValue', [ + 'price_field_id' => $this->ids['PriceField']['check_box'], + 'label' => 'CheckBox, 55 donation', + 'financial_type_id:name' => 'Financial Type 3', + 'amount' => 55, + 'name' => 'check_box', + ], 'check_box'); + } + } diff --git a/tests/phpunit/CRM/Contribute/Form/Contribution/ConfirmTest.php b/tests/phpunit/CRM/Contribute/Form/Contribution/ConfirmTest.php index cf8de42c8c..9244ec4cca 100644 --- a/tests/phpunit/CRM/Contribute/Form/Contribution/ConfirmTest.php +++ b/tests/phpunit/CRM/Contribute/Form/Contribution/ConfirmTest.php @@ -9,6 +9,7 @@ +--------------------------------------------------------------------+ */ +use Civi\Api4\LineItem; use Civi\Api4\PriceSetEntity; use Civi\Test\ContributionPageTestTrait; use Civi\Test\FormTrait; @@ -323,4 +324,67 @@ class CRM_Contribute_Form_Contribution_ConfirmTest extends CiviUnitTestCase { return $form; } + /** + * Test Tax Amount is calculated properly when using PriceSet with Field Type = Text/Numeric Quantity + * + * This test creates a pending (pay later) contribution with 3 line items + * + * |qty | unit_price| line_total| tax |total including tax| + * | 1 | 10 | 10 | 0 | 10 | + * | 180 | 16.95 | 3051 |305.1 | 3356.1| + * | 110 | 2.95 | 324.5 | 32.45 | 356.95| + * + * Contribution total = 3723.05 + * made up of tax 337.55 + * non tax 3385.5 + * + * @param string $thousandSeparator + * punctuation used to refer to thousands. + * + * @throws \CRM_Core_Exception + * + * @dataProvider getThousandSeparators + */ + public function testSubmitContributionPageWithPriceSetQuantity(string $thousandSeparator): void { + $this->setCurrencySeparators($thousandSeparator); + $this->enableTaxAndInvoicing(); + $this->contributionPageWithPriceSetCreate([], ['is_quick_config' => FALSE]); + // This function sets the Tax Rate at 10% - it currently has no way to pass Tax Rate into it - so let's work with 10% + $this->addTaxAccountToFinancialType($this->ids['FinancialType']['second']); + $submitParams = [ + 'id' => $this->getContributionPageID(), + 'first_name' => 'J', + 'last_name' => 'T', + 'email' => 'JT@ohcanada.ca', + 'receive_date' => date('Y-m-d H:i:s'), + 'payment_processor_id' => 0, + 'priceSetId' => $this->getPriceSetID('ContributionPage'), + ]; + + // Add Existing PriceField + // qty = 1; unit_price = $10.00. No sales tax. + $submitParams['price_' . $this->ids['PriceField']['radio_field']] = $this->ids['PriceFieldValue']['10_dollars']; + + // Set quantity for our 16.95 text field to 180 - ie 180 * 16.95 is the code and 180 * 16.95 * 0.10 is the tax. + $submitParams['price_' . $this->ids['PriceField']['text_field_16.95']] = 180; + + // Set quantity for our 2.95 text field to 110 - ie 180 * 2.95 is the code and 110 * 2.95 * 0.10 is the tax. + $submitParams['price_' . $this->ids['PriceField']['text_field_2.95']] = 110; + + // This is the correct Tax Amount - use it later to compare to what the CiviCRM Core came up with at the LineItem level + $taxAmount = ((180 * 16.95 * 0.10) + (110 * 2.95 * 0.10)); + $totalAmount = 10 + (180 * 16.95) + (110 * 2.95); + + $this->submitOnlineContributionForm($submitParams, $this->getContributionPageID()); + $this->validateAllContributions(); + + $contribution = $this->callAPISuccessGetSingle('Contribution', [ + 'contribution_page_id' => $this->getContributionPageID(), + ]); + + $lineItems = LineItem::get()->addWhere('contribution_id', '=', $contribution['id'])->execute(); + $this->assertEquals($lineItems[0]['line_total'] + $lineItems[1]['line_total'] + $lineItems[2]['line_total'], round($totalAmount, 2), 'Line Item Total is incorrect.'); + $this->assertEquals(round($lineItems[0]['tax_amount'] + $lineItems[1]['tax_amount'] + $lineItems[2]['tax_amount'], 2), round($taxAmount, 2), 'Wrong Sales Tax Amount is calculated and stored.'); + } + } diff --git a/tests/phpunit/api/v3/ContributionPageTest.php b/tests/phpunit/api/v3/ContributionPageTest.php index 2fade00483..5297027ff5 100644 --- a/tests/phpunit/api/v3/ContributionPageTest.php +++ b/tests/phpunit/api/v3/ContributionPageTest.php @@ -1611,121 +1611,6 @@ class api_v3_ContributionPageTest extends CiviUnitTestCase { ]; } - /** - * Test Tax Amount is calculated properly when using PriceSet with Field Type = Text/Numeric Quantity - * - * The created contribution has 3 line items - * - * |qty | unit_price| line_total| tax |total including tax| - * | 1 | 10 | 10 | 0 | 10 | - * | 180 | 16.95 | 3051 |305.1 | 3356.1| - * | 110 | 2.95 | 324.5 | 32.45 | 356.95| - * - * Contribution total = 3723.05 - * made up of tax 337.55 - * non tax 3385.5 - * - * @param string $thousandSeparator - * punctuation used to refer to thousands. - * - * @throws \CRM_Core_Exception - * @dataProvider getThousandSeparators - */ - public function testSubmitContributionPageWithPriceSetQuantity(string $thousandSeparator): void { - $this->setCurrencySeparators($thousandSeparator); - $this->enableTaxAndInvoicing(); - $financialType = $this->createFinancialType(); - $financialTypeId = $financialType['id']; - // This function sets the Tax Rate at 10% - it currently has no way to pass Tax Rate into it - so let's work with 10% - $this->addTaxAccountToFinancialType($financialType['id']); - - $this->setUpContributionPage([], ['is_quick_config' => FALSE]); - $submitParams = [ - 'id' => $this->getContributionPageID(), - 'first_name' => 'J', - 'last_name' => 'T', - 'email' => 'JT@ohcanada.ca', - 'is_pay_later' => TRUE, - 'receive_date' => date('Y-m-d H:i:s'), - ]; - - // Add Existing PriceField - // This is a Shoe-eating Goat; qty = 1; unit_price = $10.00; There is no sales tax on Goats - $submitParams['price_' . $this->_ids['price_field'][0]] = reset($this->_ids['price_field_value']); - - // Create additional PriceSet/PriceField - $priceSetID = $this->getPriceSetID(); - $priceField = $this->callAPISuccess('PriceField', 'create', [ - 'price_set_id' => $priceSetID, - 'label' => 'Printing Rights', - 'html_type' => 'Text', - ]); - - $this->callAPISuccess('PriceFieldValue', 'create', [ - 'price_set_id' => $priceSetID, - 'price_field_id' => $priceField['id'], - 'label' => 'Printing Rights', - 'financial_type_id' => $financialTypeId, - 'amount' => '16.95', - ]); - $priceFieldId = $priceField['id']; - - // Set quantity for our test - $submitParams['price_' . $priceFieldId] = 180; - - $priceField = $this->callAPISuccess('PriceField', 'create', [ - 'price_set_id' => $priceSetID, - 'label' => 'Another Line Item', - 'html_type' => 'Text', - ]); - - $this->callAPISuccess('price_field_value', 'create', [ - 'price_set_id' => $priceSetID, - 'price_field_id' => $priceField['id'], - 'label' => 'Another Line Item', - 'financial_type_id' => $financialTypeId, - 'amount' => '2.95', - ]); - $priceFieldId = $priceField['id']; - - // Set quantity for our test - $submitParams['price_' . $priceFieldId] = 110; - - // This is the correct Tax Amount - use it later to compare to what the CiviCRM Core came up with at the LineItem level - $submitParams['tax_amount'] = (180 * 16.95 * 0.10 + 110 * 2.95 * 0.10); - - $this->callAPISuccess('ContributionPage', 'submit', $submitParams); - $this->validateAllContributions(); - - $contribution = $this->callAPISuccessGetSingle('Contribution', [ - 'contribution_page_id' => $this->getContributionPageID(), - ]); - - // Retrieve the lineItem that belongs to the Goat - $lineItem1 = $this->callAPISuccessGetSingle('LineItem', [ - 'contribution_id' => $contribution['id'], - 'label' => 'Shoe-eating Goat', - 'return' => ['line_total', 'tax_amount'], - ]); - - // Retrieve the lineItem that belongs to the Printing Rights and check the tax_amount CiviCRM Core calculated for it - $lineItem2 = $this->callAPISuccessGetSingle('LineItem', [ - 'contribution_id' => $contribution['id'], - 'label' => 'Printing Rights', - 'return' => ['line_total', 'tax_amount'], - ]); - - // Retrieve the lineItem that belongs to the Another Line Item and check the tax_amount CiviCRM Core calculated for it - $lineItem3 = $this->callAPISuccessGetSingle('LineItem', [ - 'contribution_id' => $contribution['id'], - 'label' => 'Another Line Item', - 'return' => ['line_total', 'tax_amount'], - ]); - - $this->assertEquals($lineItem1['line_total'] + $lineItem2['line_total'] + $lineItem3['line_total'], round(10 + 180 * 16.95 + 110 * 2.95, 2), 'Line Item Total is incorrect.'); - $this->assertEquals(round($lineItem1['tax_amount'] + $lineItem2['tax_amount'] + $lineItem3['tax_amount'], 2), round(180 * 16.95 * 0.10 + 110 * 2.95 * 0.10, 2), 'Wrong Sales Tax Amount is calculated and stored.'); - } - /** * Test validating a contribution page submit. */ -- 2.25.1