X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=CRM%2FContribute%2FBAO%2FContribution.php;h=d6cdb7f1c78d00aface3b2a6cd516c3eab4d2be9;hb=5e68fa06f6069a95e16b449805ace2be08ab27c0;hp=7f825adfa016215bbd39444fc7f1a56ba56ad303;hpb=ec60f0f2e4ce9815ee5f0bd8906717feb80f0744;p=civicrm-core.git diff --git a/CRM/Contribute/BAO/Contribution.php b/CRM/Contribute/BAO/Contribution.php index 7f825adfa0..d6cdb7f1c7 100644 --- a/CRM/Contribute/BAO/Contribution.php +++ b/CRM/Contribute/BAO/Contribution.php @@ -10,9 +10,10 @@ */ use Civi\Api4\Activity; -use Civi\Api4\ContributionPage; +use Civi\Api4\Contribution; use Civi\Api4\ContributionRecur; use Civi\Api4\PaymentProcessor; +use Civi\Api4\PledgePayment; /** * @@ -501,6 +502,10 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution { CRM_Contribute_BAO_ContributionSoft::processSoftContribution($params, $contribution); + if (!empty($params['id']) && !empty($params['contribution_status_id']) + ) { + self::disconnectPledgePaymentsIfCancelled((int) $params['id'], CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $params['contribution_status_id'])); + } $transaction->commit(); if (empty($contribution->contact_id)) { @@ -517,18 +522,24 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution { ])->execute()->first(); $campaignParams = isset($params['campaign_id']) ? ['campaign_id' => ($params['campaign_id'] ?? NULL)] : []; - Activity::save(FALSE)->addRecord(array_merge([ + $activityParams = array_merge([ 'activity_type_id:name' => 'Contribution', 'source_record_id' => $contribution->id, - 'source_contact_id' => CRM_Core_Session::getLoggedInContactID() ?: $contribution->contact_id, - 'target_contact_id' => CRM_Core_Session::getLoggedInContactID() ? [$contribution->contact_id] : [], 'activity_date_time' => $contribution->receive_date, 'is_test' => (bool) $contribution->is_test, 'status_id:name' => $isCompleted ? 'Completed' : 'Scheduled', 'skipRecentView' => TRUE, 'subject' => CRM_Activity_BAO_Activity::getActivitySubject($contribution), 'id' => $existingActivity['id'] ?? NULL, - ], $campaignParams))->execute(); + ], $campaignParams); + if (!$activityParams['id']) { + // Don't set target contacts on update as these will have been + // correctly created and we risk overwriting them with + // 'best guess' params. + $activityParams['source_contact_id'] = (int) ($params['source_contact_id'] ?? (CRM_Core_Session::getLoggedInContactID() ?: $contribution->contact_id)); + $activityParams['target_contact_id'] = ($activityParams['source_contact_id'] === (int) $contribution->contact_id) ? [] : [$contribution->contact_id]; + } + Activity::save(FALSE)->addRecord($activityParams)->execute(); } // do not add to recent items for import, CRM-4399 @@ -926,34 +937,6 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution { ])['values']; } - /** - * Cancel contribution. - * - * This function should only be called from transitioncomponents - it is an interim step in refactoring. - * - * @param $memberships - * @param $contributionId - * @param $membershipStatuses - * @param $participant - * @param $oldStatus - * @param $pledgePayment - * @param $pledgeID - * @param $pledgePaymentIDs - * @param $contributionStatusId - */ - protected static function cancel($memberships, $contributionId, $membershipStatuses, $participant, $oldStatus, $pledgePayment, $pledgeID, $pledgePaymentIDs, $contributionStatusId) { - // @fixme https://lab.civicrm.org/dev/core/issues/927 Cancelling membership etc is not desirable for all use-cases and we should be able to disable it - $participantStatuses = CRM_Event_PseudoConstant::participantStatus(); - if ($participant) { - $updatedStatusId = array_search('Cancelled', $participantStatuses); - CRM_Event_BAO_Participant::updateParticipantStatus($participant->id, $oldStatus, $updatedStatusId, TRUE); - } - - if ($pledgePayment) { - CRM_Pledge_BAO_PledgePayment::updatePledgePaymentStatus($pledgeID, $pledgePaymentIDs, $contributionStatusId); - } - } - /** * Do any accounting updates required as a result of a contribution status change. * @@ -1275,14 +1258,13 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution { * Should an email receipt be sent for this contribution on completion. * * @param array $input - * @param int $contributionPageID + * @param int $contributionID * @param int $recurringContributionID * * @return bool * @throws \API_Exception - * @throws \Civi\API\Exception\UnauthorizedException */ - protected static function isEmailReceipt(array $input, $contributionPageID, $recurringContributionID): bool { + protected static function isEmailReceipt(array $input, int $contributionID, $recurringContributionID): bool { if (isset($input['is_email_receipt'])) { return (bool) $input['is_email_receipt']; } @@ -1295,8 +1277,14 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution { // https://lab.civicrm.org/dev/core/issues/1245 return (bool) ContributionRecur::get(FALSE)->addWhere('id', '=', $recurringContributionID)->addSelect('is_email_receipt')->execute()->first()['is_email_receipt']; } - if ($contributionPageID) { - return (bool) ContributionPage::get(FALSE)->addWhere('id', '=', $contributionPageID)->addSelect('is_email_receipt')->execute()->first()['is_email_receipt']; + $contributionPage = Contribution::get(FALSE) + ->addSelect('contribution_page.is_email_receipt') + ->addWhere('contribution_page_id', 'IS NOT NULL') + ->addWhere('id', '=', $contributionID) + ->execute()->first(); + + if (!empty($contributionPage)) { + return (bool) $contributionPage['contribution_page.is_email_receipt']; } // This would be the case for backoffice (where is_email_receipt is not passed in) or events, where Event::sendMail will filter // again anyway. @@ -1304,54 +1292,40 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution { } /** - * Process failed contribution. + * Disconnect pledge payments from cancelled or failed contributions. * - * @param $memberships - * @param $contributionId - * @param array $membershipStatuses - * @param $participant - * @param $pledgePayment - * @param $pledgeID - * @param array $pledgePaymentIDs - * @param $contributionStatusId + * If the contribution has been cancelled or has failed check to + * see if it is linked to a pledge and unlink it. * - * @throws \CRM_Core_Exception + * @param int $pledgePaymentID + * @param string $contributionStatus + * + * @throws \API_Exception + * @throws \Civi\API\Exception\UnauthorizedException */ - protected static function processFail($memberships, $contributionId, array $membershipStatuses, array $participant, $pledgePayment, $pledgeID, array $pledgePaymentIDs, $contributionStatusId): void { - if (is_array($memberships)) { - foreach ($memberships as $membership) { - $update = TRUE; - //Update Membership status if there is no other completed contribution associated with the membership. - $relatedContributions = CRM_Member_BAO_Membership::getMembershipContributionId($membership->id, TRUE); - foreach ($relatedContributions as $contriId) { - if ($contriId == $contributionId) { - continue; - } - $statusId = CRM_Core_DAO::getFieldValue('CRM_Contribute_BAO_Contribution', $contriId, 'contribution_status_id'); - if (CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $statusId) === 'Completed') { - $update = FALSE; - } - } - if ($membership && $update) { - $membership->status_id = array_search('Expired', $membershipStatuses); - $membership->is_override = TRUE; - $membership->status_override_end_date = 'null'; - $membership->save(); - } - } - } - if ($participant) { - $oldStatus = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Participant', - $participant->id, - 'status_id' - ); - $participantStatuses = CRM_Event_PseudoConstant::participantStatus(); - $updatedStatusId = array_search('Cancelled', $participantStatuses); - CRM_Event_BAO_Participant::updateParticipantStatus($participant->id, $oldStatus, $updatedStatusId, TRUE); + protected static function disconnectPledgePaymentsIfCancelled(int $pledgePaymentID, $contributionStatus): void { + if (!in_array($contributionStatus, ['Failed', 'Cancelled'], TRUE)) { + return; } - - if ($pledgePayment) { - CRM_Pledge_BAO_PledgePayment::updatePledgePaymentStatus($pledgeID, $pledgePaymentIDs, $contributionStatusId); + // Check first since just doing an update could be locking under load. + $pledgePayment = PledgePayment::get(FALSE) + ->addWhere('contribution_id', '=', $pledgePaymentID) + ->setSelect(['id', 'pledge_id', 'scheduled_date', 'scheduled_amount']) + ->execute() + ->first(); + if (!empty($pledgePayment)) { + PledgePayment::update(FALSE)->setValues([ + 'contribution_id' => NULL, + 'actual_amount' => NULL, + 'status_id:name' => 'Pending', + // We need to set these fields for now because the PledgePayment::create + // function doesn't handled updates well at the moment. Test cover + // in testCancelOrderWithPledge. + 'scheduled_date' => $pledgePayment['scheduled_date'], + 'installment_amount' => $pledgePayment['scheduled_amount'], + 'installments' => 1, + 'pledge_id' => $pledgePayment['pledge_id'], + ])->addWhere('id', '=', $pledgePayment['id'])->execute(); } } @@ -2063,6 +2037,8 @@ LEFT JOIN civicrm_contribution contribution ON ( componentPayment.contribution_ * */ public static function transitionComponents($params) { + // @todo fix the one place that calls this function to use Payment.create + // remove this. // get minimum required values. $contactId = $params['contact_id'] ?? NULL; $componentId = $params['component_id'] ?? NULL; @@ -2079,8 +2055,6 @@ LEFT JOIN civicrm_contribution contribution ON ( componentPayment.contribution_ if (!$contributionId || !in_array($contributionStatusId, [ array_search('Completed', $contributionStatuses), - array_search('Cancelled', $contributionStatuses), - array_search('Failed', $contributionStatuses), ]) ) { return; @@ -2154,14 +2128,7 @@ LEFT JOIN civicrm_contribution contribution ON ( componentPayment.contribution_ 'status_id' ); } - if ($contributionStatusId == array_search('Cancelled', $contributionStatuses)) { - // Call interim cancel function - with a goal to cleaning up the signature on it and switching to a tested api Contribution.cancel function. - self::cancel($memberships, $contributionId, $membershipStatuses, $participant, $oldStatus, $pledgePayment, $pledgeID, $pledgePaymentIDs, $contributionStatusId); - } - elseif ($contributionStatusId == array_search('Failed', $contributionStatuses)) { - self::processFail($memberships, $contributionId, $membershipStatuses, $participant, $pledgePayment, $pledgeID, $pledgePaymentIDs, $contributionStatusId); - } - elseif ($contributionStatusId == array_search('Completed', $contributionStatuses)) { + if ($contributionStatusId == array_search('Completed', $contributionStatuses)) { // only pending contribution related object processed. if ($previousContriStatusId && @@ -2494,72 +2461,68 @@ LEFT JOIN civicrm_contribution contribution ON ( componentPayment.contribution_ if (!empty($contribution->id)) { return FALSE; } - if (empty($contribution->id)) { - // Unclear why this would only be set for repeats. - if (!empty($input['amount'])) { - $contribution->total_amount = $contributionParams['total_amount'] = $input['amount']; - } - $recurringContribution = civicrm_api3('ContributionRecur', 'getsingle', [ - 'id' => $contributionParams['contribution_recur_id'], - ]); - if (!empty($recurringContribution['financial_type_id'])) { - // CRM-17718 the campaign id on the contribution recur record should get precedence. - $contributionParams['financial_type_id'] = $recurringContribution['financial_type_id']; - } - $templateContribution = CRM_Contribute_BAO_ContributionRecur::getTemplateContribution( - $contributionParams['contribution_recur_id'], - array_intersect_key($contributionParams, [ - 'total_amount' => TRUE, - 'financial_type_id' => TRUE, - ]) - ); - $input['line_item'] = $contributionParams['line_item'] = $templateContribution['line_item']; - $contributionParams['status_id'] = 'Pending'; + // Unclear why this would only be set for repeats. + if (!empty($input['amount'])) { + $contribution->total_amount = $contributionParams['total_amount'] = $input['amount']; + } - if (isset($contributionParams['financial_type_id']) && count($input['line_item']) === 1) { - // We permit the financial type to be overridden for single line items. - // More comments on this are in getTemplateTransaction. - $contribution->financial_type_id = $contributionParams['financial_type_id']; - } - else { - $contributionParams['financial_type_id'] = $templateContribution['financial_type_id']; - } - foreach (['contact_id', 'currency', 'source', 'amount_level', 'address_id'] as $fieldName) { - if (isset($templateContribution[$fieldName])) { - $contributionParams[$fieldName] = $templateContribution[$fieldName]; - } - } - if (!empty($recurringContribution['campaign_id'])) { - // CRM-17718 the campaign id on the contribution recur record should get precedence. - $contributionParams['campaign_id'] = $recurringContribution['campaign_id']; - } - if (!isset($contributionParams['campaign_id']) && isset($templateContribution['campaign_id'])) { - // Fall back on value from the previous contribution if not passed in as input - // or loadable from the recurring contribution. - $contributionParams['campaign_id'] = $templateContribution['campaign_id']; - } - $contributionParams['source'] = $contributionParams['source'] ?? ts('Recurring contribution'); + $recurringContribution = civicrm_api3('ContributionRecur', 'getsingle', [ + 'id' => $contributionParams['contribution_recur_id'], + ]); + if (!empty($recurringContribution['financial_type_id'])) { + // CRM-17718 the campaign id on the contribution recur record should get precedence. + $contributionParams['financial_type_id'] = $recurringContribution['financial_type_id']; + } + $templateContribution = CRM_Contribute_BAO_ContributionRecur::getTemplateContribution( + $contributionParams['contribution_recur_id'], + array_intersect_key($contributionParams, [ + 'total_amount' => TRUE, + 'financial_type_id' => TRUE, + ]) + ); + $input['line_item'] = $contributionParams['line_item'] = $templateContribution['line_item']; + $contributionParams['status_id'] = 'Pending'; - //CRM-18805 -- Contribution page not recorded on recurring transactions, Recurring contribution payments - //do not create CC or BCC emails or profile notifications. - //The if is just to be safe. Not sure if we can ever arrive with this unset - // but per CRM-19478 it seems it can be 'null' - if (isset($contribution->contribution_page_id) && is_numeric($contribution->contribution_page_id)) { - $contributionParams['contribution_page_id'] = $contribution->contribution_page_id; - } - if (!empty($contribution->tax_amount)) { - $contributionParams['tax_amount'] = $contribution->tax_amount; + if (isset($contributionParams['financial_type_id']) && count($input['line_item']) === 1) { + // We permit the financial type to be overridden for single line items. + // More comments on this are in getTemplateTransaction. + $contribution->financial_type_id = $contributionParams['financial_type_id']; + } + else { + $contributionParams['financial_type_id'] = $templateContribution['financial_type_id']; + } + foreach (['contact_id', 'currency', 'source', 'amount_level', 'address_id', 'on_behalf', 'source_contact_id', 'tax_amount', 'contribution_page_id'] as $fieldName) { + if (isset($templateContribution[$fieldName])) { + $contributionParams[$fieldName] = $templateContribution[$fieldName]; } + } + if (!empty($recurringContribution['campaign_id'])) { + // CRM-17718 the campaign id on the contribution recur record should get precedence. + $contributionParams['campaign_id'] = $recurringContribution['campaign_id']; + } + if (!isset($contributionParams['campaign_id']) && isset($templateContribution['campaign_id'])) { + // Fall back on value from the previous contribution if not passed in as input + // or loadable from the recurring contribution. + $contributionParams['campaign_id'] = $templateContribution['campaign_id']; + } + $contributionParams['source'] = $contributionParams['source'] ?? ts('Recurring contribution'); - $createContribution = civicrm_api3('Contribution', 'create', $contributionParams); - $contribution->id = $createContribution['id']; - $contribution->copyCustomFields($templateContribution['id'], $contribution->id); - self::handleMembershipIDOverride($contribution->id, $input); - // Add new soft credit against current $contribution. - CRM_Contribute_BAO_ContributionRecur::addrecurSoftCredit($contributionParams['contribution_recur_id'], $createContribution['id']); - return $createContribution; + //CRM-18805 -- Contribution page not recorded on recurring transactions, Recurring contribution payments + //do not create CC or BCC emails or profile notifications. + //The if is just to be safe. Not sure if we can ever arrive with this unset + // but per CRM-19478 it seems it can be 'null' + if (isset($contribution->contribution_page_id) && is_numeric($contribution->contribution_page_id)) { + $contributionParams['contribution_page_id'] = $contribution->contribution_page_id; } + + $createContribution = civicrm_api3('Contribution', 'create', $contributionParams); + $contribution->id = $createContribution['id']; + $contribution->copyCustomFields($templateContribution['id'], $contribution->id); + self::handleMembershipIDOverride($contribution->id, $input); + // Add new soft credit against current $contribution. + CRM_Contribute_BAO_ContributionRecur::addrecurSoftCredit($contributionParams['contribution_recur_id'], $createContribution['id']); + return $createContribution; } /** @@ -2839,7 +2802,7 @@ INNER JOIN civicrm_activity ON civicrm_activity_contact.activity_id = civicrm_ac * @throws Exception */ public function composeMessageArray(&$input, &$ids, &$values, $returnMessageText = TRUE) { - $this->loadRelatedObjects($input, $ids); + $this->loadRelatedObjects($input, $ids, TRUE); if (empty($this->_component)) { $this->_component = $input['component'] ?? NULL; @@ -4269,7 +4232,6 @@ INNER JOIN civicrm_activity ON civicrm_activity_contact.activity_id = civicrm_ac // @todo see if we even need this - it's used further down to create an activity // but the BAO layer should create that - we just need to add a test to cover it & can // maybe remove $ids altogether. - $contributionContactID = $ids['related_contact']; $participantID = $ids['participant']; $recurringContributionID = $ids['contributionRecur']; @@ -4317,6 +4279,7 @@ INNER JOIN civicrm_activity ON civicrm_activity_contact.activity_id = civicrm_ac $contributionResult = self::repeatTransaction($contribution, $input, $contributionParams); $contributionID = (int) $contribution->id; + unset($contribution); if ($input['component'] == 'contribute') { if ($contributionParams['contribution_status_id'] === $completedContributionStatusID) { @@ -4341,36 +4304,21 @@ INNER JOIN civicrm_activity ON civicrm_activity_contact.activity_id = civicrm_ac $contributionResult = civicrm_api3('Contribution', 'create', $contributionParams); } - $contribution->contribution_status_id = $contributionParams['contribution_status_id']; - - CRM_Core_Error::debug_log_message('Contribution record updated successfully'); $transaction->commit(); + \Civi::log()->info("Contribution {$contributionParams['id']} updated successfully"); // @todo - check if Contribution::create does this, test, remove. CRM_Contribute_BAO_ContributionRecur::updateRecurLinkedPledge($contributionID, $recurringContributionID, $contributionParams['contribution_status_id'], $input['amount']); - // create an activity record - // @todo - check if Contribution::create does this, test, remove. - if ($input['component'] == 'contribute') { - //CRM-4027 - $targetContactID = NULL; - if ($contributionContactID) { - $targetContactID = $contribution->contact_id; - $contribution->contact_id = $contributionContactID; - } - CRM_Activity_BAO_Activity::addActivity($contribution, 'Contribution', $targetContactID); - } - - if (self::isEmailReceipt($input, $contribution->contribution_page_id, $recurringContributionID)) { + if (self::isEmailReceipt($input, $contributionID, $recurringContributionID)) { civicrm_api3('Contribution', 'sendconfirmation', [ 'id' => $contributionID, 'payment_processor_id' => $paymentProcessorId, ]); - CRM_Core_Error::debug_log_message("Receipt sent"); + \Civi::log()->info("Contribution {$contributionParams['id']} Receipt sent"); } - CRM_Core_Error::debug_log_message("Success: Database updated"); return $contributionResult; } @@ -4403,7 +4351,6 @@ INNER JOIN civicrm_activity ON civicrm_activity_contact.activity_id = civicrm_ac if (!$contribution->find(TRUE)) { throw new CRM_Core_Exception('Contribution does not exist'); } - $contribution->loadRelatedObjects($input, $ids, TRUE); // set receipt from e-mail and name in value if (!$returnMessageText) { list($values['receipt_from_name'], $values['receipt_from_email']) = self::generateFromEmailAndName($input, $contribution);