From 45a6ec43d0de8eacbbe5379a523decd1e94601cd Mon Sep 17 00:00:00 2001 From: eileen Date: Fri, 16 Apr 2021 12:16:16 +1200 Subject: [PATCH] Use order api when creating a recurring membership from the Membership backoffice form This removes the need for some 'magic' code from the membership BAO that was really only there to support the fact that this code was doing some particularly convoluted manoevering in order to share code with the front end form (since unshared). Not this adds a feature Matt requested - the created membership id is returned. This is only done for memberships at the moment but could be other entities too as test cover is added (the membership tests fail without the change in this PR so it has cover in the context it is added --- CRM/Member/BAO/Membership.php | 13 +--- CRM/Member/Form.php | 12 +++ CRM/Member/Form/Membership.php | 76 +++++++++++++++---- api/v3/Order.php | 8 +- .../CRM/Member/Form/MembershipTest.php | 2 +- 5 files changed, 82 insertions(+), 29 deletions(-) diff --git a/CRM/Member/BAO/Membership.php b/CRM/Member/BAO/Membership.php index a3f77a558e..419e169c6d 100644 --- a/CRM/Member/BAO/Membership.php +++ b/CRM/Member/BAO/Membership.php @@ -344,22 +344,11 @@ class CRM_Member_BAO_Membership extends CRM_Member_DAO_Membership { // Record contribution for this membership and create a MembershipPayment // @todo deprecate this. - if (!empty($params['contribution_status_id']) && empty($params['relate_contribution_id'])) { + if (!empty($params['contribution_status_id'])) { $memInfo = array_merge($params, ['membership_id' => $membership->id]); $params['contribution'] = self::recordMembershipContribution($memInfo); } - // Add/update MembershipPayment record for this membership if it is a related contribution - // @todo remove this - called from one remaining place in CRM_Member_Form_Membership - if (!empty($params['relate_contribution_id'])) { - $membershipPaymentParams = [ - 'membership_id' => $membership->id, - 'membership_type_id' => $membership->membership_type_id, - 'contribution_id' => $params['relate_contribution_id'], - ]; - civicrm_api3('MembershipPayment', 'create', $membershipPaymentParams); - } - // If the membership has no associated contribution then we ensure // the line items are 'correct' here. This is a lazy legacy // hack whereby they are deleted and recreated diff --git a/CRM/Member/Form.php b/CRM/Member/Form.php index 452bd38114..b5a5d5403f 100644 --- a/CRM/Member/Form.php +++ b/CRM/Member/Form.php @@ -352,6 +352,18 @@ class CRM_Member_Form extends CRM_Contribute_Form_AbstractEditPayment { return (int) ($this->getSubmittedValue('soft_credit_contact_id') ?: $this->getSubmittedValue('contact_id')); } + /** + * Get the contact id for the contribution. + * + * @return int + */ + protected function getMembershipContactID(): int { + // It's not clear that $this->_contactID *could* be set outside + // tests when contact_id is not submitted - so this fallback + // is precautionary in order to be similar to past behaviour. + return (int) ($this->getSubmittedValue('contact_id') ?: $this->_contactID); + } + /** * Set variables in a way that can be accessed from different places. * diff --git a/CRM/Member/Form/Membership.php b/CRM/Member/Form/Membership.php index 7d6667a9ee..1b897280df 100644 --- a/CRM/Member/Form/Membership.php +++ b/CRM/Member/Form/Membership.php @@ -1144,11 +1144,10 @@ DESC limit 1"); $result = NULL; if ($this->isCreateRecurringContribution()) { $this->_params = $formValues; - - $contribution = civicrm_api3('Contribution', 'create', + $contribution = civicrm_api3('Order', 'create', [ 'contact_id' => $this->_contributorContactID, - 'line_item' => [$this->order->getPriceSetID() => $this->order->getLineItems()], + 'line_items' => $this->getLineItemForOrderApi(), 'is_test' => $this->isTest(), 'campaign_id' => $this->getSubmittedValue('campaign_id'), 'source' => CRM_Utils_Array::value('source', $paymentParams, CRM_Utils_Array::value('description', $paymentParams)), @@ -1159,12 +1158,13 @@ DESC limit 1"); 'total_amount' => $this->order->getTotalAmount(), 'invoice_id' => $this->getInvoiceID(), 'currency' => $this->getCurrency(), - 'contribution_status_id' => 'Pending', 'receipt_date' => $this->getSubmittedValue('send_receipt') ? date('YmdHis') : NULL, 'contribution_recur_id' => $this->getContributionRecurID(), 'skipCleanMoney' => TRUE, ] ); + $this->ids['Contribution'] = $contribution['id']; + $this->setMembershipIDs($contribution['values'][$contribution['id']]['membership_id']); //create new soft-credit record, CRM-13981 if ($softParams) { @@ -1190,6 +1190,16 @@ DESC limit 1"); $result = $payment->doPayment($paymentParams); $formValues = array_merge($formValues, $result); $paymentStatus = CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $formValues['payment_status_id']); + if (!empty($params['contribution_id']) && $paymentStatus === 'Completed') { + civicrm_api3('Payment', 'create', [ + 'fee_amount' => $result['fee_amount'] ?? 0, + 'total_amount' => $this->order->getTotalAmount(), + 'payment_instrument_id' => $this->getPaymentInstrumentID(), + 'trxn_id' => $result['trxn_id'], + 'contribution_id' => $params['contribution_id'], + 'is_send_contribution_notification' => FALSE, + ]); + } } catch (\Civi\Payment\Exception\PaymentProcessorException $e) { if (!empty($paymentParams['contributionID'])) { @@ -1245,12 +1255,6 @@ DESC limit 1"); //create membership record. $count = 0; foreach ($this->_memTypeSelected as $memType) { - if ($count && - ($relateContribution = CRM_Member_BAO_Membership::getMembershipContributionId($this->getMembershipID())) - ) { - $membershipTypeValues[$memType]['relate_contribution_id'] = $relateContribution; - } - $membershipParams = array_merge($membershipTypeValues[$memType], $params); //CRM-15366 if (!empty($softParams) && !$this->isCreateRecurringContribution()) { @@ -1273,10 +1277,14 @@ DESC limit 1"); } $membershipParams['payment_instrument_id'] = $this->getPaymentInstrumentID(); // @todo stop passing $ids (membership and userId only are set above) - $this->setMembership((array) CRM_Member_BAO_Membership::create($membershipParams, $ids)); + if (!$this->isCreateRecurringContribution()) { + // For recurring we already created it 'the right way' (order api). + // In time we will do that for all paths through this code but for now + // we have not migrated the other paths. + $this->setMembership((array) CRM_Member_BAO_Membership::create($membershipParams, $ids)); + } $params['contribution'] = $membershipParams['contribution'] ?? NULL; unset($params['lineItems']); - $count++; } } @@ -1348,7 +1356,7 @@ DESC limit 1"); $this->assign('lineItem', !empty($lineItem) && !$isQuickConfig ? $lineItem : FALSE); $receiptSend = FALSE; - $contributionId = CRM_Member_BAO_Membership::getMembershipContributionId($this->getMembershipID()); + $contributionId = $this->ids['Contribution'] ?? CRM_Member_BAO_Membership::getMembershipContributionId($this->getMembershipID()); $membershipIds = $this->_membershipIDs; if ($contributionId && !empty($membershipIds)) { $contributionDetails = CRM_Contribute_BAO_Contribution::getContributionDetails( @@ -1783,12 +1791,15 @@ DESC limit 1"); * against breakage if code is moved around). * * @return array + * @throws \API_Exception + * @throws \CiviCRM_API3_Exception */ protected function getFormMembershipParams(): array { $submittedValues = $this->controller->exportValues($this->_name); return [ 'status_id' => $this->getSubmittedValue('status_id'), - 'source' => $this->getSubmittedValue('source'), + 'source' => $this->getSubmittedValue('source') ?? $this->getContributionSource(), + 'contact_id' => $this->getMembershipContactID(), 'is_override' => $this->getSubmittedValue('is_override'), 'status_override_end_date' => $this->getSubmittedValue('status_override_end_date'), 'campaign_id' => $this->getSubmittedValue('campaign_id'), @@ -1800,6 +1811,7 @@ DESC limit 1"); // when is_override false ignore is_admin statuses during membership // status calculation. similarly we did fix for import in CRM-3570. 'exclude_is_admin' => !$this->getSubmittedValue('is_override'), + 'contribution_recur_id' => $this->getContributionRecurID(), ]; } @@ -1921,7 +1933,7 @@ DESC limit 1"); */ protected function getMembership(): array { if (empty($this->membership)) { - $this->membership = civicrm_api3('Membership', 'get', ['id' => $this->getMembershipID()]); + $this->membership = civicrm_api3('Membership', 'get', ['id' => $this->getMembershipID()])['values'][$this->getMembershipID()]; } return $this->membership; } @@ -1938,4 +1950,38 @@ DESC limit 1"); $this->membership = $membership; } + /** + * Get line items formatted for the Order api. + * + * @return array + * + * @throws \CiviCRM_API3_Exception + */ + protected function getLineItemForOrderApi(): array { + $lineItems = []; + foreach ($this->order->getLineItems() as $line) { + $params = []; + if (!empty($line['membership_type_id'])) { + $params = $this->getMembershipParamsForType((int) $line['membership_type_id']); + } + $lineItems[] = [ + 'line_item' => [$line['price_field_value_id'] => $line], + 'params' => $params, + ]; + } + return $lineItems; + } + + /** + * Get the parameters for the given membership type. + * + * @param int $membershipTypeID + * + * @return mixed + * @throws \CiviCRM_API3_Exception + */ + protected function getMembershipParamsForType(int $membershipTypeID) { + return array_merge($this->getFormMembershipParams(), $this->getMembershipParameters()[$membershipTypeID]); + } + } diff --git a/api/v3/Order.php b/api/v3/Order.php index 8e9c5b89dc..441de374e6 100644 --- a/api/v3/Order.php +++ b/api/v3/Order.php @@ -86,7 +86,12 @@ function civicrm_api3_order_create($params) { $entityParams = $lineItems['params'] ?? []; if (!empty($entityParams) && !empty($lineItems['line_item'])) { $item = reset($lineItems['line_item']); - $entity = str_replace('civicrm_', '', $item['entity_table']); + if (!empty($item['membership_type_id'])) { + $entity = 'membership'; + } + else { + $entity = str_replace('civicrm_', '', $item['entity_table']); + } } if ($entityParams) { @@ -158,6 +163,7 @@ function civicrm_api3_order_create($params) { $paymentParams += $entityParams; } elseif ($entity == 'membership') { + $contribution['values'][$contribution['id']]['membership_id'][] = $entityId; $paymentParams['isSkipLineItem'] = TRUE; } civicrm_api3($entity . '_payment', 'create', $paymentParams); diff --git a/tests/phpunit/CRM/Member/Form/MembershipTest.php b/tests/phpunit/CRM/Member/Form/MembershipTest.php index ac1b98dee6..45fdd0f7eb 100644 --- a/tests/phpunit/CRM/Member/Form/MembershipTest.php +++ b/tests/phpunit/CRM/Member/Form/MembershipTest.php @@ -705,7 +705,7 @@ class CRM_Member_Form_MembershipTest extends CiviUnitTestCase { CRM_Price_BAO_PriceSet::buildPriceSet($form); $params = [ - 'cid' => $this->_individualId, + 'contact_id' => $this->_individualId, 'join_date' => date('Y-m-d'), 'start_date' => '', 'end_date' => '', -- 2.25.1