diff --git a/modules/ppcp-button/src/Endpoint/ApproveOrderEndpoint.php b/modules/ppcp-button/src/Endpoint/ApproveOrderEndpoint.php index 57e394539..7d586a6c8 100644 --- a/modules/ppcp-button/src/Endpoint/ApproveOrderEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/ApproveOrderEndpoint.php @@ -13,6 +13,7 @@ namespace WooCommerce\PayPalCommerce\Button\Endpoint; use Exception; use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; +use WooCommerce\PayPalCommerce\ApiClient\Entity\Order; use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies; @@ -199,29 +200,23 @@ class ApproveOrderEndpoint implements EndpointInterface { ); } } - $proceed = $this->threed_secure->proceed_with_order( $order ); - if ( ThreeDSecure::RETRY === $proceed ) { - throw new RuntimeException( - __( - 'Something went wrong. Please try again.', - 'woocommerce-paypal-payments' - ) - ); - } - if ( ThreeDSecure::REJECT === $proceed ) { - throw new RuntimeException( - __( - 'Unfortunately, we can\'t accept your card. Please choose a different payment method.', - 'woocommerce-paypal-payments' - ) - ); - } + + // This check will either pass, or throw an exception. + $this->verify_three_d_secure( $order ); + $this->session_handler->replace_order( $order ); + // Exit the request early. wp_send_json_success(); } - if ( $this->order_helper->contains_physical_goods( $order ) && ! $order->status()->is( OrderStatus::APPROVED ) && ! $order->status()->is( OrderStatus::CREATED ) ) { + // Verify 3DS details. Throws an error when security check fails. + $this->verify_three_d_secure( $order ); + + $is_ready = $order->status()->is( OrderStatus::APPROVED ) + || $order->status()->is( OrderStatus::CREATED ); + + if ( ! $is_ready && $this->order_helper->contains_physical_goods( $order ) ) { $message = sprintf( // translators: %s is the id of the order. __( 'Order %s is not ready for processing yet.', 'woocommerce-paypal-payments' ), @@ -279,4 +274,73 @@ class ApproveOrderEndpoint implements EndpointInterface { $this->settings->set( 'blocks_final_review_enabled', ! $final_review_enabled_setting ); $this->settings->persist(); } + + /** + * Performs a 3DS check to verify the payment is not rejected from PayPal side. + * + * This method only checks, if the payment was rejected: + * + * - No 3DS details are present: The payment can proceed. + * - 3DS details present but no rejected: Payment can proceed. + * - 3DS details with a clear rejected: Payment fails. + * + * @param Order $order The PayPal order to inspect. + * @throws RuntimeException When the 3DS check was rejected. + */ + protected function verify_three_d_secure( Order $order ) : void { + $payment_source = $order->payment_source(); + + if ( ! $payment_source ) { + // Missing 3DS details. + return; + } + + $proceed = ThreeDSecure::NO_DECISION; + $order_status = $order->status(); + $source_name = $payment_source->name(); + + /** + * For GooglePay (and possibly other payment sources) we check the order + * status, as it will clearly indicate if verification is needed. + * + * Note: PayPal is currently investigating this case. + * Maybe the order status is wrong and should be ACCEPTED, in that case, + * we could drop the condition and always run proceed_with_order(). + */ + if ( $order_status->is( OrderStatus::PAYER_ACTION_REQUIRED ) ) { + $proceed = $this->threed_secure->proceed_with_order( $order ); + } elseif ( 'card' === $source_name ) { + // For credit cards, we also check the 3DS response. + $proceed = $this->threed_secure->proceed_with_order( $order ); + } + + // Handle the verification result based on the proceed value. + switch ( $proceed ) { + case ThreeDSecure::PROCCEED: + // Check was successful. + return; + + case ThreeDSecure::NO_DECISION: + // No rejection. Let's proceed with the payment. + return; + + case ThreeDSecure::RETRY: + // Rejection case 1, verification can be retried. + throw new RuntimeException( + __( + 'Something went wrong. Please try again.', + 'woocommerce-paypal-payments' + ) + ); + + case ThreeDSecure::REJECT: + // Rejection case 2, payment was rejected. + throw new RuntimeException( + __( + 'Unfortunately, we can\'t accept your card. Please choose a different payment method.', + 'woocommerce-paypal-payments' + ) + ); + } + } }