From 9394ac40b5ee3900b222f8852d839304f3e8ba25 Mon Sep 17 00:00:00 2001 From: Eileen McNaughton Date: Tue, 19 Dec 2023 00:01:40 +1300 Subject: [PATCH] Fix error on recurring membership in the rc Extend recur membership frequency test + fix to bi-monthly payments --- CRM/Contribute/Form/Contribution/Confirm.php | 2 +- CRM/Contribute/Form/ContributionBase.php | 27 ++-- CRM/Financial/BAO/Order.php | 1 + .../Contribute/Form/Contribution/ThankYou.tpl | 2 +- .../Form/Contribution/ConfirmTest.php | 106 +++++++++++++++ tests/phpunit/api/v3/ContributionPageTest.php | 123 ------------------ 6 files changed, 123 insertions(+), 138 deletions(-) diff --git a/CRM/Contribute/Form/Contribution/Confirm.php b/CRM/Contribute/Form/Contribution/Confirm.php index 02db81ad49..08c86427bd 100644 --- a/CRM/Contribute/Form/Contribution/Confirm.php +++ b/CRM/Contribute/Form/Contribution/Confirm.php @@ -1321,7 +1321,7 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr protected function processMembership($membershipParams, $contactID, $customFieldsFormatted, $premiumParams, $membershipLineItems): void { - $membershipTypeIDs = (array) $membershipParams['selectMembership']; + $membershipTypeIDs = array_keys($this->order->getMembershipTypes()); $membershipTypes = CRM_Member_BAO_Membership::buildMembershipTypeValues($this, $membershipTypeIDs); $membershipType = empty($membershipTypes) ? [] : reset($membershipTypes); diff --git a/CRM/Contribute/Form/ContributionBase.php b/CRM/Contribute/Form/ContributionBase.php index 306a6ab2af..d5d7561771 100644 --- a/CRM/Contribute/Form/ContributionBase.php +++ b/CRM/Contribute/Form/ContributionBase.php @@ -1143,30 +1143,31 @@ class CRM_Contribute_Form_ContributionBase extends CRM_Core_Form { $priceFieldValue = $this->_params["price_{$priceFieldId}"]; } $selectedMembershipTypeID = $this->_values['fee'][$priceFieldId]['options'][$priceFieldValue]['membership_type_id'] ?? NULL; - if (!$selectedMembershipTypeID) { + if (!$selectedMembershipTypeID || !$this->getPaymentProcessorValue('is_recur')) { return; } // Check if membership the selected membership is automatically opted into auto renew or give user the option. // In the 2nd case we check that the user has in deed opted in (auto renew as at June 22 is the field name for the membership auto renew checkbox) // Also check that the payment Processor used can support recurring contributions. - $membershipTypes = CRM_Price_BAO_PriceSet::getMembershipTypesFromPriceSet($this->getPriceSetID()); - if (in_array($selectedMembershipTypeID, $membershipTypes['autorenew_required']) - || (in_array($selectedMembershipTypeID, $membershipTypes['autorenew_optional']) && - !empty($this->_params['auto_renew'])) - && !empty($this->_paymentProcessor['is_recur']) + $membershipTypeDetails = CRM_Member_BAO_MembershipType::getMembershipType($selectedMembershipTypeID); + if ( + // 2 means required + $membershipTypeDetails['auto_renew'] === 2 + // 1 means optional - so they must also select the check box on the form + || ($membershipTypeDetails['auto_renew'] === 1 && !empty($this->_params['auto_renew'])) ) { $this->_params['auto_renew'] = TRUE; $this->_params['is_recur'] = $this->_values['is_recur'] = 1; - $membershipTypeDetails = \Civi\Api4\MembershipType::get(FALSE) - ->addWhere('id', '=', $selectedMembershipTypeID) - ->execute() - ->first(); - $this->_params['frequency_interval'] = $this->_params['frequency_interval'] ?? $this->_values['fee'][$priceFieldId]['options'][$priceFieldValue]['membership_num_terms']; + // If membership_num_terms is not specified on the the price field value (which seems not uncommon + // in default config) then the membership type provides the values. + // @todo - access the line item value from $this->getLineItems() rather than _values['fee'] + $this->_params['frequency_interval'] = $this->getSubmittedValue('frequency_interval') ?? ($this->_values['fee'][$priceFieldId]['options'][$priceFieldValue]['membership_num_terms'] ?? ($membershipTypeDetails['duration_interval'] ?? 1)); $this->_params['frequency_unit'] = $this->_params['frequency_unit'] ?? $membershipTypeDetails['duration_unit']; } - elseif (!$this->_separateMembershipPayment && (in_array($selectedMembershipTypeID, $membershipTypes['autorenew_required']) - || in_array($selectedMembershipTypeID, $membershipTypes['autorenew_optional']))) { + // This seems like it repeats the above with less care... + elseif (!$this->_separateMembershipPayment && ($membershipTypeDetails['auto_renew'] === 2 + || $membershipTypeDetails['auto_renew'] === 1)) { // otherwise check if we have a separate membership payment setting as that will allow people to independently opt into recurring contributions and memberships // If we don't have that and the membership type is auto recur or opt into recur set is_recur to 0. $this->_params['is_recur'] = $this->_values['is_recur'] = 0; diff --git a/CRM/Financial/BAO/Order.php b/CRM/Financial/BAO/Order.php index 8272744f13..ec85b13fec 100644 --- a/CRM/Financial/BAO/Order.php +++ b/CRM/Financial/BAO/Order.php @@ -957,6 +957,7 @@ class CRM_Financial_BAO_Order { $lineItem['membership_type_id'] = $lineItem['membership_type_id'] ?? NULL; if ($lineItem['membership_type_id']) { $lineItem['entity_table'] = 'civicrm_membership'; + $lineItem['membership_num_terms'] = $lineItem['membership_num_terms'] ?:1; } $lineItem['title'] = $this->getLineItemTitle($lineItem); $lineItem['tax_rate'] = $taxRate = $this->getTaxRate((int) $lineItem['financial_type_id']); diff --git a/templates/CRM/Contribute/Form/Contribution/ThankYou.tpl b/templates/CRM/Contribute/Form/Contribution/ThankYou.tpl index 948173ce94..8e17cca2ad 100644 --- a/templates/CRM/Contribute/Form/Contribution/ThankYou.tpl +++ b/templates/CRM/Contribute/Form/Contribution/ThankYou.tpl @@ -116,7 +116,7 @@ {if !empty($auto_renew)} {* Auto-renew membership confirmation *} {crmRegion name="contribution-thankyou-recur-membership"}
- {if $installments > 1} + {if !$installments || $installments > 1} {if $frequency_interval > 1} {ts 1=$frequency_interval 2=$frequency_unit}This membership will be renewed automatically every %1 %2(s).{/ts} {else} diff --git a/tests/phpunit/CRM/Contribute/Form/Contribution/ConfirmTest.php b/tests/phpunit/CRM/Contribute/Form/Contribution/ConfirmTest.php index 21ea558e29..8010ccded7 100644 --- a/tests/phpunit/CRM/Contribute/Form/Contribution/ConfirmTest.php +++ b/tests/phpunit/CRM/Contribute/Form/Contribution/ConfirmTest.php @@ -11,6 +11,7 @@ use Civi\Api4\Contribution; use Civi\Api4\LineItem; +use Civi\Api4\PriceSet; use Civi\Api4\PriceSetEntity; use Civi\Test\ContributionPageTestTrait; use Civi\Test\FormTrait; @@ -816,4 +817,109 @@ class CRM_Contribute_Form_Contribution_ConfirmTest extends CiviUnitTestCase { $this->assertEquals(5, $recurringContribution['contribution_status_id']); } + /** + * Helper function to set up contribution page which can be used to purchase a + * membership type for different intervals. + */ + public function setUpMultiIntervalMembershipContributionPage(): void { + // These all have auto_renew set to 2 - ie require auto-renew. + $this->membershipTypeCreate([ + 'name' => 'monthly', + 'auto_renew' => 2, + 'duration_unit' => 'month', + 'minimum_fee' => 10, + ], 'monthly'); + + $this->membershipTypeCreate([ + 'name' => 'bi_monthly', + 'auto_renew' => 2, + 'duration_unit' => 'month', + 'duration_interval' => 2, + 'minimum_fee' => 79, + ], 'bi_monthly'); + + $this->membershipTypeCreate([ + 'auto_renew' => 2, + 'name' => 'yearly', + 'duration_unit' => 'year', + 'minimum_fee' => 100, + ], 'yearly'); + $this->contributionPageQuickConfigCreate([], [], FALSE); + } + + /** + * Test submit with a membership block in place. + * + * @dataProvider getBooleanDataProvider + * + * @param bool $isQuickConfig + */ + public function testSubmitMultiIntervalMembershipContributionPage(bool $isQuickConfig): void { + $this->setUpMultiIntervalMembershipContributionPage(); + PriceSet::update()->setValues(['is_quick_config' => $isQuickConfig])->addWhere('id', '=', $this->ids['PriceSet']['QuickConfig'])->execute(); + if (!$isQuickConfig) { + $this->createTestEntity('PriceFieldValue', [ + 'name' => 'CRM-21177_12_Months', + 'label' => 'CRM-21177 - 12 Months', + 'amount' => 200, + 'membership_num_terms' => 12, + 'membership_type_id' => $this->ids['MembershipType']['monthly'], + 'price_field_id' => $this->ids['PriceField']['membership_amount'], + 'financial_type_id:name' => 'Member Dues', + ], 'membership_12_months'); + } + $submitParams = [ + 'price_' . $this->ids['PriceField']['membership_amount'] => $this->ids['PriceFieldValue']['membership_monthly'], + 'first_name' => 'Billy', + 'last_name' => 'Gruff', + 'email' => 'billy@goat.gruff', + 'payment_processor_id' => $this->ids['PaymentProcessor']['dummy'], + 'credit_card_number' => '4111111111111111', + 'credit_card_type' => 'Visa', + 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040], + 'cvv2' => 123, + ]; + $this->submitOnlineContributionForm($submitParams, + $this->getContributionPageID()); + $membership = $this->callAPISuccessGetSingle('Membership', []); + $contributionRecur = $this->callAPISuccessGetSingle('ContributionRecur', ['id' => $membership['contribution_recur_id']]); + $this->assertEquals('month', $contributionRecur['frequency_unit']); + $this->assertEquals(1, $contributionRecur['frequency_interval']); + + $submitParams['price_' . $this->ids['PriceField']['membership_amount']] = $this->ids['PriceFieldValue']['membership_yearly']; + $this->submitOnlineContributionForm($submitParams, + $this->getContributionPageID()); + $membership = $this->callAPISuccessGetSingle('Membership', ['membership_type_id' => $this->ids['MembershipType']['yearly']]); + $contributionRecur = $this->callAPISuccessGetSingle('ContributionRecur', ['id' => $membership['contribution_recur_id']]); + $this->assertEquals('year', $contributionRecur['frequency_unit']); + $this->assertEquals(1, $contributionRecur['frequency_interval']); + + if (!$isQuickConfig) { + $submitParams['price_' . $this->ids['PriceField']['membership_amount']] = $this->ids['PriceFieldValue']['membership_12_months']; + $this->submitOnlineContributionForm($submitParams, + $this->getContributionPageID()); + $contribution = $this->callAPISuccess('Contribution', 'get', [ + 'contribution_page_id' => $this->getContributionPageID(), + 'sequential' => 1, + 'api.ContributionRecur.getsingle' => [], + 'version' => 3, + ]); + $this->assertEquals(1, $contribution['values'][0]['api.ContributionRecur.getsingle']['frequency_interval']); + $this->assertEquals(1, $contribution['values'][1]['api.ContributionRecur.getsingle']['frequency_interval']); + $this->assertEquals(12, $contribution['values'][2]['api.ContributionRecur.getsingle']['frequency_interval']); + + $this->assertEquals('month', $contribution['values'][0]['api.ContributionRecur.getsingle']['frequency_unit']); + $this->assertEquals('year', $contribution['values'][1]['api.ContributionRecur.getsingle']['frequency_unit']); + $this->assertEquals('month', $contribution['values'][2]['api.ContributionRecur.getsingle']['frequency_unit']); + } + + $submitParams['price_' . $this->ids['PriceField']['membership_amount']] = $this->ids['PriceFieldValue']['membership_bi_monthly']; + $this->submitOnlineContributionForm($submitParams, + $this->getContributionPageID()); + $membership = $this->callAPISuccessGetSingle('Membership', ['membership_type_id' => $this->ids['MembershipType']['bi_monthly']]); + $contributionRecur = $this->callAPISuccessGetSingle('ContributionRecur', ['id' => $membership['contribution_recur_id']]); + $this->assertEquals('month', $contributionRecur['frequency_unit']); + $this->assertEquals(2, $contributionRecur['frequency_interval']); + } + } diff --git a/tests/phpunit/api/v3/ContributionPageTest.php b/tests/phpunit/api/v3/ContributionPageTest.php index e9f86f2234..495303f5ac 100644 --- a/tests/phpunit/api/v3/ContributionPageTest.php +++ b/tests/phpunit/api/v3/ContributionPageTest.php @@ -1007,129 +1007,6 @@ class api_v3_ContributionPageTest extends CiviUnitTestCase { } } - /** - * Helper function to set up contribution page which can be used to purchase a - * membership type for different intervals. - */ - public function setUpMultiIntervalMembershipContributionPage(): void { - $this->setupPaymentProcessor(); - $contributionPage = $this->callAPISuccess('ContributionPage', 'create', $this->params); - $this->ids['ContributionPage']['Membership'] = $contributionPage['id']; - - $this->ids['MembershipTypeMonth'] = $this->membershipTypeCreate([ - // force auto-renew - 'auto_renew' => 2, - 'duration_unit' => 'month', - ]); - - $this->ids['MembershipTypeYear'] = $this->membershipTypeCreate([ - // force auto-renew - 'auto_renew' => 2, - 'duration_unit' => 'year', - ]); - - $priceSet = $this->callAPISuccess('PriceSet', 'create', [ - 'is_quick_config' => 0, - 'extends' => 'CiviMember', - 'financial_type_id' => 'Member Dues', - 'title' => 'CRM-21177', - ]); - $this->_ids['price_set'] = $priceSet['id']; - - $priceField = $this->callAPISuccess('price_field', 'create', [ - 'price_set_id' => $this->_ids['price_set'], - 'name' => 'membership_type', - 'label' => 'Membership Type', - 'html_type' => 'Radio', - ]); - $this->_ids['price_field'] = $priceField['id']; - - $priceFieldValueMonthly = $this->callAPISuccess('price_field_value', 'create', [ - 'name' => 'CRM-21177_Monthly', - 'label' => 'CRM-21177 - Monthly', - 'amount' => 20, - 'membership_num_terms' => 1, - 'membership_type_id' => $this->ids['MembershipTypeMonth'], - 'price_field_id' => $this->_ids['price_field'], - 'financial_type_id' => 'Member Dues', - ]); - $this->_ids['price_field_value_monthly'] = $priceFieldValueMonthly['id']; - - $priceFieldValue12Months = $this->callAPISuccess('price_field_value', 'create', [ - 'name' => 'CRM-21177_12_Months', - 'label' => 'CRM-21177 - 12 Months', - 'amount' => 200, - 'membership_num_terms' => 12, - 'membership_type_id' => $this->ids['MembershipTypeMonth'], - 'price_field_id' => $this->_ids['price_field'], - 'financial_type_id' => 'Member Dues', - ]); - $this->_ids['price_field_value_12_months'] = $priceFieldValue12Months['id']; - - $priceFieldValueYearly = $this->callAPISuccess('price_field_value', 'create', [ - 'name' => 'CRM-21177_Yearly', - 'label' => 'CRM-21177 - Yearly', - 'amount' => 200, - 'membership_num_terms' => 1, - 'membership_type_id' => $this->ids['MembershipTypeYear'], - 'price_field_id' => $this->_ids['price_field'], - 'financial_type_id' => 'Member Dues', - ]); - $this->_ids['price_field_value_yearly'] = $priceFieldValueYearly['id']; - - CRM_Price_BAO_PriceSet::addTo('civicrm_contribution_page', $this->getContributionPageID(), $this->_ids['price_set']); - - $this->callAPISuccess('membership_block', 'create', [ - 'entity_id' => $this->getContributionPageID(), - 'entity_table' => 'civicrm_contribution_page', - 'is_required' => TRUE, - 'is_separate_payment' => FALSE, - 'is_active' => TRUE, - 'membership_type_default' => $this->ids['MembershipTypeMonth'], - ]); - } - - /** - * Test submit with a membership block in place. - */ - public function testSubmitMultiIntervalMembershipContributionPage(): void { - $this->setUpMultiIntervalMembershipContributionPage(); - $submitParams = [ - 'price_' . $this->_ids['price_field'] => $this->_ids['price_field_value_monthly'], - 'id' => $this->getContributionPageID(), - 'amount' => 20, - 'first_name' => 'Billy', - 'last_name' => 'Gruff', - 'email' => 'billy@goat.gruff', - 'payment_processor_id' => $this->ids['PaymentProcessor']['dummy'], - 'credit_card_number' => '4111111111111111', - 'credit_card_type' => 'Visa', - 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040], - 'cvv2' => 123, - 'auto_renew' => 1, - ]; - $this->callAPISuccess('contribution_page', 'submit', $submitParams); - - $submitParams['price_' . $this->_ids['price_field']] = $this->_ids['price_field_value_yearly']; - $this->callAPISuccess('contribution_page', 'submit', $submitParams); - - $submitParams['price_' . $this->_ids['price_field']] = $this->_ids['price_field_value_12_months']; - $this->callAPISuccess('contribution_page', 'submit', $submitParams); - - $contribution = $this->callAPISuccess('Contribution', 'get', [ - 'contribution_page_id' => $this->getContributionPageID(), - 'sequential' => 1, - 'api.ContributionRecur.getsingle' => [], - ]); - $this->assertEquals(1, $contribution['values'][0]['api.ContributionRecur.getsingle']['frequency_interval']); - $this->assertEquals(1, $contribution['values'][1]['api.ContributionRecur.getsingle']['frequency_interval']); - $this->assertEquals(12, $contribution['values'][2]['api.ContributionRecur.getsingle']['frequency_interval']); - - $this->assertEquals('month', $contribution['values'][0]['api.ContributionRecur.getsingle']['frequency_unit']); - $this->assertEquals('year', $contribution['values'][1]['api.ContributionRecur.getsingle']['frequency_unit']); - $this->assertEquals('month', $contribution['values'][2]['api.ContributionRecur.getsingle']['frequency_unit']); - } - /** * Create a payment processor instance. */ -- 2.25.1