Move set up of complicated price set to the ContributionPageTestTrait & test to the...
authorEileen McNaughton <emcnaughton@wikimedia.org>
Mon, 13 Nov 2023 18:19:51 +0000 (07:19 +1300)
committerEileen McNaughton <emcnaughton@wikimedia.org>
Tue, 14 Nov 2023 01:24:22 +0000 (14:24 +1300)
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
tests/phpunit/CRM/Contribute/Form/Contribution/ConfirmTest.php
tests/phpunit/api/v3/ContributionPageTest.php

index d20bc0863fb2631cfc04ac39fc823551c60ed7c7..c1003e74b6eac1b6f4dc9cc5f084a2bb0d65e14e 100644 (file)
@@ -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');
+  }
+
 }
index cf8de42c8c29c644138d8d915362e43ebc416c62..9244ec4cca7bcf96f1e63d66912df9ea0f85e0e1 100644 (file)
@@ -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.');
+  }
+
 }
index 2fade00483d4edcc3db670f4fb5ce6bc98c4a264..5297027ff5fa13f15e7d19d9e71a37b133e6dc30 100644 (file)
@@ -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.
    */