diff --git a/CRM/Contribute/Form/ContributionBase.php b/CRM/Contribute/Form/ContributionBase.php index 963044d2aa6c..e44a816bae8f 100644 --- a/CRM/Contribute/Form/ContributionBase.php +++ b/CRM/Contribute/Form/ContributionBase.php @@ -1138,40 +1138,33 @@ public function cancelRecurring() { * Arguably the form should start to build $this->_params in the pre-process main page & use that array consistently throughout. */ protected function setRecurringMembershipParams() { - $selectedMembershipTypeID = $this->_params['selectMembership'] ?? NULL; - if ($selectedMembershipTypeID) { - // @todo the price_x fields will ALWAYS allow us to determine the membership - so we should ignore - // 'selectMembership' and calculate from the price_x fields so we have one method that always works - // this is lazy & only catches when selectMembership is set, but the worst of all worlds would be to fix - // this with an else (calculate for price set). - $membershipTypes = CRM_Price_BAO_PriceSet::getMembershipTypesFromPriceSet($this->_priceSetId); - if (in_array($selectedMembershipTypeID, $membershipTypes['autorenew_required']) - || (in_array($selectedMembershipTypeID, $membershipTypes['autorenew_optional']) && - !empty($this->_params['is_recur'])) - ) { - $this->_params['auto_renew'] = TRUE; - } + $priceFieldId = array_key_first($this->_values['fee']); + // Why is this an array in CRM_Contribute_Form_Contribution_Main::submit and a string in CRM_Contribute_Form_Contribution_Confirm::preProcess()? + if (is_array($this->_params["price_{$priceFieldId}"])) { + $priceFieldValue = array_key_first($this->_params["price_{$priceFieldId}"]); + } + else { + $priceFieldValue = $this->_params["price_{$priceFieldId}"]; + } + $selectedMembershipTypeID = $this->_values['fee'][$priceFieldId]['options'][$priceFieldValue]['membership_type_id'] ?? NULL; + if (!$selectedMembershipTypeID) { + return; } - if ((!empty($this->_params['selectMembership']) || !empty($this->_params['priceSetId'])) - && !empty($this->_paymentProcessor['is_recur']) && - !empty($this->_params['auto_renew']) - && empty($this->_params['is_recur']) && empty($this->_params['frequency_interval']) - ) { + $membershipTypes = CRM_Price_BAO_PriceSet::getMembershipTypesFromPriceSet($this->_priceSetId); + if (in_array($selectedMembershipTypeID, $membershipTypes['autorenew_required']) + || (in_array($selectedMembershipTypeID, $membershipTypes['autorenew_optional']) && + !empty($this->_params['is_recur'])) + && !empty($this->_paymentProcessor['is_recur']) + ) { + $this->_params['auto_renew'] = TRUE; $this->_params['is_recur'] = $this->_values['is_recur'] = 1; - // check if price set is not quick config - if (!empty($this->_params['priceSetId']) && !CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_params['priceSetId'], 'is_quick_config')) { - list($this->_params['frequency_interval'], $this->_params['frequency_unit']) = CRM_Price_BAO_PriceSet::getRecurDetails($this->_params['priceSetId']); - } - else { - // FIXME: set interval and unit based on selected membership type - $this->_params['frequency_interval'] = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipType', - $this->_params['selectMembership'], 'duration_interval' - ); - $this->_params['frequency_unit'] = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipType', - $this->_params['selectMembership'], 'duration_unit' - ); - } + $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']; + $this->_params['frequency_unit'] = $this->_params['frequency_unit'] ?? $membershipTypeDetails['duration_unit']; } } diff --git a/CRM/Price/BAO/PriceSet.php b/CRM/Price/BAO/PriceSet.php index acc70725784d..b09f917bbced 100644 --- a/CRM/Price/BAO/PriceSet.php +++ b/CRM/Price/BAO/PriceSet.php @@ -1247,28 +1247,6 @@ public static function checkAutoRenewForPriceSet($priceSetId) { return $autoRenewOption; } - /** - * Retrieve auto renew frequency and interval. - * - * @param int $priceSetId - * Price set id. - * - * @return array - * associate array of frequency interval and unit - */ - public static function getRecurDetails($priceSetId) { - $query = 'SELECT mt.duration_interval, mt.duration_unit - FROM civicrm_price_field_value pfv - INNER JOIN civicrm_membership_type mt ON pfv.membership_type_id = mt.id - INNER JOIN civicrm_price_field pf ON pfv.price_field_id = pf.id - WHERE pf.price_set_id = %1 LIMIT 1'; - - $params = [1 => [$priceSetId, 'Integer']]; - $dao = CRM_Core_DAO::executeQuery($query, $params); - $dao->fetch(); - return [$dao->duration_interval, $dao->duration_unit]; - } - /** * @return object */ diff --git a/tests/phpunit/CRM/Contribute/Form/Contribution/MainTest.php b/tests/phpunit/CRM/Contribute/Form/Contribution/MainTest.php index 04ddd1f0caa9..1423778ba6ea 100644 --- a/tests/phpunit/CRM/Contribute/Form/Contribution/MainTest.php +++ b/tests/phpunit/CRM/Contribute/Form/Contribution/MainTest.php @@ -18,6 +18,24 @@ */ class CRM_Contribute_Form_Contribution_MainTest extends CiviUnitTestCase { + /** + * The id of the contribution page. + * @var int + */ + private int $contributionPageId; + + /** + * The id of the contribution page's payment processor. + * @var int + */ + private int $paymentProcessorId; + + /** + * The price set of the contribution page. + * @var int + */ + private int $priceSetId; + /** * Clean up DB. */ @@ -26,53 +44,84 @@ public function tearDown(): void { parent::tearDown(); } + /** + * Given a membership type ID, return the price field value. + */ + private function getPriceFieldValue($membershipTypeId) { + return $this->callAPISuccessGetValue('PriceFieldValue', ['membership_type_id' => $membershipTypeId, 'return' => 'id']); + } + + /** + * Establish a standard list of submit params to more accurately test the submission. + */ + private function getSubmitParams() { + return [ + 'id' => $this->contributionPageId, + 'amount' => 80, + 'first_name' => 'Billy', + 'last_name' => 'Gruff', + 'email' => 'billy@goat.gruff', + 'payment_processor_id' => $this->paymentProcessorId, + 'credit_card_number' => '4111111111111111', + 'credit_card_type' => 'Visa', + 'credit_card_exp_date' => ['M' => 9, 'Y' => 2040], + 'cvv2' => 123, + 'auto_renew' => 1, + 'priceSetId' => $this->priceSetId, + ]; + } + /** * Test that the membership is set to recurring if the membership type is always autorenew. */ public function testSetRecurFunction() { $membershipTypeID = $this->membershipTypeCreate(['auto_renew' => 2, 'minimum_fee' => 80]); $form = $this->getContributionForm(); - $form->testSubmit([ - 'selectMembership' => $membershipTypeID, - ]); + $priceFieldValueId = $this->getPriceFieldValue($membershipTypeID); + $form->testSubmit(array_merge($this->getSubmitParams(), [ + 'price_' . $this->priceSetId => $priceFieldValueId, + ])); $this->assertEquals(1, $form->_params['is_recur']); } /** - * Test that the membership is set to recurring if the membership type is always autorenew. + * Test that the membership is set to recurring if the membership type is optionally autorenew and is_recur is true. */ public function testSetRecurFunctionOptionalYes() { $membershipTypeID = $this->membershipTypeCreate(['auto_renew' => 1, 'minimum_fee' => 80]); $form = $this->getContributionForm(); - $form->testSubmit([ - 'selectMembership' => $membershipTypeID, + $priceFieldValueId = $this->getPriceFieldValue($membershipTypeID); + $form->testSubmit(array_merge($this->getSubmitParams(), [ + 'price_' . $this->priceSetId => $priceFieldValueId, 'is_recur' => 1, - ]); + ])); $this->assertEquals(1, $form->_params['is_recur']); } /** - * Test that the membership is set to recurring if the membership type is always autorenew. + * Test that the membership is not set to recurring if the membership type is optionally autorenew and is_recur is false. */ public function testSetRecurFunctionOptionalNo() { $membershipTypeID = $this->membershipTypeCreate(['auto_renew' => 1, 'minimum_fee' => 80]); $form = $this->getContributionForm(); - $form->testSubmit([ - 'selectMembership' => $membershipTypeID, + $priceFieldValueId = $this->getPriceFieldValue($membershipTypeID); + $form->testSubmit(array_merge($this->getSubmitParams(), [ + 'price_' . $this->priceSetId => $priceFieldValueId, 'is_recur' => 0, - ]); + ])); $this->assertEquals(0, $form->_params['is_recur']); } /** - * Test that the membership is set to recurring if the membership type is always autorenew. + * Test that the membership doesn't have an "is_recur" key if the membership type can never autorenew. */ public function testSetRecurFunctionNotAvailable() { $membershipTypeID = $this->membershipTypeCreate(['auto_renew' => 0, 'minimum_fee' => 80]); $form = $this->getContributionForm(); - $form->testSubmit([ - 'selectMembership' => $membershipTypeID, - ]); + $priceFieldValueId = $this->getPriceFieldValue($membershipTypeID); + $form->testSubmit(array_merge($this->getSubmitParams(), [ + 'price_' . $this->priceSetId => $priceFieldValueId, + ])); $this->assertArrayNotHasKey('is_recur', $form->_params); } @@ -82,11 +131,16 @@ public function testSetRecurFunctionNotAvailable() { * @return \CRM_Contribute_Form_Contribution_Main */ protected function getContributionForm($params = []) { - $params['priceSetID'] = $params['priceSetID'] ?? $this->callAPISuccessGetValue('PriceSet', [ + $this->priceSetId = $params['priceSetID'] ?? $this->callAPISuccessGetValue('PriceSet', [ 'name' => 'default_membership_type_amount', 'return' => 'id', ]); + $paymentProcessor = $this->paymentProcessorCreate([ + 'payment_processor_type_id' => 'Dummy', + 'is_test' => 0, + ]); + $contributionPageParams = (array_merge($params, [ 'currency' => 'NZD', 'goal_amount' => 6000, @@ -95,10 +149,7 @@ protected function getContributionForm($params = []) { 'pay_later_text' => 'Front up', 'pay_later_receipt' => 'Ta', 'is_email_receipt' => 1, - 'payment_processor' => $this->paymentProcessorCreate([ - 'payment_processor_type_id' => 'Dummy', - 'is_test' => 0, - ]), + 'payment_processor' => $paymentProcessor, 'amount_block_is_active' => 1, ])); @@ -106,9 +157,12 @@ protected function getContributionForm($params = []) { $form = $this->getFormObject('CRM_Contribute_Form_Contribution_Main'); $contributionPage = reset($this->contributionPageCreate($contributionPageParams)['values']); $form->set('id', $contributionPage['id']); - CRM_Price_BAO_PriceSet::addTo('civicrm_contribution_page', $contributionPage['id'], $params['priceSetID']); + CRM_Price_BAO_PriceSet::addTo('civicrm_contribution_page', $contributionPage['id'], $this->priceSetId); $form->preProcess(); $form->buildQuickForm(); + // Need these values to create more realistic submit params (in getSubmitParams). + $this->paymentProcessorId = $paymentProcessor; + $this->contributionPageId = (int) $contributionPage['id']; return $form; } diff --git a/tests/phpunit/api/v3/ContributionPageTest.php b/tests/phpunit/api/v3/ContributionPageTest.php index 74ca273263ca..952443e07a57 100644 --- a/tests/phpunit/api/v3/ContributionPageTest.php +++ b/tests/phpunit/api/v3/ContributionPageTest.php @@ -1406,12 +1406,18 @@ public function setUpMultiIntervalMembershipContributionPage(): void { $contributionPage = $this->callAPISuccess($this->_entity, 'create', $this->params); $this->_ids['contribution_page'] = $contributionPage['id']; - $this->ids['MembershipType'] = $this->membershipTypeCreate([ + $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', @@ -1433,18 +1439,29 @@ public function setUpMultiIntervalMembershipContributionPage(): void { 'label' => 'CRM-21177 - Monthly', 'amount' => 20, 'membership_num_terms' => 1, - 'membership_type_id' => $this->ids['MembershipType'], + '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' => 12, - 'membership_type_id' => $this->ids['MembershipType'], + 'membership_num_terms' => 1, + 'membership_type_id' => $this->ids['MembershipTypeYear'], 'price_field_id' => $this->_ids['price_field'], 'financial_type_id' => 'Member Dues', ]); @@ -1458,7 +1475,7 @@ public function setUpMultiIntervalMembershipContributionPage(): void { 'is_required' => TRUE, 'is_separate_payment' => FALSE, 'is_active' => TRUE, - 'membership_type_default' => $this->ids['MembershipType'], + 'membership_type_default' => $this->ids['MembershipTypeMonth'], ]); } @@ -1488,13 +1505,21 @@ public function testSubmitMultiIntervalMembershipContributionPage(): void { $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->_ids['contribution_page'], 'sequential' => 1, 'api.ContributionRecur.getsingle' => [], ]); $this->assertEquals(1, $contribution['values'][0]['api.ContributionRecur.getsingle']['frequency_interval']); - //$this->assertEquals(12, $contribution['values'][1]['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']); } /**