$params['contribution'] = $contribution;
self::recordFinancialAccounts($params);
+ if (self::isUpdateToRecurringContribution($params)) {
+ CRM_Contribute_BAO_ContributionRecur::updateOnNewPayment(
+ $params['contribution_recur_id'],
+ $contributionStatus[$params['contribution_status_id']]
+ );
+ }
+
// reset the group contact cache for this group
CRM_Contact_BAO_GroupContactCache::remove();
return $result;
}
+ /**
+ * Is this contribution updating an existing recurring contribution.
+ *
+ * We need to upd the status of the linked recurring contribution if we have a new payment against it, or the initial
+ * pending payment is being confirmed (or failing).
+ *
+ * @param array $params
+ *
+ * @return bool
+ */
+ public static function isUpdateToRecurringContribution($params) {
+ if (!empty($params['contribution_recur_id']) && empty($params['id'])) {
+ return TRUE;
+ }
+ if (empty($params['prevContribution']) || empty($params['contribution_status_id'])) {
+ return FALSE;
+ }
+ if (empty($params['contribution_recur_id']) && empty($params['prevContribution']->contribution_recur_id)) {
+ return FALSE;
+ }
+ $contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name');
+ if ($params['prevContribution']->contribution_status_id == array_search('Pending', $contributionStatus)) {
+ return TRUE;
+ }
+ return FALSE;
+ }
+
/**
* Get defaults for new entity.
* @return array
);
}
+ /**
+ * Update recurring contribution based on incoming payment.
+ *
+ * Do not rename or move this function without updating https://issues.civicrm.org/jira/browse/CRM-17655.
+ *
+ * @param int $recurringContributionID
+ * @param string $paymentStatus
+ * Payment status - this correlates to the machine name of the contribution status ID ie
+ * - Completed
+ * - Failed
+ *
+ * @throws \CiviCRM_API3_Exception
+ */
+ public static function updateOnNewPayment($recurringContributionID, $paymentStatus) {
+ if (!in_array($paymentStatus, array('Completed', 'Failed'))) {
+ return;
+ }
+ $params = array(
+ 'id' => $recurringContributionID,
+ 'return' => array(
+ 'contribution_status_id',
+ 'next_sched_contribution_date',
+ 'frequency_unit',
+ 'frequency_interval',
+ 'installments',
+ 'failure_count',
+ ),
+ );
+
+ $existing = civicrm_api3('ContributionRecur', 'getsingle', $params);
+
+ if ($paymentStatus == 'Completed'
+ && CRM_Contribute_PseudoConstant::contributionStatus($existing['contribution_status_id'], 'name') == 'Pending') {
+ $params['contribution_status_id'] = 'In Progress';
+ }
+ if ($paymentStatus == 'Failed') {
+ $params['failure_count'] = $existing['failure_count'];
+ }
+ $params['modified_date'] = date('Y-m-d H:i:s');
+
+ if (!empty($existing['installments']) && self::isComplete($recurringContributionID, $existing['installments'])) {
+ $params['contribution_status_id'] = 'Completed';
+ }
+ else {
+ // Only update next sched date if it's empty or 'just now' because payment processors may be managing
+ // the scheduled date themselves as core did not previously provide any help.
+ if (empty($params['next_sched_contribution_date']) || strtotime($params['next_sched_contribution_date']) ==
+ strtotime(date('Y-m-d'))) {
+ $params['next_sched_contribution_date'] = date('Y-m-d', strtotime('+' . $existing['frequency_interval'] . ' ' . $existing['frequency_unit']));
+ }
+ }
+ civicrm_api3('ContributionRecur', 'create', $params);
+ }
+
+ /**
+ * Is this recurring contribution now complete.
+ *
+ * Have all the payments expected been received now.
+ *
+ * @param int $recurringContributionID
+ * @param int $installments
+ *
+ * @return bool
+ */
+ protected static function isComplete($recurringContributionID, $installments) {
+ $paidInstallments = CRM_Core_DAO::singleValueQuery(
+ 'SELECT count(*) FROM civicrm_contribution WHERE id = %1',
+ array(1 => array($recurringContributionID, 'Integer'))
+ );
+ if ($paidInstallments >= $installments) {
+ return TRUE;
+ }
+ return FALSE;
+ }
+
}
$renewedMembership = $this->callAPISuccessGetSingle('membership', array('id' => $membershipPayment['membership_id']));
$this->assertEquals(date('Y-m-d', strtotime('+ 1 year', strtotime($membership['end_date']))), $renewedMembership['end_date']);
$recurringContribution = $this->callAPISuccess('contribution_recur', 'getsingle', array('id' => $contribution['contribution_recur_id']));
- $this->assertEquals(2, $recurringContribution['contribution_status_id']);
+ $this->assertEquals(5, $recurringContribution['contribution_status_id']);
}
/**
$mut->stop();
}
+ /**
+ * Test completing first transaction in a recurring series.
+ *
+ * The status should be set to 'in progress' and the next scheduled payment date calculated.
+ */
+ public function testCompleteTransactionSetStatusToInProgress() {
+ $paymentProcessorID = $this->paymentProcessorCreate();
+ $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', array(
+ 'contact_id' => $this->_individualId,
+ 'installments' => '12',
+ 'frequency_interval' => '1',
+ 'amount' => '500',
+ 'contribution_status_id' => 'Pending',
+ 'start_date' => '2012-01-01 00:00:00',
+ 'currency' => 'USD',
+ 'frequency_unit' => 'month',
+ 'payment_processor_id' => $paymentProcessorID,
+ ));
+ $contribution = $this->callAPISuccess('contribution', 'create', array_merge(
+ $this->_params,
+ array(
+ 'contribution_recur_id' => $contributionRecur['id'],
+ 'contribution_status_id' => 'Pending',
+ ))
+ );
+ $this->callAPISuccess('Contribution', 'completetransaction', array('id' => $contribution));
+ $contributionRecur = $this->callAPISuccessGetSingle('ContributionRecur', array(
+ 'id' => $contributionRecur['id'],
+ 'return' => array('next_sched_contribution_date', 'contribution_status_id'),
+ ));
+ $this->assertEquals(5, $contributionRecur['contribution_status_id']);
+ $this->assertEquals(date('Y-m-d 00:00:00', strtotime('+1 month')), $contributionRecur['next_sched_contribution_date']);
+ }
+
/**
* Test completing a transaction with an event via the API.
*