From 4f420a2f8ab24ec05b7e86adf814ca2f4ef2a941 Mon Sep 17 00:00:00 2001
From: Philipp Stracker
Date: Tue, 4 Mar 2025 17:29:41 +0100
Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Extract=20the=203DS=20chec?=
=?UTF-8?q?k=20into=20a=20new=20method?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../src/Endpoint/ApproveOrderEndpoint.php | 100 ++++++++++++++----
1 file changed, 82 insertions(+), 18 deletions(-)
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'
+ )
+ );
+ }
+ }
}