From a6e27f8be8f4c251864664e1bb8bac5efaf698ba Mon Sep 17 00:00:00 2001 From: Eileen McNaughton Date: Thu, 22 Jun 2023 21:45:10 -0700 Subject: [PATCH] Update Recurring end_date when setting status to completed This moves the logic from Authorize.net to the ContributionRecur function that is called when a contribution is added to a recurring such that when it determines it is completed it also sets the end_date --- CRM/Contribute/BAO/ContributionRecur.php | 1 + CRM/Core/Payment/AuthorizeNetIPN.php | 9 ++- .../CRM/Core/Payment/AuthorizeNetIPNTest.php | 65 ++++++++++++++----- tests/phpunit/CiviTest/CiviUnitTestCase.php | 5 +- 4 files changed, 56 insertions(+), 24 deletions(-) diff --git a/CRM/Contribute/BAO/ContributionRecur.php b/CRM/Contribute/BAO/ContributionRecur.php index 3f7731ef28..6d74fa4f07 100644 --- a/CRM/Contribute/BAO/ContributionRecur.php +++ b/CRM/Contribute/BAO/ContributionRecur.php @@ -973,6 +973,7 @@ INNER JOIN civicrm_contribution con ON ( con.id = mp.contribution_id ) if (!empty($existing['installments']) && self::isComplete($recurringContributionID, $existing['installments'])) { $params['contribution_status_id'] = 'Completed'; $params['next_sched_contribution_date'] = 'null'; + $params['end_date'] = 'now'; } else { // Only update next sched date if it's empty or up to 48 hours away because payment processors may be managing diff --git a/CRM/Core/Payment/AuthorizeNetIPN.php b/CRM/Core/Payment/AuthorizeNetIPN.php index f5f21f9875..2507fed8db 100644 --- a/CRM/Core/Payment/AuthorizeNetIPN.php +++ b/CRM/Core/Payment/AuthorizeNetIPN.php @@ -56,6 +56,10 @@ class CRM_Core_Payment_AuthorizeNetIPN extends CRM_Core_Payment_BaseIPN { \Civi::log('authorize_net')->info($errorMessage); return; } + if ($this->isSuccess() && ($this->getContributionStatus() !== 'Completed')) { + ContributionRecur::update(FALSE)->addWhere('id', '=', $this->getContributionRecurID()) + ->setValues(['trxn_id' => $this->getRecurProcessorID()])->execute(); + } $this->recur(); } catch (CRM_Core_Exception $e) { @@ -78,7 +82,6 @@ class CRM_Core_Payment_AuthorizeNetIPN extends CRM_Core_Payment_BaseIPN { if ($this->isSuccess()) { // Approved if ($this->getContributionStatus() !== 'Completed') { - $recur->trxn_id = $recur->processor_id; $isFirstOrLastRecurringPayment = CRM_Core_Payment::RECURRING_PAYMENT_START; } @@ -86,11 +89,7 @@ class CRM_Core_Payment_AuthorizeNetIPN extends CRM_Core_Payment_BaseIPN { ($input['subscription_paynum'] >= $recur->installments) ) { // this is the last payment - $recur->end_date = $now; $isFirstOrLastRecurringPayment = CRM_Core_Payment::RECURRING_PAYMENT_END; - // This end date update should occur in ContributionRecur::updateOnNewPayment - // testIPNPaymentRecurNoReceipt has test cover. - $recur->save(); } } diff --git a/tests/phpunit/CRM/Core/Payment/AuthorizeNetIPNTest.php b/tests/phpunit/CRM/Core/Payment/AuthorizeNetIPNTest.php index d15269881e..19376e92b4 100644 --- a/tests/phpunit/CRM/Core/Payment/AuthorizeNetIPNTest.php +++ b/tests/phpunit/CRM/Core/Payment/AuthorizeNetIPNTest.php @@ -1,5 +1,6 @@ _paymentProcessorID = $this->paymentProcessorAuthorizeNetCreate(['is_test' => 0]); $this->_contactID = $this->individualCreate(); - $contributionPage = $this->callAPISuccess('contribution_page', 'create', [ + $contributionPage = $this->callAPISuccess('ContributionPage', 'create', [ 'title' => 'Test Contribution Page', - 'financial_type_id' => $this->_financialTypeID, + 'financial_type_id' => 'Donation', 'currency' => 'USD', - 'payment_processor' => $this->_paymentProcessorID, + 'payment_processor' => $this->ids['PaymentProcessor']['authorize_net'], 'max_amount' => 1000, 'receipt_from_email' => 'gaia@the.cosmos', 'receipt_from_name' => 'Pachamama', @@ -54,7 +55,7 @@ class CRM_Core_Payment_AuthorizeNetIPNTest extends CiviUnitTestCase { $mut = new CiviMailUtils($this, TRUE); // Turn off receipts in contribution page. $api_params = [ - 'id' => $this->_contributionPageID, + 'id' => $this->ids['ContributionPage'][0], 'is_email_receipt' => FALSE, ]; $this->callAPISuccess('ContributionPage', 'update', $api_params); @@ -68,12 +69,13 @@ class CRM_Core_Payment_AuthorizeNetIPNTest extends CiviUnitTestCase { // is_email_receipt is not set to 1 if the originating contribution page // has is_email_receipt set to 0. $_REQUEST['mode'] = 'live'; + /* @var \CRM_Contribute_Form_Contribution $form */ $form = $this->getFormObject('CRM_Contribute_Form_Contribution', [ 'total_amount' => 200, 'financial_type_id' => 1, 'receive_date' => date('m/d/Y'), 'receive_date_time' => date('H:i:s'), - 'contact_id' => $this->_contactID, + 'contact_id' => $this->ids['Contact']['individual_0'], 'contribution_status_id' => 1, 'credit_card_number' => 4444333322221111, 'cvv2' => 123, @@ -95,10 +97,10 @@ class CRM_Core_Payment_AuthorizeNetIPNTest extends CiviUnitTestCase { 'installments' => 2, 'hidden_AdditionalDetail' => 1, 'hidden_Premium' => 1, - 'payment_processor_id' => $this->_paymentProcessorID, + 'payment_processor_id' => $this->ids['PaymentProcessor']['authorize_net'], 'currency' => 'USD', 'source' => 'bob sled race', - 'contribution_page_id' => $this->_contributionPageID, + 'contribution_page_id' => $this->ids['ContributionPage'][0], 'is_recur' => TRUE, ]); $form->buildForm(); @@ -151,20 +153,25 @@ class CRM_Core_Payment_AuthorizeNetIPNTest extends CiviUnitTestCase { */ public function testIPNPaymentRecurSuccess(): void { CRM_Core_BAO_ConfigSetting::enableComponent('CiviCampaign'); - $this->setupRecurringPaymentProcessorTransaction(); + $this->setupRecurringPaymentProcessorTransaction([ + 'installments' => 3, + ]); + $this->assertRecurStatus('Pending'); + $IPN = new CRM_Core_Payment_AuthorizeNetIPN($this->getRecurTransaction()); $IPN->main(); - $contribution = $this->callAPISuccess('contribution', 'getsingle', ['id' => $this->_contributionID]); + $this->assertRecurStatus('In Progress'); + $contribution = $this->callAPISuccess('Contribution', 'getsingle', ['id' => $this->ids['Contribution']['default']]); $this->assertEquals(1, $contribution['contribution_status_id']); $this->assertEquals('6511143069', $contribution['trxn_id']); // source gets set by processor $this->assertSame(strpos($contribution['contribution_source'], 'Online Contribution:'), 0); - $contributionRecur = $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $this->_contributionRecurID]); - $this->assertEquals(5, $contributionRecur['contribution_status_id']); + $IPN = new CRM_Core_Payment_AuthorizeNetIPN($this->getRecurSubsequentTransaction()); $IPN->main(); - $contribution = $this->callAPISuccess('contribution', 'get', [ - 'contribution_recur_id' => $this->_contributionRecurID, + $this->assertRecurStatus('In Progress'); + $contribution = $this->callAPISuccess('Contribution', 'get', [ + 'contribution_recur_id' => $this->ids['ContributionRecur']['default'], 'sequential' => 1, ])['values']; $this->assertCount(2, $contribution); @@ -173,6 +180,31 @@ class CRM_Core_Payment_AuthorizeNetIPNTest extends CiviUnitTestCase { $this->assertEquals(date('Y-m-d'), date('Y-m-d', strtotime($secondContribution['receive_date']))); $this->assertEquals('expensive', $secondContribution['amount_level']); $this->assertEquals($this->ids['campaign'][0], $secondContribution['campaign_id']); + + $IPN = new CRM_Core_Payment_AuthorizeNetIPN(array_merge($this->getRecurSubsequentTransaction(), ['x_subscription_paynum' => 3, 'x_trans_id' => 'three'])); + $IPN->main(); + $this->assertRecurStatus('Completed'); + } + + /** + * Assertion for recurring status. + * + * @param string $status + */ + public function assertRecurStatus(string $status): void { + try { + $contributionRecur = ContributionRecur::get() + ->addWhere('id', '=', $this->ids['ContributionRecur']['default']) + ->addSelect('contribution_status_id:name', 'end_date') + ->execute()->first(); + $this->assertEquals($status, $contributionRecur['contribution_status_id:name']); + if ($status === 'Completed') { + $this->assertNotEmpty($contributionRecur['end_date']); + } + } + catch (CRM_Core_Exception $e) { + $this->fail('Failed to get recurring' . $e->getMessage()); + } } /** @@ -193,8 +225,7 @@ class CRM_Core_Payment_AuthorizeNetIPNTest extends CiviUnitTestCase { $this->assertEquals('6511143069', $contribution['trxn_id']); // source gets set by processor $this->assertEquals('Online Contribution:', substr($contribution['contribution_source'], 0, 20)); - $contributionRecur = $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $this->_contributionRecurID]); - $this->assertEquals(5, $contributionRecur['contribution_status_id']); + $this->assertRecurStatus('In Progress'); } /** @@ -211,8 +242,8 @@ class CRM_Core_Payment_AuthorizeNetIPNTest extends CiviUnitTestCase { $this->assertEquals('6511143069', $contribution['trxn_id']); // source gets set by processor $this->assertEquals('Online Contribution:', substr($contribution['contribution_source'], 0, 20)); - $contributionRecur = $this->callAPISuccess('contribution_recur', 'getsingle', ['id' => $this->_contributionRecurID]); - $this->assertEquals(5, $contributionRecur['contribution_status_id']); + + $this->assertRecurStatus('In Progress'); $IPN = new CRM_Core_Payment_AuthorizeNetIPN(array_merge(['receive_date' => '2010-07-01'], $this->getRecurSubsequentTransaction())); $IPN->main(); $contribution = $this->callAPISuccess('contribution', 'get', [ diff --git a/tests/phpunit/CiviTest/CiviUnitTestCase.php b/tests/phpunit/CiviTest/CiviUnitTestCase.php index df2a5f2774..d9617122ef 100644 --- a/tests/phpunit/CiviTest/CiviUnitTestCase.php +++ b/tests/phpunit/CiviTest/CiviUnitTestCase.php @@ -782,6 +782,7 @@ class CiviUnitTestCase extends PHPUnit\Framework\TestCase { ], $params); $result = $this->callAPISuccess('PaymentProcessor', 'create', $params); + $this->ids['PaymentProcessor']['authorize_net'] = (int) $result['id']; return (int) $result['id']; } @@ -2335,8 +2336,8 @@ class CiviUnitTestCase extends PHPUnit\Framework\TestCase { 'processor_id' => $this->ids['Contact']['individual_0'], 'api.Order.create' => $contributionParams, ], $recurParams))['values'][0]; - $this->_contributionRecurID = $contributionRecur['id']; - $this->_contributionID = $contributionRecur['api.Order.create']['id']; + $this->ids['ContributionRecur']['default'] = $this->_contributionRecurID = $contributionRecur['id']; + $this->ids['Contribution']['default'] = $this->_contributionID = $contributionRecur['api.Order.create']['id']; $this->ids['Contribution'][0] = $this->_contributionID; } -- 2.25.1