diff --git a/CRM/Financial/BAO/Payment.php b/CRM/Financial/BAO/Payment.php index d43a9d54ea2a41410369299cf5d02579d6d993c1..7da4925b20f0b62b88c726b84781e9cf812dec78 100644 --- a/CRM/Financial/BAO/Payment.php +++ b/CRM/Financial/BAO/Payment.php @@ -38,8 +38,8 @@ class CRM_Financial_BAO_Payment { */ public static function create($params) { $contribution = civicrm_api3('Contribution', 'getsingle', ['id' => $params['contribution_id']]); - $contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus($contribution['contribution_status_id'], 'name'); - $isPaymentCompletesContribution = self::isPaymentCompletesContribution($params['contribution_id'], $params['total_amount']); + $contributionStatus = CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $contribution['contribution_status_id']); + $isPaymentCompletesContribution = self::isPaymentCompletesContribution($params['contribution_id'], $params['total_amount'], $contributionStatus); $lineItems = self::getPayableLineItems($params); $whiteList = ['check_number', 'payment_processor_id', 'fee_amount', 'total_amount', 'contribution_id', 'net_amount', 'card_type_id', 'pan_truncation', 'trxn_result_code', 'payment_instrument_id', 'trxn_id', 'trxn_date']; @@ -358,14 +358,18 @@ class CRM_Financial_BAO_Payment { } /** - * Does this payment complete the contribution + * Does this payment complete the contribution. * * @param int $contributionID * @param float $paymentAmount + * @param string $previousStatus * * @return bool */ - protected static function isPaymentCompletesContribution($contributionID, $paymentAmount) { + protected static function isPaymentCompletesContribution($contributionID, $paymentAmount, $previousStatus) { + if ($previousStatus === 'Completed') { + return FALSE; + } $outstandingBalance = CRM_Contribute_BAO_Contribution::getContributionBalance($contributionID); $cmp = bccomp($paymentAmount, $outstandingBalance, 5); return ($cmp == 0 || $cmp == 1); diff --git a/tests/phpunit/CiviTest/CiviUnitTestCase.php b/tests/phpunit/CiviTest/CiviUnitTestCase.php index 7b5cabb79040fa621ebf4b47d60e0d67f4bd1d1b..4930ee0708464533bf0f66f46b205a2dc92f6c42 100644 --- a/tests/phpunit/CiviTest/CiviUnitTestCase.php +++ b/tests/phpunit/CiviTest/CiviUnitTestCase.php @@ -3426,6 +3426,13 @@ AND ( TABLE_NAME LIKE 'civicrm_value_%' ) */ protected function validatePayments($payments) { foreach ($payments as $payment) { + $balance = CRM_Contribute_BAO_Contribution::getContributionBalance($payment['contribution_id']); + if ($balance < 0 && $balance + $payment['total_amount'] === 0.0) { + // This is an overpayment situation. there are no financial items to allocate the overpayment. + // This is a pretty rough way at guessing which payment is the overpayment - but + // for the test suite it should be enough. + continue; + } $items = $this->callAPISuccess('EntityFinancialTrxn', 'get', [ 'financial_trxn_id' => $payment['id'], 'entity_table' => 'civicrm_financial_item', diff --git a/tests/phpunit/api/v3/PaymentTest.php b/tests/phpunit/api/v3/PaymentTest.php index 13ec8c9e8e9f93d1c30af56fb85dfdbb091598a9..9fd996d667981b35e8c8e60a68ed48dfb957a270 100644 --- a/tests/phpunit/api/v3/PaymentTest.php +++ b/tests/phpunit/api/v3/PaymentTest.php @@ -687,6 +687,21 @@ class api_v3_PaymentTest extends CiviUnitTestCase { $this->validateAllPayments(); } + /** + * Test that a contribution can be overpaid with the payment api. + * + * @throws \CRM_Core_Exception + */ + public function testCreatePaymentOverPay() { + $contributionID = $this->contributionCreate(['contact_id' => $this->individualCreate()]); + $payment = $this->callAPISuccess('Payment', 'create', ['total_amount' => 5, 'order_id' => $contributionID]); + $contribution = $this->callAPISuccessGetSingle('Contribution', ['id' => $contributionID]); + $this->assertEquals('Completed', $contribution['contribution_status']); + $this->callAPISuccessGetCount('EntityFinancialTrxn', ['financial_trxn_id' => $payment['id'], 'entity_table' => 'civicrm_financial_item'], 0); + $this->validateAllPayments(); + $this->validateAllContributions(); + } + /** * Test create payment api for paylater contribution *