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
}
/**
+ * 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
*/
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',
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');
+ }
+
}
+--------------------------------------------------------------------+
*/
+use Civi\Api4\LineItem;
use Civi\Api4\PriceSetEntity;
use Civi\Test\ContributionPageTestTrait;
use Civi\Test\FormTrait;
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.');
+ }
+
}
];
}
- /**
- * 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.
*/