diff --git a/changelog/feat-9810-add-seller-message b/changelog/feat-9810-add-seller-message new file mode 100644 index 00000000000..2669c24015b --- /dev/null +++ b/changelog/feat-9810-add-seller-message @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add seller_message to failed order notes diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index 4f579caf2b1..d1be21241b9 100644 --- a/includes/class-wc-payment-gateway-wcpay.php +++ b/includes/class-wc-payment-gateway-wcpay.php @@ -20,7 +20,19 @@ use WCPay\Constants\Intent_Status; use WCPay\Constants\Payment_Type; use WCPay\Constants\Payment_Method; -use WCPay\Exceptions\{ Add_Payment_Method_Exception, Amount_Too_Small_Exception, Process_Payment_Exception, Intent_Authentication_Exception, API_Exception, Invalid_Address_Exception, Fraud_Prevention_Enabled_Exception, Invalid_Phone_Number_Exception, Rate_Limiter_Enabled_Exception, Order_ID_Mismatch_Exception, Order_Not_Found_Exception, New_Process_Payment_Exception }; +use WCPay\Exceptions\{Add_Payment_Method_Exception, + Amount_Too_Small_Exception, + API_Merchant_Exception, + Process_Payment_Exception, + Intent_Authentication_Exception, + API_Exception, + Invalid_Address_Exception, + Fraud_Prevention_Enabled_Exception, + Invalid_Phone_Number_Exception, + Rate_Limiter_Enabled_Exception, + Order_ID_Mismatch_Exception, + Order_Not_Found_Exception, + New_Process_Payment_Exception}; use WCPay\Core\Server\Request\Cancel_Intention; use WCPay\Core\Server\Request\Capture_Intention; use WCPay\Core\Server\Request\Create_And_Confirm_Intention; @@ -1270,6 +1282,9 @@ public function process_payment( $order_id ) { ); $error_details = esc_html( rtrim( $e->getMessage(), '.' ) ); + if ( $e instanceof API_Merchant_Exception ) { + $error_details = $error_details . '. ' . esc_html( rtrim( $e->get_merchant_message(), '.' ) ); + } if ( $e instanceof API_Exception && 'card_error' === $e->get_error_type() ) { // If the payment failed with a 'card_error' API exception, initialize the fraud meta box diff --git a/includes/class-wc-payments.php b/includes/class-wc-payments.php index 17300478794..fff5b9bb591 100644 --- a/includes/class-wc-payments.php +++ b/includes/class-wc-payments.php @@ -354,6 +354,7 @@ public static function init() { include_once __DIR__ . '/exceptions/class-base-exception.php'; include_once __DIR__ . '/exceptions/class-api-exception.php'; + include_once __DIR__ . '/exceptions/class-api-merchant-exception.php'; include_once __DIR__ . '/exceptions/class-connection-exception.php'; include_once __DIR__ . '/core/class-mode.php'; diff --git a/includes/compat/subscriptions/trait-wc-payment-gateway-wcpay-subscriptions.php b/includes/compat/subscriptions/trait-wc-payment-gateway-wcpay-subscriptions.php index 31ec70bedf8..d2584f9b824 100644 --- a/includes/compat/subscriptions/trait-wc-payment-gateway-wcpay-subscriptions.php +++ b/includes/compat/subscriptions/trait-wc-payment-gateway-wcpay-subscriptions.php @@ -11,6 +11,7 @@ use WCPay\Core\Server\Request\Get_Intention; use WCPay\Exceptions\API_Exception; +use WCPay\Exceptions\API_Merchant_Exception; use WCPay\Exceptions\Invalid_Payment_Method_Exception; use WCPay\Exceptions\Add_Payment_Method_Exception; use WCPay\Exceptions\Order_Not_Found_Exception; @@ -342,6 +343,11 @@ public function scheduled_subscription_payment( $amount, $renewal_order ) { $renewal_order->update_status( 'failed' ); if ( ! empty( $payment_information ) ) { + $error_details = esc_html( rtrim( $e->getMessage(), '.' ) ); + if ( $e instanceof API_Merchant_Exception ) { + $error_details = $error_details . '. ' . esc_html( rtrim( $e->get_merchant_message(), '.' ) ); + } + $note = sprintf( WC_Payments_Utils::esc_interpolated_html( /* translators: %1: the failed payment amount, %2: error message */ @@ -358,7 +364,7 @@ public function scheduled_subscription_payment( $amount, $renewal_order ) { wc_price( $amount, [ 'currency' => WC_Payments_Utils::get_order_intent_currency( $renewal_order ) ] ), $renewal_order ), - esc_html( rtrim( $e->getMessage(), '.' ) ) + $error_details ); $renewal_order->add_order_note( $note ); } diff --git a/includes/exceptions/class-api-merchant-exception.php b/includes/exceptions/class-api-merchant-exception.php new file mode 100644 index 00000000000..ac10bd271bc --- /dev/null +++ b/includes/exceptions/class-api-merchant-exception.php @@ -0,0 +1,49 @@ +merchant_message = $merchant_message; + + parent::__construct( $message, $error_code, $http_code, $error_type, $decline_code, $code, $previous ); + } + + /** + * Returns the merchant message. + * + * @return string Merchant message. + */ + public function get_merchant_message(): string { + return $this->merchant_message; + } +} diff --git a/includes/wc-payment-api/class-wc-payments-api-client.php b/includes/wc-payment-api/class-wc-payments-api-client.php index b3adf5bf7eb..e90094d57de 100644 --- a/includes/wc-payment-api/class-wc-payments-api-client.php +++ b/includes/wc-payment-api/class-wc-payments-api-client.php @@ -9,6 +9,7 @@ use WCPay\Constants\Intent_Status; use WCPay\Exceptions\API_Exception; +use WCPay\Exceptions\API_Merchant_Exception; use WCPay\Exceptions\Amount_Too_Small_Exception; use WCPay\Exceptions\Amount_Too_Large_Exception; use WCPay\Exceptions\Connection_Exception; @@ -2419,6 +2420,13 @@ protected function check_response_for_errors( $response ) { ); Logger::error( "$error_message ($error_code)" ); + + if ( 'card_declined' === $error_code && isset( $response_body['error']['payment_intent']['charges']['data'][0]['outcome']['seller_message'] ) ) { + $merchant_message = $response_body['error']['payment_intent']['charges']['data'][0]['outcome']['seller_message']; + + throw new API_Merchant_Exception( $message, $error_code, $response_code, $merchant_message, $error_type, $decline_code ); + } + throw new API_Exception( $message, $error_code, $response_code, $error_type, $decline_code ); } } diff --git a/tests/unit/wc-payment-api/test-class-wc-payments-api-client.php b/tests/unit/wc-payment-api/test-class-wc-payments-api-client.php index 3fc4a56c8f6..fb95bcf1591 100644 --- a/tests/unit/wc-payment-api/test-class-wc-payments-api-client.php +++ b/tests/unit/wc-payment-api/test-class-wc-payments-api-client.php @@ -7,7 +7,9 @@ use WCPay\Constants\Country_Code; use WCPay\Constants\Intent_Status; +use WCPay\Core\Server\Request\Create_And_Confirm_Intention; use WCPay\Exceptions\API_Exception; +use WCPay\Exceptions\API_Merchant_Exception; use WCPay\Internal\Logger; use WCPay\Exceptions\Connection_Exception; use WCPay\Fraud_Prevention\Fraud_Prevention_Service; @@ -1195,6 +1197,24 @@ public function test_get_tracking_info() { $this->assertEquals( $expect, $result ); } + public function test_throws_api_merchant_exception() { + $mock_response = []; + $mock_response['error']['code'] = 'card_declined'; + $mock_response['error']['payment_intent']['charges']['data'][0]['outcome']['seller_message'] = 'Bank declined'; + $this->set_http_mock_response( + 401, + $mock_response + ); + + try { + // This is a dummy call to trigger the response so that our test can validate the exception. + $this->payments_api_client->create_subscription(); + } catch ( API_Merchant_Exception $e ) { + $this->assertSame( 'card_declined', $e->get_error_code() ); + $this->assertSame( 'Bank declined', $e->get_merchant_message() ); + } + } + /** * Set up http mock response. *