From 86b1c2f3331ea7e282995e92c608f36b8df2a76f Mon Sep 17 00:00:00 2001 From: Eileen McNaughton <emcnaughton@wikimedia.org> Date: Thu, 22 Jun 2023 21:54:35 -0700 Subject: [PATCH] Switch to Payment.create & repeattransaction in Authorize.net --- CRM/Contribute/BAO/Contribution.php | 1 + CRM/Core/Payment/AuthorizeNetIPN.php | 100 +++++++++++++----- CRM/Financial/BAO/Payment.php | 1 + .../CRM/Core/Payment/AuthorizeNetIPNTest.php | 2 +- 4 files changed, 76 insertions(+), 28 deletions(-) diff --git a/CRM/Contribute/BAO/Contribution.php b/CRM/Contribute/BAO/Contribution.php index d01cbcc9ee5a..6b7e21327687 100644 --- a/CRM/Contribute/BAO/Contribution.php +++ b/CRM/Contribute/BAO/Contribution.php @@ -3816,6 +3816,7 @@ public static function isSingleLineItem($id) { */ public static function completeOrder($input, $recurringContributionID, $contributionID, $isPostPaymentCreate = FALSE) { if (!$contributionID) { + CRM_Core_Error::deprecatedFunctionWarning('v3api Contribution.repeattransaction. This handling will be removed around 5.70 (calling this function directly has never been supported outside core anyway)'); return self::repeatTransaction($input, $recurringContributionID); } $transaction = new CRM_Core_Transaction(); diff --git a/CRM/Core/Payment/AuthorizeNetIPN.php b/CRM/Core/Payment/AuthorizeNetIPN.php index 2507fed8dbbd..6cd7b694a5a6 100644 --- a/CRM/Core/Payment/AuthorizeNetIPN.php +++ b/CRM/Core/Payment/AuthorizeNetIPN.php @@ -33,6 +33,16 @@ public function __construct($inputData) { parent::__construct(); } + /** + * @var string + */ + protected $transactionID; + + /** + * @var string + */ + protected $contributionStatus; + /** * Main IPN processing function. */ @@ -45,7 +55,7 @@ public function main() { $x_subscription_id = $this->getRecurProcessorID(); if (!$this->isSuccess()) { - $errorMessage = ts('Subscription payment failed - %1', [1 => htmlspecialchars($input['response_reason_text'])]); + $errorMessage = ts('Subscription payment failed - %1', [1 => htmlspecialchars($this->getInput()['response_reason_text'])]); ContributionRecur::update(FALSE) ->addWhere('id', '=', $this->getContributionRecurID()) ->setValues([ @@ -56,11 +66,30 @@ public function main() { \Civi::log('authorize_net')->info($errorMessage); return; } - if ($this->isSuccess() && ($this->getContributionStatus() !== 'Completed')) { + if ($this->getContributionStatus() !== 'Completed') { ContributionRecur::update(FALSE)->addWhere('id', '=', $this->getContributionRecurID()) ->setValues(['trxn_id' => $this->getRecurProcessorID()])->execute(); + $contributionID = $this->getContributionID(); + } + else { + $contribution = civicrm_api3('Contribution', 'repeattransaction', [ + 'contribution_recur_id' => $this->getContributionRecurID(), + 'receive_date' => $this->getInput()['receive_date'], + 'payment_processor_id' => $this->getPaymentProcessorID(), + 'trxn_id' => $this->getInput()['trxn_id'], + 'amount' => $this->getAmount(), + ]); + $contributionID = $contribution['id']; } - $this->recur(); + civicrm_api3('Payment', 'create', [ + 'trxn_id' => $this->getInput()['trxn_id'], + 'trxn_date' => $this->getInput()['receive_date'], + 'payment_processor_id' => $this->getPaymentProcessorID(), + 'contribution_id' => $contributionID, + 'total_amount' => $this->getAmount(), + 'is_send_contribution_notification' => $this->getContributionRecur()->is_email_receipt, + ]); + $this->notify(); } catch (CRM_Core_Exception $e) { Civi::log('authorize_net')->debug($e->getMessage()); @@ -71,13 +100,11 @@ public function main() { /** * @throws \CRM_Core_Exception */ - public function recur() { + public function notify() { $recur = $this->getContributionRecur(); $input = $this->getInput(); $input['payment_processor_id'] = $this->getPaymentProcessorID(); - $now = date('YmdHis'); - $isFirstOrLastRecurringPayment = FALSE; if ($this->isSuccess()) { // Approved @@ -93,7 +120,6 @@ public function recur() { } } - CRM_Contribute_BAO_Contribution::completeOrder($input, $recur->id, $this->getContributionStatus() !== 'Completed' ? $this->getContributionID() : NULL); if ($isFirstOrLastRecurringPayment) { //send recurring Notification email for user CRM_Contribute_BAO_ContributionPage::recurringNotify($this->getContributionID(), TRUE, @@ -117,7 +143,7 @@ public function getInput(): array { $input['response_reason_text'] = $this->retrieve('x_response_reason_text', 'String', FALSE); $input['subscription_paynum'] = $this->retrieve('x_subscription_paynum', 'Integer', FALSE, 0); $input['trxn_id'] = $this->retrieve('x_trans_id', 'String', FALSE); - $input['receive_date'] = $this->retrieve('receive_date', 'String', FALSE, date('YmdHis', strtotime('now'))); + $input['receive_date'] = $this->retrieve('receive_date', 'String', FALSE, date('YmdHis', time())); if ($input['trxn_id']) { $input['is_test'] = 0; @@ -126,9 +152,11 @@ public function getInput(): array { // Per CRM-17611 it would also not be passed back for a decline. elseif ($this->isSuccess()) { $input['is_test'] = 1; - $input['trxn_id'] = md5(uniqid(rand(), TRUE)); + $input['trxn_id'] = $this->transactionID ?: md5(uniqid(mt_rand(), TRUE)); } + $this->transactionID = $input['trxn_id']; + // None of this is used... $billingID = CRM_Core_BAO_LocationType::getBilling(); $params = [ 'first_name' => 'x_first_name', @@ -146,6 +174,17 @@ public function getInput(): array { return $input; } + /** + * Get amount. + * + * @return string + * + * @throws \CRM_Core_Exception + */ + protected function getAmount(): string { + return $this->retrieve('x_amount', 'String'); + } + /** * Was the transaction successful. * @@ -193,22 +232,26 @@ public function retrieve($name, $type, $abort = TRUE, $default = NULL) { */ protected function getContributionRecurObject(string $processorID, int $contactID, int $contributionID) { // joining with contribution table for extra checks - $sql = " + $sql = ' SELECT cr.id, cr.contact_id FROM civicrm_contribution_recur cr INNER JOIN civicrm_contribution co ON co.contribution_recur_id = cr.id - WHERE cr.processor_id = '{$processorID}' AND - (cr.contact_id = $contactID OR co.id = $contributionID) - LIMIT 1"; - $contRecur = CRM_Core_DAO::executeQuery($sql); - if (!$contRecur->fetch()) { + WHERE cr.processor_id = %1 AND + (cr.contact_id = %2 OR co.id = %3) + LIMIT 1'; + $contributionRecur = CRM_Core_DAO::executeQuery($sql, [ + 1 => [$processorID, 'String'], + 2 => [$contactID, 'Integer'], + 3 => [$contributionID, 'Integer'], + ]); + if (!$contributionRecur->fetch()) { throw new CRM_Core_Exception('Could not find contributionRecur id'); } - if ($contactID != $contRecur->contact_id) { - $message = ts('Recurring contribution appears to have been re-assigned from id %1 to %2, continuing with %2.', [1 => $contactID, 2 => $contRecur->contact_id]); - CRM_Core_Error::debug_log_message($message); + if ($contactID != $contributionRecur->contact_id) { + $message = ts('Recurring contribution appears to have been re-assigned from id %1 to %2, continuing with %2.', [1 => $contactID, 2 => $contributionRecur->contact_id]); + \Civi::log('authorize_net')->warning($message); } - return $contRecur; + return $contributionRecur; } /** @@ -307,15 +350,18 @@ private function getContributionRecur(): CRM_Contribute_BAO_ContributionRecur { * @throws \CRM_Core_Exception */ private function getContributionStatus(): string { - // Check if the contribution exists - // make sure contribution exists and is valid - $contribution = Contribution::get(FALSE) - ->addWhere('id', '=', $this->getContributionID()) - ->addSelect('contribution_status_id:name')->execute()->first(); - if (empty($contribution)) { - throw new CRM_Core_Exception('Failure: Could not find contribution record for ' . $this->getContributionID(), NULL, ['context' => 'Could not find contribution record: ' . $this->getContributionID() . ' in IPN request: ' . print_r($this->getInput(), TRUE)]); + if (!$this->contributionStatus) { + // Check if the contribution exists + // make sure contribution exists and is valid + $contribution = Contribution::get(FALSE) + ->addWhere('id', '=', $this->getContributionID()) + ->addSelect('contribution_status_id:name')->execute()->first(); + if (empty($contribution)) { + throw new CRM_Core_Exception('Failure: Could not find contribution record for ' . $this->getContributionID(), NULL, ['context' => 'Could not find contribution record: ' . $this->getContributionID() . ' in IPN request: ' . print_r($this->getInput(), TRUE)]); + } + $this->contributionStatus = $contribution['contribution_status_id:name']; } - return $contribution['contribution_status_id:name']; + return $this->contributionStatus; } } diff --git a/CRM/Financial/BAO/Payment.php b/CRM/Financial/BAO/Payment.php index 267a3082646f..97795f7cac19 100644 --- a/CRM/Financial/BAO/Payment.php +++ b/CRM/Financial/BAO/Payment.php @@ -188,6 +188,7 @@ public static function create(array $params): CRM_Financial_DAO_FinancialTrxn { 'is_post_payment_create' => TRUE, 'is_email_receipt' => $params['is_send_contribution_notification'], 'trxn_date' => $params['trxn_date'], + 'trxn_id' => $params['trxn_id'] ?? NULL, 'payment_instrument_id' => $paymentTrxnParams['payment_instrument_id'], 'payment_processor_id' => $paymentTrxnParams['payment_processor_id'] ?? '', ]); diff --git a/tests/phpunit/CRM/Core/Payment/AuthorizeNetIPNTest.php b/tests/phpunit/CRM/Core/Payment/AuthorizeNetIPNTest.php index 19376e92b45c..c2be5534b3b3 100644 --- a/tests/phpunit/CRM/Core/Payment/AuthorizeNetIPNTest.php +++ b/tests/phpunit/CRM/Core/Payment/AuthorizeNetIPNTest.php @@ -88,7 +88,7 @@ public function testIPNPaymentRecurNoReceipt(): void { 'billing_middle_name' => '', 'billing_last_name' => 'Adams', 'billing_street_address-5' => time() . ' Lincoln St S', - 'billing_city-5' => 'Maryknoll', + 'billing_city-5' => 'Mary-knoll', 'billing_state_province_id-5' => 1031, 'billing_postal_code-5' => 10545, 'billing_country_id-5' => 1228,