From 5acf07039814c71af0d529a4b1522d3b41369acd Mon Sep 17 00:00:00 2001 From: Jitendra Purohit Date: Fri, 24 Jul 2020 19:29:26 +0530 Subject: [PATCH] dev/core#1906 - Allow payment create api to record payment on Failed Contributions --- CRM/Contribute/BAO/Contribution.php | 1 + CRM/Financial/BAO/Payment.php | 15 ++++ tests/phpunit/api/v3/PaymentTest.php | 116 ++++++++++++++++++++++++--- 3 files changed, 121 insertions(+), 11 deletions(-) diff --git a/CRM/Contribute/BAO/Contribution.php b/CRM/Contribute/BAO/Contribution.php index 97632dc2a3..07465e6e3d 100644 --- a/CRM/Contribute/BAO/Contribution.php +++ b/CRM/Contribute/BAO/Contribution.php @@ -3924,6 +3924,7 @@ INNER JOIN civicrm_activity ON civicrm_activity_contact.activity_id = civicrm_ac 'Refunded' => ['Cancelled', 'Completed'], 'Partially paid' => ['Completed'], 'Pending refund' => ['Completed', 'Refunded'], + 'Failed' => ['Pending'], ]; if (!in_array($contributionStatuses[$fields['contribution_status_id']], diff --git a/CRM/Financial/BAO/Payment.php b/CRM/Financial/BAO/Payment.php index 1101aab35e..f841363f14 100644 --- a/CRM/Financial/BAO/Payment.php +++ b/CRM/Financial/BAO/Payment.php @@ -79,6 +79,21 @@ class CRM_Financial_BAO_Payment { $paymentTrxnParams['status_id'] = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Refunded'); } + //If Payment is recorded on Failed contribution, update it to Pending. + if ($contributionStatus === 'Failed' && $params['total_amount'] > 0) { + //Enter a financial trxn to record a payment in receivable account + //as failed transaction does not insert any trxn values. Hence, if Payment is + //recorded on a failed contribution, the transition happens from Failed -> Pending -> Completed. + $ftParams = array_merge($paymentTrxnParams, [ + 'from_financial_account_id' => NULL, + 'to_financial_account_id' => $accountsReceivableAccount, + 'is_payment' => 0, + 'status_id' => CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Pending'), + ]); + CRM_Core_BAO_FinancialTrxn::create($ftParams); + $contributionStatus = 'Pending'; + self::updateContributionStatus($contribution['id'], $contributionStatus); + } $trxn = CRM_Core_BAO_FinancialTrxn::create($paymentTrxnParams); if ($params['total_amount'] < 0 && !empty($params['cancelled_payment_id'])) { diff --git a/tests/phpunit/api/v3/PaymentTest.php b/tests/phpunit/api/v3/PaymentTest.php index c01bf4b608..50eaf3b738 100644 --- a/tests/phpunit/api/v3/PaymentTest.php +++ b/tests/phpunit/api/v3/PaymentTest.php @@ -899,52 +899,146 @@ class api_v3_PaymentTest extends CiviUnitTestCase { } /** - * Test create payment api for pay later contribution with partial payment. + * Test create payment api for failed contribution. * * @throws \CRM_Core_Exception */ - public function testCreatePaymentPayLaterPartialPayment() { + public function testCreatePaymentOnFailedContribution() { $this->createLoggedInUser(); + //Create a direct Failed Contribution (no ft record inserted). + $contributionParams = [ + 'total_amount' => 50, + 'currency' => 'USD', + 'contact_id' => $this->_individualId, + 'financial_type_id' => 1, + 'contribution_status_id' => 'Failed', + ]; + $contribution = $this->callAPISuccess('Contribution', 'create', $contributionParams); + + //Complete the payment in a single call. + $params = [ + 'contribution_id' => $contribution['id'], + 'total_amount' => 50, + ]; + $payment = $this->callAPISuccess('Payment', 'create', $params); + + //Verify 2 rows are added to the financial trxn as payment is moved from + //Failed -> Pending -> Completed, i.e, 0 -> 7(Account receivable) -> 6 (Deposit Bank). + $params = [ + 'entity_id' => $contribution['id'], + 'entity_table' => 'civicrm_contribution', + ]; + $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params); + $this->assertEquals($eft['count'], 2); + + //Test 2 + //Create a Pending Contribution so an FT record is inserted. $contributionParams = [ 'total_amount' => 100, 'currency' => 'USD', 'contact_id' => $this->_individualId, 'financial_type_id' => 1, - 'contribution_status_id' => 2, + 'contribution_status_id' => 'Pending', 'is_pay_later' => 1, ]; $contribution = $this->callAPISuccess('Order', 'create', $contributionParams); - //Create partial payment + + //Mark it as failed. No FT record inserted on this update + //so the payment is still in the account receivable account id 7. + $this->callAPISuccess('Contribution', 'create', [ + 'id' => $contribution['id'], + 'contribution_status_id' => 'Failed', + ]); + $this->createPartialPaymentOnContribution($contribution['id'], 60, 100.00); + + //Call payment create on the failed contribution. $params = [ 'contribution_id' => $contribution['id'], - 'total_amount' => 60, + 'total_amount' => 40, ]; $payment = $this->callAPISuccess('Payment', 'create', $params); $expectedResult = [ $payment['id'] => [ - 'total_amount' => 60, + 'from_financial_account_id' => 7, + 'to_financial_account_id' => 6, + 'total_amount' => 40, 'status_id' => 1, 'is_payment' => 1, ], ]; $this->checkPaymentResult($payment, $expectedResult); - // Check entity financial trxn created properly + + //Check total ft rows are 4: 2 from initial pending + partial payment + //+ 2 for failed -> completed transition. $params = [ 'entity_id' => $contribution['id'], 'entity_table' => 'civicrm_contribution', + ]; + $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params); + $this->assertEquals($eft['count'], 4); + + $this->validateAllPayments(); + } + + /** + * Create partial payment for contribution + * + * @param $contributionID + * @param $partialAmount + * @param $totalAmount + */ + public function createPartialPaymentOnContribution($contributionID, $partialAmount, $totalAmount) { + //Create partial payment + $params = [ + 'contribution_id' => $contributionID, + 'total_amount' => $partialAmount, + ]; + $payment = $this->callAPISuccess('Payment', 'create', $params); + $expectedResult = [ + $payment['id'] => [ + 'total_amount' => $partialAmount, + 'status_id' => 1, + 'is_payment' => 1, + ], + ]; + $this->checkPaymentResult($payment, $expectedResult); + // Check entity financial trxn created properly + $params = [ + 'entity_id' => $contributionID, + 'entity_table' => 'civicrm_contribution', 'financial_trxn_id' => $payment['id'], ]; $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params); - $this->assertEquals($eft['values'][$eft['id']]['amount'], 60); + $this->assertEquals($eft['values'][$eft['id']]['amount'], $partialAmount); $params = [ 'entity_table' => 'civicrm_financial_item', 'financial_trxn_id' => $payment['id'], ]; $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params); - $this->assertEquals($eft['values'][$eft['id']]['amount'], 60); - $contribution = $this->callAPISuccess('contribution', 'get', ['id' => $contribution['id']]); + $this->assertEquals($eft['values'][$eft['id']]['amount'], $partialAmount); + $contribution = $this->callAPISuccess('contribution', 'get', ['id' => $contributionID]); $this->assertEquals($contribution['values'][$contribution['id']]['contribution_status'], 'Partially paid'); - $this->assertEquals($contribution['values'][$contribution['id']]['total_amount'], 100.00); + $this->assertEquals($contribution['values'][$contribution['id']]['total_amount'], $totalAmount); + } + + /** + * Test create payment api for pay later contribution with partial payment. + * + * @throws \CRM_Core_Exception + */ + public function testCreatePaymentPayLaterPartialPayment() { + $this->createLoggedInUser(); + $contributionParams = [ + 'total_amount' => 100, + 'currency' => 'USD', + 'contact_id' => $this->_individualId, + 'financial_type_id' => 1, + 'contribution_status_id' => 2, + 'is_pay_later' => 1, + ]; + $contribution = $this->callAPISuccess('Order', 'create', $contributionParams); + $this->createPartialPaymentOnContribution($contribution['id'], 60, 100.00); + //Create full payment $params = [ 'contribution_id' => $contribution['id'], -- 2.25.1