From: eileenmcnaughton Date: Thu, 3 Dec 2015 09:32:03 +0000 (+0000) Subject: CRM-17655 Adding a payment to a recurring contribution sequence should update the... X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=912594079224e305e4723e6538eaed1e9e081448;p=civicrm-core.git CRM-17655 Adding a payment to a recurring contribution sequence should update the contribution_recur table --- diff --git a/CRM/Contribute/BAO/Contribution.php b/CRM/Contribute/BAO/Contribution.php index 79bebb63b6..12c5ee398c 100644 --- a/CRM/Contribute/BAO/Contribution.php +++ b/CRM/Contribute/BAO/Contribution.php @@ -200,6 +200,13 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution { $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(); @@ -213,6 +220,33 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution { 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 diff --git a/CRM/Contribute/BAO/ContributionRecur.php b/CRM/Contribute/BAO/ContributionRecur.php index ac4d40faf4..ca7c4009a4 100644 --- a/CRM/Contribute/BAO/ContributionRecur.php +++ b/CRM/Contribute/BAO/ContributionRecur.php @@ -770,4 +770,79 @@ INNER JOIN civicrm_contribution con ON ( con.id = mp.contribution_id ) ); } + /** + * 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; + } + } diff --git a/tests/phpunit/api/v3/ContributionPageTest.php b/tests/phpunit/api/v3/ContributionPageTest.php index 5aae15a30e..228c3f8836 100644 --- a/tests/phpunit/api/v3/ContributionPageTest.php +++ b/tests/phpunit/api/v3/ContributionPageTest.php @@ -641,7 +641,7 @@ class api_v3_ContributionPageTest extends CiviUnitTestCase { $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']); } /** diff --git a/tests/phpunit/api/v3/ContributionTest.php b/tests/phpunit/api/v3/ContributionTest.php index 5b5c3bbb50..664f4f99d1 100644 --- a/tests/phpunit/api/v3/ContributionTest.php +++ b/tests/phpunit/api/v3/ContributionTest.php @@ -1556,6 +1556,40 @@ class api_v3_ContributionTest extends CiviUnitTestCase { $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. *