From 510a6c6913c8b13a0672a02a3e276e5e6f83bc2d Mon Sep 17 00:00:00 2001
From: Pedro Silva
Date: Wed, 27 Mar 2024 11:50:40 +0000
Subject: [PATCH] Improve fraud prevention capabilities
---
.../ppcp-button/src/Assets/SmartButton.php | 14 ++++++--
.../src/Endpoint/CreateOrderEndpoint.php | 9 ++++++
.../src/Helper/EarlyOrderHandler.php | 9 ++++++
.../ppcp-button/src/Helper/ThreeDSecure.php | 32 ++++++++++++++-----
.../ppcp-card-fields/src/CardFieldsModule.php | 14 ++++----
.../src/SavePaymentMethodsModule.php | 6 +++-
.../src/Gateway/PayPalGateway.php | 1 +
.../CreditCardOrderInfoHandlingTrait.php | 7 ++--
.../src/Processor/OrderMetaTrait.php | 8 +++++
.../ppcp-wc-gateway/src/WCGatewayModule.php | 14 ++++++++
.../Vaulting/VaultedCreditCardHandlerTest.php | 3 ++
.../Gateway/OXXO/OXXOGatewayTest.php | 1 +
.../Processor/OrderProcessorTest.php | 3 ++
13 files changed, 102 insertions(+), 19 deletions(-)
diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php
index 12e7abfbd..17e9810ab 100644
--- a/modules/ppcp-button/src/Assets/SmartButton.php
+++ b/modules/ppcp-button/src/Assets/SmartButton.php
@@ -990,11 +990,21 @@ document.querySelector("#payment").before(document.querySelector("#ppcp-messages
if ( $this->settings->has( '3d_secure_contingency' ) ) {
$value = $this->settings->get( '3d_secure_contingency' );
if ( $value ) {
- return $value;
+ return $this->return_3ds_contingency( $value );
}
}
- return 'SCA_WHEN_REQUIRED';
+ return $this->return_3ds_contingency( 'SCA_WHEN_REQUIRED' );
+ }
+
+ /**
+ * Processes and returns the 3D Secure contingency.
+ *
+ * @param string $contingency The ThreeD secure contingency.
+ * @return string
+ */
+ private function return_3ds_contingency( string $contingency ): string {
+ return apply_filters( 'woocommerce_paypal_payments_three_d_secure_contingency', $contingency );
}
/**
diff --git a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php
index c286b9f20..980e03770 100644
--- a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php
+++ b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php
@@ -329,6 +329,15 @@ class CreateOrderEndpoint implements EndpointInterface {
if ( 'pay-now' === $data['context'] && is_a( $wc_order, \WC_Order::class ) ) {
$wc_order->update_meta_data( PayPalGateway::ORDER_ID_META_KEY, $order->id() );
$wc_order->update_meta_data( PayPalGateway::INTENT_META_KEY, $order->intent() );
+
+ $payer = $order->payer();
+ if ( $payer ) {
+ $payer_email = $payer->email_address();
+ if ( $payer_email ) {
+ $wc_order->update_meta_data( PayPalGateway::ORDER_PAYER_EMAIL_META_KEY, $payer_email );
+ }
+ }
+
$wc_order->save_meta_data();
do_action( 'woocommerce_paypal_payments_woocommerce_order_created', $wc_order, $order );
diff --git a/modules/ppcp-button/src/Helper/EarlyOrderHandler.php b/modules/ppcp-button/src/Helper/EarlyOrderHandler.php
index 03d8fbeed..adff6f56c 100644
--- a/modules/ppcp-button/src/Helper/EarlyOrderHandler.php
+++ b/modules/ppcp-button/src/Helper/EarlyOrderHandler.php
@@ -159,6 +159,15 @@ class EarlyOrderHandler {
$wc_order = wc_get_order( $order_id );
$wc_order->update_meta_data( PayPalGateway::ORDER_ID_META_KEY, $order->id() );
$wc_order->update_meta_data( PayPalGateway::INTENT_META_KEY, $order->intent() );
+
+ $payer = $order->payer();
+ if ( $payer && $wc_order instanceof \WC_Order ) {
+ $payer_email = $payer->email_address();
+ if ( $payer_email ) {
+ $wc_order->update_meta_data( PayPalGateway::ORDER_PAYER_EMAIL_META_KEY, $payer_email );
+ }
+ }
+
$wc_order->save_meta_data();
/**
diff --git a/modules/ppcp-button/src/Helper/ThreeDSecure.php b/modules/ppcp-button/src/Helper/ThreeDSecure.php
index 66c89e4be..1991112b5 100644
--- a/modules/ppcp-button/src/Helper/ThreeDSecure.php
+++ b/modules/ppcp-button/src/Helper/ThreeDSecure.php
@@ -57,21 +57,24 @@ class ThreeDSecure {
*
* @link https://developer.paypal.com/docs/business/checkout/add-capabilities/3d-secure/#authenticationresult
*
- * @param Order $order The order for which the decission is needed.
+ * @param Order $order The order for which the decision is needed.
*
* @return int
*/
public function proceed_with_order( Order $order ): int {
+
+ do_action( 'woocommerce_paypal_payments_three_d_secure_before_check', $order );
+
$payment_source = $order->payment_source();
if ( ! $payment_source ) {
- return self::NO_DECISION;
+ return $this->return_decision( self::NO_DECISION, $order );
}
if ( ! ( $payment_source->properties()->brand ?? '' ) ) {
- return self::NO_DECISION;
+ return $this->return_decision( self::NO_DECISION, $order );
}
if ( ! ( $payment_source->properties()->authentication_result ?? '' ) ) {
- return self::NO_DECISION;
+ return $this->return_decision( self::NO_DECISION, $order );
}
$authentication_result = $payment_source->properties()->authentication_result ?? null;
@@ -81,18 +84,31 @@ class ThreeDSecure {
$this->logger->info( '3DS Authentication Result: ' . wc_print_r( $result->to_array(), true ) );
if ( $result->liability_shift() === AuthResult::LIABILITY_SHIFT_POSSIBLE ) {
- return self::PROCCEED;
+ return $this->return_decision( self::PROCCEED, $order );
}
if ( $result->liability_shift() === AuthResult::LIABILITY_SHIFT_UNKNOWN ) {
- return self::RETRY;
+ return $this->return_decision( self::RETRY, $order );
}
if ( $result->liability_shift() === AuthResult::LIABILITY_SHIFT_NO ) {
- return $this->no_liability_shift( $result );
+ return $this->return_decision( $this->no_liability_shift( $result ), $order );
}
}
- return self::NO_DECISION;
+ return $this->return_decision( self::NO_DECISION, $order );
+ }
+
+ /**
+ * Processes and returns a ThreeD secure decision.
+ *
+ * @param int $decision The ThreeD secure decision.
+ * @param Order $order The PayPal Order object.
+ * @return int
+ */
+ public function return_decision( int $decision, Order $order ) {
+ $decision = apply_filters( 'woocommerce_paypal_payments_three_d_secure_decision', $decision, $order );
+ do_action( 'woocommerce_paypal_payments_three_d_secure_after_check', $order, $decision );
+ return $decision;
}
/**
diff --git a/modules/ppcp-card-fields/src/CardFieldsModule.php b/modules/ppcp-card-fields/src/CardFieldsModule.php
index 213134857..6835108c3 100644
--- a/modules/ppcp-card-fields/src/CardFieldsModule.php
+++ b/modules/ppcp-card-fields/src/CardFieldsModule.php
@@ -115,17 +115,19 @@ class CardFieldsModule implements ModuleInterface {
$settings = $c->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
+ $three_d_secure_contingency =
+ $settings->has( '3d_secure_contingency' )
+ ? apply_filters( 'woocommerce_paypal_payments_three_d_secure_contingency', $settings->get( '3d_secure_contingency' ) )
+ : '';
+
if (
- $settings->has( '3d_secure_contingency' )
- && (
- $settings->get( '3d_secure_contingency' ) === 'SCA_ALWAYS'
- || $settings->get( '3d_secure_contingency' ) === 'SCA_WHEN_REQUIRED'
- )
+ $three_d_secure_contingency === 'SCA_ALWAYS'
+ || $three_d_secure_contingency === 'SCA_WHEN_REQUIRED'
) {
$data['payment_source']['card'] = array(
'attributes' => array(
'verification' => array(
- 'method' => $settings->get( '3d_secure_contingency' ),
+ 'method' => $three_d_secure_contingency,
),
),
);
diff --git a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php
index 59010f6ca..667175aaf 100644
--- a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php
+++ b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php
@@ -281,7 +281,11 @@ class SavePaymentMethodsModule implements ModuleInterface {
$settings = $c->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
- $verification_method = $settings->has( '3d_secure_contingency' ) ? $settings->get( '3d_secure_contingency' ) : '';
+
+ $verification_method =
+ $settings->has( '3d_secure_contingency' )
+ ? apply_filters( 'woocommerce_paypal_payments_three_d_secure_contingency', $settings->get( '3d_secure_contingency' ) )
+ : '';
$change_payment_method = wc_clean( wp_unslash( $_GET['change_payment_method'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification
diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php
index 73afb085b..2a04aa478 100644
--- a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php
+++ b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php
@@ -48,6 +48,7 @@ class PayPalGateway extends \WC_Payment_Gateway {
const ORDER_ID_META_KEY = '_ppcp_paypal_order_id';
const ORDER_PAYMENT_MODE_META_KEY = '_ppcp_paypal_payment_mode';
const ORDER_PAYMENT_SOURCE_META_KEY = '_ppcp_paypal_payment_source';
+ const ORDER_PAYER_EMAIL_META_KEY = '_ppcp_paypal_payer_email';
const FEES_META_KEY = '_ppcp_paypal_fees';
const REFUND_FEES_META_KEY = '_ppcp_paypal_refund_fees';
const REFUNDS_META_KEY = '_ppcp_refunds';
diff --git a/modules/ppcp-wc-gateway/src/Processor/CreditCardOrderInfoHandlingTrait.php b/modules/ppcp-wc-gateway/src/Processor/CreditCardOrderInfoHandlingTrait.php
index 32569c60b..281045099 100644
--- a/modules/ppcp-wc-gateway/src/Processor/CreditCardOrderInfoHandlingTrait.php
+++ b/modules/ppcp-wc-gateway/src/Processor/CreditCardOrderInfoHandlingTrait.php
@@ -52,6 +52,7 @@ trait CreditCardOrderInfoHandlingTrait {
%1$s
%2$s
%3$s
+ %4$s
';
$three_d_response_order_note_result = sprintf(
$three_d_response_order_note_result_format,
@@ -60,7 +61,9 @@ trait CreditCardOrderInfoHandlingTrait {
/* translators: %s is enrollment status */
sprintf( __( 'Enrollment Status: %s', 'woocommerce-paypal-payments' ), esc_html( $result->enrollment_status() ) ),
/* translators: %s is authentication status */
- sprintf( __( 'Authentication Status: %s', 'woocommerce-paypal-payments' ), esc_html( $result->authentication_result() ) )
+ sprintf( __( 'Authentication Status: %s', 'woocommerce-paypal-payments' ), esc_html( $result->authentication_result() ) ),
+ /* translators: %s card last digits */
+ sprintf( __( 'Card Last Digits: %s', 'woocommerce-paypal-payments' ), esc_html( $payment_source->properties()->last_digits ?? '' ) )
);
$three_d_response_order_note = sprintf(
$three_d_response_order_note_format,
@@ -76,7 +79,7 @@ trait CreditCardOrderInfoHandlingTrait {
/**
* Fired when the 3DS information is added to WC order.
*/
- do_action( 'woocommerce_paypal_payments_thee_d_secure_added', $wc_order, $order );
+ do_action( 'woocommerce_paypal_payments_three_d_secure_added', $wc_order, $order );
}
}
diff --git a/modules/ppcp-wc-gateway/src/Processor/OrderMetaTrait.php b/modules/ppcp-wc-gateway/src/Processor/OrderMetaTrait.php
index b18468d7b..1d9b87a2f 100644
--- a/modules/ppcp-wc-gateway/src/Processor/OrderMetaTrait.php
+++ b/modules/ppcp-wc-gateway/src/Processor/OrderMetaTrait.php
@@ -45,6 +45,14 @@ trait OrderMetaTrait {
$wc_order->update_meta_data( PayPalGateway::ORDER_PAYMENT_SOURCE_META_KEY, $payment_source );
}
+ $payer = $order->payer();
+ if ( $payer ) {
+ $payer_email = $payer->email_address();
+ if ( $payer_email ) {
+ $wc_order->update_meta_data( PayPalGateway::ORDER_PAYER_EMAIL_META_KEY, $payer_email );
+ }
+ }
+
$wc_order->save();
do_action( 'woocommerce_paypal_payments_woocommerce_order_created', $wc_order, $order );
diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php
index e82f65e9e..03241f555 100644
--- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php
+++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php
@@ -448,6 +448,20 @@ class WCGatewayModule implements ModuleInterface {
delete_transient( 'ppcp_reference_transaction_enabled' );
}
);
+
+ add_action(
+ 'woocommerce_admin_order_data_after_billing_address',
+ function ( \WC_Order $wc_order ) {
+ if ( ! apply_filters( 'woocommerce_paypal_payments_order_details_show_paypal_email', true ) ) {
+ return;
+ }
+
+ $email = $wc_order->get_meta( PayPalGateway::ORDER_PAYER_EMAIL_META_KEY ) ?: '';
+ if ( $email ) {
+ echo '' . esc_html__( 'PayPal buyer account', 'woocommerce-paypal-payments' ) . ':
' . esc_attr( $email ) . '
';
+ }
+ }
+ );
}
/**
diff --git a/tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php b/tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php
index fb728709a..c7d176c8e 100644
--- a/tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php
+++ b/tests/PHPUnit/Vaulting/VaultedCreditCardHandlerTest.php
@@ -92,6 +92,8 @@ class VaultedCreditCardHandlerTest extends TestCase
$customer = Mockery::mock(WC_Customer::class);
$payer = Mockery::mock(Payer::class);
+ $payer->shouldReceive('email_address');
+
$this->payerFactory->shouldReceive('from_wc_order')
->andReturn($payer);
$this->shippingPreferenceFactory->shouldReceive('from_state')
@@ -100,6 +102,7 @@ class VaultedCreditCardHandlerTest extends TestCase
$order = Mockery::mock(Order::class);
$order->shouldReceive('id')->andReturn('1');
$order->shouldReceive('intent')->andReturn('CAPTURE');
+ $order->shouldReceive('payer')->andReturn($payer);
$paymentSource = Mockery::mock(PaymentSource::class);
$paymentSource->shouldReceive('name')->andReturn('card');
diff --git a/tests/PHPUnit/WcGateway/Gateway/OXXO/OXXOGatewayTest.php b/tests/PHPUnit/WcGateway/Gateway/OXXO/OXXOGatewayTest.php
index abff51a10..0f70f801c 100644
--- a/tests/PHPUnit/WcGateway/Gateway/OXXO/OXXOGatewayTest.php
+++ b/tests/PHPUnit/WcGateway/Gateway/OXXO/OXXOGatewayTest.php
@@ -89,6 +89,7 @@ private $testee;
$order->shouldReceive('id')->andReturn('1');
$order->shouldReceive('intent');
$order->shouldReceive('payment_source');
+ $order->shouldReceive('payer');
$this->orderEndpoint
->shouldReceive('create')
diff --git a/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php b/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php
index d27ec6ff7..95f1b7b48 100644
--- a/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php
+++ b/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php
@@ -93,6 +93,7 @@ class OrderProcessorTest extends TestCase
$currentOrder
->shouldReceive('payment_source')
->andReturn(null);
+ $currentOrder->shouldReceive('payer');
$wcOrder
->shouldReceive('get_meta')
@@ -230,6 +231,7 @@ class OrderProcessorTest extends TestCase
$currentOrder
->shouldReceive('payment_source')
->andReturn(null);
+ $currentOrder->shouldReceive('payer');
$wcOrder
->shouldReceive('get_meta')
@@ -357,6 +359,7 @@ class OrderProcessorTest extends TestCase
$currentOrder
->shouldReceive('purchase_units')
->andReturn([$purchaseUnit]);
+ $currentOrder->shouldReceive('payer');
$wcOrder
->shouldReceive('get_meta')