*/
use Civi\Api4\Activity;
-use Civi\Api4\ContributionPage;
+use Civi\Api4\Contribution;
use Civi\Api4\ContributionRecur;
use Civi\Api4\PaymentProcessor;
+use Civi\Api4\PledgePayment;
/**
*
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)) {
])->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
])['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.
*
* 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'];
}
// 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.
}
/**
- * 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();
}
}
*
*/
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;
if (!$contributionId ||
!in_array($contributionStatusId, [
array_search('Completed', $contributionStatuses),
- array_search('Cancelled', $contributionStatuses),
- array_search('Failed', $contributionStatuses),
])
) {
return;
'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 &&
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;
}
/**
* @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;
// @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'];
$contributionResult = self::repeatTransaction($contribution, $input, $contributionParams);
$contributionID = (int) $contribution->id;
+ unset($contribution);
if ($input['component'] == 'contribute') {
if ($contributionParams['contribution_status_id'] === $completedContributionStatusID) {
$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;
}
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);