Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CRM-19594 fix line item memberships for price sets with multiple memb… #9449

Merged
merged 1 commit into from
Nov 24, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions CRM/Contribute/Form/Contribution/Confirm.php
Original file line number Diff line number Diff line change
Expand Up @@ -1456,6 +1456,7 @@ protected function postProcessMembership(
$errors = $paymentResults = array();
$form->_values['isMembership'] = TRUE;
$isRecurForFirstTransaction = CRM_Utils_Array::value('is_recur', $form->_values, CRM_Utils_Array::value('is_recur', $membershipParams));
$unprocessedLineItems = $form->_lineItem;
$totalAmount = $membershipParams['amount'];

if ($isPaidMembership) {
Expand Down Expand Up @@ -1521,7 +1522,26 @@ protected function postProcessMembership(
//@todo it should no longer be possible for it to get to this point & membership to not be an array
if (is_array($membershipTypeIDs) && !empty($membershipContributionID)) {
$typesTerms = CRM_Utils_Array::value('types_terms', $membershipParams, array());

$membershipLines = $nonMembershipLines = array();
foreach ($unprocessedLineItems as $priceSetID => $lines) {
foreach ($lines as $line) {
if (!empty($line['membership_type_id'])) {
$membershipLines[$line['membership_type_id']] = $line['price_field_value_id'];
}
}
}

$i = 1;
foreach ($membershipTypeIDs as $memType) {
if ($i < count($membershipTypeIDs)) {
$membershipLineItems[$priceSetID][$membershipLines[$memType]] = $unprocessedLineItems[$priceSetID][$membershipLines[$memType]];
unset($unprocessedLineItems[$priceSetID][$membershipLines[$memType]]);
}
else {
$membershipLineItems = $unprocessedLineItems;
}
$i++;
$numTerms = CRM_Utils_Array::value($memType, $typesTerms, 1);
if (!empty($membershipContribution)) {
$pendingStatus = CRM_Core_OptionGroup::getValue('contribution_status', 'Pending', 'name');
Expand Down Expand Up @@ -1557,7 +1577,7 @@ protected function postProcessMembership(
$customFieldsFormatted,
$numTerms, $membershipID, $pending,
$contributionRecurID, $membershipSource, $isPayLater, $campaignId, array(), $membershipContribution,
$form->_lineItem
$membershipLineItems
);

$form->set('renewal_mode', $renewalMode);
Expand Down Expand Up @@ -1936,9 +1956,20 @@ public static function submit($params) {
else {
$form->_params['payment_processor_id'] = 0;
}

$priceFields = $priceFields[$priceSetID]['fields'];
CRM_Price_BAO_PriceSet::processAmount($priceFields, $paramsProcessedForForm, $lineItems, 'civicrm_contribution');
$form->_lineItem = array($priceSetID => $lineItems);
$membershipPriceFieldIDs = array();
foreach ((array) $lineItems as $lineItem) {
if (!empty($lineItem['membership_type_id'])) {
$form->set('useForMember', 1);
$form->_useForMember = 1;
$membershipPriceFieldIDs['id'] = $priceSetID;
$membershipPriceFieldIDs[] = $lineItem['price_field_value_id'];
}
}
$form->set('memberPriceFieldIDS', $membershipPriceFieldIDs);
$form->processFormSubmission(CRM_Utils_Array::value('contact_id', $params));
}

Expand Down Expand Up @@ -2325,7 +2356,7 @@ protected function doMembershipProcessing($contactID, $membershipParams, $premiu
$priceFieldIds = $this->get('memberPriceFieldIDS');

if (!empty($priceFieldIds)) {
$contributionTypeID = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $priceFieldIds['id'], 'financial_type_id');
$financialTypeID = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $priceFieldIds['id'], 'financial_type_id');
unset($priceFieldIds['id']);
$membershipTypeIds = array();
$membershipTypeTerms = array();
Expand All @@ -2344,7 +2375,7 @@ protected function doMembershipProcessing($contactID, $membershipParams, $premiu
}
}
$membershipParams['selectMembership'] = $membershipTypeIds;
$membershipParams['financial_type_id'] = $contributionTypeID;
$membershipParams['financial_type_id'] = $financialTypeID;
$membershipParams['types_terms'] = $membershipTypeTerms;
}
if (!empty($membershipParams['selectMembership'])) {
Expand Down
4 changes: 2 additions & 2 deletions CRM/Contribute/Form/Contribution/Main.php
Original file line number Diff line number Diff line change
Expand Up @@ -1054,7 +1054,7 @@ public function submit($params) {

$params['currencyID'] = CRM_Core_Config::singleton()->defaultCurrency;

$is_quick_config = 0;
// @todo refactor this & leverage it from the unit tests.
if (!empty($params['priceSetId'])) {
$is_quick_config = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_priceSetId, 'is_quick_config');
if ($is_quick_config) {
Expand Down Expand Up @@ -1118,7 +1118,7 @@ public function submit($params) {
}
}
//If the membership & contribution is used in contribution page & not separate payment
$fieldId = $memPresent = $membershipLabel = $fieldOption = $is_quick_config = NULL;
$memPresent = $membershipLabel = $fieldOption = $is_quick_config = NULL;
$proceFieldAmount = 0;
if (property_exists($this, '_separateMembershipPayment') && $this->_separateMembershipPayment == 0) {
$is_quick_config = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_priceSetId, 'is_quick_config');
Expand Down
74 changes: 74 additions & 0 deletions tests/phpunit/CiviTest/CiviUnitTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -3647,4 +3647,78 @@ protected function enableTaxAndInvoicing($params = array()) {
return Civi::settings()->set('contribution_invoice_settings', $contributeSetting);
}

/**
* Create price set with contribution test for test setup.
*
* This could be merged with 4.5 function setup in api_v3_ContributionPageTest::setUpContributionPage
* on parent class at some point (fn is not in 4.4).
*
* @param $entity
* @param array $params
*/
public function createPriceSetWithPage($entity = NULL, $params = array()) {
$membershipTypeID = $this->membershipTypeCreate(array('name' => 'Special'));
$contributionPageResult = $this->callAPISuccess('contribution_page', 'create', array(
'title' => "Test Contribution Page",
'financial_type_id' => 1,
'currency' => 'NZD',
'goal_amount' => 50,
'is_pay_later' => 1,
'is_monetary' => TRUE,
'is_email_receipt' => FALSE,
));
$priceSet = $this->callAPISuccess('price_set', 'create', array(
'is_quick_config' => 0,
'extends' => 'CiviMember',
'financial_type_id' => 1,
'title' => 'my Page',
));
$priceSetID = $priceSet['id'];

CRM_Price_BAO_PriceSet::addTo('civicrm_contribution_page', $contributionPageResult['id'], $priceSetID);
$priceField = $this->callAPISuccess('price_field', 'create', array(
'price_set_id' => $priceSetID,
'label' => 'Goat Breed',
'html_type' => 'Radio',
));
$priceFieldValue = $this->callAPISuccess('price_field_value', 'create', array(
'price_set_id' => $priceSetID,
'price_field_id' => $priceField['id'],
'label' => 'Long Haired Goat',
'amount' => 20,
'financial_type_id' => 'Donation',
'membership_type_id' => $membershipTypeID,
'membership_num_terms' => 1,
)
);
$this->_ids['price_field_value'] = array($priceFieldValue['id']);
$priceFieldValue = $this->callAPISuccess('price_field_value', 'create', array(
'price_set_id' => $priceSetID,
'price_field_id' => $priceField['id'],
'label' => 'Shoe-eating Goat',
'amount' => 10,
'financial_type_id' => 'Donation',
'membership_type_id' => $membershipTypeID,
'membership_num_terms' => 2,
)
);
$this->_ids['price_field_value'][] = $priceFieldValue['id'];

$priceFieldValue = $this->callAPISuccess('price_field_value', 'create', array(
'price_set_id' => $priceSetID,
'price_field_id' => $priceField['id'],
'label' => 'Shoe-eating Goat',
'amount' => 10,
'financial_type_id' => 'Donation',
)
);
$this->_ids['price_field_value']['cont'] = $priceFieldValue['id'];

$this->_ids['price_set'] = $priceSetID;
$this->_ids['contribution_page'] = $contributionPageResult['id'];
$this->_ids['price_field'] = array($priceField['id']);

$this->_ids['membership_type'] = $membershipTypeID;
}

}
159 changes: 157 additions & 2 deletions tests/phpunit/api/v3/ContributionPageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,7 @@ public function testSubmitMembershipPriceSetPaymentPaymentProcessorRecurInstantP
* - the first creates a new membership, completed contribution, in progress recurring. Check these
* - create another - end date should be extended
*/
public function testSubmitMembershipComplexPriceSetPaymentPaymentProcessorRecurInstantPayment() {
public function testSubmitMembershipComplexNonPriceSetPaymentPaymentProcessorRecurInstantPayment() {
$this->params['is_recur'] = 1;
$this->params['recur_frequency_unit'] = 'month';
// Add a membership so membership & contribution are not both 1.
Expand Down Expand Up @@ -740,6 +740,140 @@ public function testSubmitMembershipComplexPriceSetPaymentPaymentProcessorRecurI
$this->assertEquals(5, $recurringContribution['contribution_status_id']);
}

/**
* Test submit recurring membership with immediate confirmation (IATS style).
*
* - we process 2 membership transactions against with a recurring contribution against a contribution page with an immediate
* processor (IATS style - denoted by returning trxn_id)
* - the first creates a new membership, completed contribution, in progress recurring. Check these
* - create another - end date should be extended
*/
public function testSubmitMembershipComplexPriceSetPaymentPaymentProcessorRecurInstantPayment() {
$this->params['is_recur'] = 1;
$this->params['recur_frequency_unit'] = 'month';
// Add a membership so membership & contribution are not both 1.
$preExistingMembershipID = $this->contactMembershipCreate(array('contact_id' => $this->contactIds[0]));
$this->createPriceSetWithPage();
$this->addSecondOrganizationMembershipToPriceSet();
$this->setupPaymentProcessor();

$dummyPP = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor);
$dummyPP->setDoDirectPaymentResult(array('payment_status_id' => 1, 'trxn_id' => 'create_first_success'));
$processor = $dummyPP->getPaymentProcessor();

$submitParams = array(
'price_' . $this->_ids['price_field'][0] => $this->_ids['price_field_value']['cont'],
'price_' . $this->_ids['price_field']['org1'] => $this->_ids['price_field_value']['org1'],
'price_' . $this->_ids['price_field']['org2'] => $this->_ids['price_field_value']['org2'],
'id' => (int) $this->_ids['contribution_page'],
'amount' => 10,
'billing_first_name' => 'Billy',
'billing_middle_name' => 'Goat',
'billing_last_name' => 'Gruff',
'email' => '[email protected]',
'selectMembership' => NULL,
'payment_processor_id' => 1,
'credit_card_number' => '4111111111111111',
'credit_card_type' => 'Visa',
'credit_card_exp_date' => array('M' => 9, 'Y' => 2040),
'cvv2' => 123,
'frequency_interval' => 1,
'frequency_unit' => 'month',
);

$this->callAPIAndDocument('contribution_page', 'submit', $submitParams, __FUNCTION__, __FILE__, 'submit contribution page', NULL);
$contribution = $this->callAPISuccess('contribution', 'getsingle', array(
'contribution_page_id' => $this->_ids['contribution_page'],
'contribution_status_id' => 1,
));
$this->assertEquals($processor['payment_instrument_id'], $contribution['payment_instrument_id']);

$this->assertEquals('create_first_success', $contribution['trxn_id']);
$membershipPayments = $this->callAPISuccess('membership_payment', 'get', array(
'sequential' => 1,
'contribution_id' => $contribution['id'],
));
$this->assertEquals(2, $membershipPayments['count']);
$lines = $this->callAPISuccess('line_item', 'get', array('sequential' => 1, 'contribution_id' => $contribution['id']));
$this->assertEquals(3, $lines['count']);
$this->assertEquals('civicrm_membership', $lines['values'][0]['entity_table']);
$this->assertEquals($preExistingMembershipID + 1, $lines['values'][0]['entity_id']);
$this->assertEquals('civicrm_contribution', $lines['values'][1]['entity_table']);
$this->assertEquals($contribution['id'], $lines['values'][1]['entity_id']);
$this->assertEquals('civicrm_membership', $lines['values'][2]['entity_table']);
$this->assertEquals($preExistingMembershipID + 2, $lines['values'][2]['entity_id']);

$this->callAPISuccessGetSingle('MembershipPayment', array('contribution_id' => $contribution['id'], 'membership_id' => $preExistingMembershipID + 1));
$membership = $this->callAPISuccessGetSingle('membership', array('id' => $preExistingMembershipID + 1));

//renew it with processor setting completed - should extend membership
$submitParams['contact_id'] = $contribution['contact_id'];
$dummyPP->setDoDirectPaymentResult(array('payment_status_id' => 1, 'trxn_id' => 'create_second_success'));
$this->callAPISuccess('contribution_page', 'submit', $submitParams);
$renewContribution = $this->callAPISuccess('contribution', 'getsingle', array(
'id' => array('NOT IN' => array($contribution['id'])),
'contribution_page_id' => $this->_ids['contribution_page'],
'contribution_status_id' => 1,
));
$lines = $this->callAPISuccess('line_item', 'get', array('sequential' => 1, 'contribution_id' => $renewContribution['id']));
$this->assertEquals(3, $lines['count']);
$this->assertEquals('civicrm_membership', $lines['values'][0]['entity_table']);
$this->assertEquals($preExistingMembershipID + 1, $lines['values'][0]['entity_id']);
$this->assertEquals('civicrm_contribution', $lines['values'][1]['entity_table']);
$this->assertEquals($renewContribution['id'], $lines['values'][1]['entity_id']);

$renewedMembership = $this->callAPISuccessGetSingle('membership', array('id' => $preExistingMembershipID + 1));
$this->assertEquals(date('Y-m-d', strtotime('+ 1 year', strtotime($membership['end_date']))), $renewedMembership['end_date']);
}

/**
* Extend the price set with a second organisation's membership.
*/
public function addSecondOrganizationMembershipToPriceSet() {
$organization2ID = $this->organizationCreate();
$membershipTypes = $this->callAPISuccess('MembershipType', 'get', array());
$this->_ids['membership_type'] = array_keys($membershipTypes['values']);
$this->_ids['membership_type']['org2'] = $this->membershipTypeCreate(array('contact_id' => $organization2ID, 'name' => 'Org 2'));
$priceField = $this->callAPISuccess('PriceField', 'create', array(
'price_set_id' => $this->_ids['price_set'],
'html_type' => 'Radio',
'name' => 'Org1 Price',
'label' => 'Org1Price',
));
$this->_ids['price_field']['org1'] = $priceField['id'];

$priceFieldValue = $this->callAPISuccess('price_field_value', 'create', array(
'name' => 'org1 amount',
'label' => 'org 1 Amount',
'amount' => 2,
'financial_type_id' => 'Member Dues',
'format.only_id' => TRUE,
'membership_type_id' => reset($this->_ids['membership_type']),
'price_field_id' => $priceField['id'],
));
$this->_ids['price_field_value']['org1'] = $priceFieldValue;

$priceField = $this->callAPISuccess('PriceField', 'create', array(
'price_set_id' => $this->_ids['price_set'],
'html_type' => 'Radio',
'name' => 'Org2 Price',
'label' => 'Org2Price',
));
$this->_ids['price_field']['org2'] = $priceField['id'];

$priceFieldValue = $this->callAPISuccess('price_field_value', 'create', array(
'name' => 'org2 amount',
'label' => 'org 2 Amount',
'amount' => 200,
'financial_type_id' => 'Member Dues',
'format.only_id' => TRUE,
'membership_type_id' => $this->_ids['membership_type']['org2'],
'price_field_id' => $priceField['id'],
));
$this->_ids['price_field_value']['org2'] = $priceFieldValue;

}

/**
* Test submit recurring membership with immediate confirmation (IATS style).
*
Expand Down Expand Up @@ -944,14 +1078,35 @@ public function setUpMembershipBlockPriceSet() {
$priceFieldValue = $this->callAPISuccess('price_field_value', 'create', array(
'name' => 'membership_amount',
'label' => 'Membership Amount',
'amount' => 1,
'amount' => 2,
'financial_type_id' => 'Donation',
'format.only_id' => TRUE,
'membership_type_id' => $membershipTypeID,
'price_field_id' => $priceField['id'],
));
$this->_ids['price_field_value'][] = $priceFieldValue;
}
if (!empty($this->_ids['membership_type']['org2'])) {
$priceField = $this->callAPISuccess('price_field', 'create', array(
'price_set_id' => reset($this->_ids['price_set']),
'name' => 'membership_org2',
'label' => 'Membership Org2',
'html_type' => 'Checkbox',
'sequential' => 1,
));
$this->_ids['price_field']['org2'] = $priceField['id'];

$priceFieldValue = $this->callAPISuccess('price_field_value', 'create', array(
'name' => 'membership_org2',
'label' => 'Membership org 2',
'amount' => 55,
'financial_type_id' => 'Member Dues',
'format.only_id' => TRUE,
'membership_type_id' => $this->_ids['membership_type']['org2'],
'price_field_id' => $priceField['id'],
));
$this->_ids['price_field_value']['org2'] = $priceFieldValue;
}
$priceField = $this->callAPISuccess('price_field', 'create', array(
'price_set_id' => reset($this->_ids['price_set']),
'name' => 'Contribution',
Expand Down
Loading