diff --git a/composer.lock b/composer.lock
index 450c96e96..afd6af3dc 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4996,5 +4996,5 @@
"ext-json": "*"
},
"platform-dev": [],
- "plugin-api-version": "2.3.0"
+ "plugin-api-version": "2.6.0"
}
diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php
index 346002474..7f8c7acc2 100644
--- a/modules/ppcp-api-client/services.php
+++ b/modules/ppcp-api-client/services.php
@@ -417,7 +417,7 @@ return array(
return new PaymentsFactory( $authorizations_factory, $capture_factory, $refund_factory );
},
'api.factory.authorization' => static function ( ContainerInterface $container ): AuthorizationFactory {
- return new AuthorizationFactory();
+ return new AuthorizationFactory( $container->get( 'api.factory.fraud-processor-response' ) );
},
'api.factory.exchange-rate' => static function ( ContainerInterface $container ): ExchangeRateFactory {
return new ExchangeRateFactory();
diff --git a/modules/ppcp-api-client/src/Entity/Authorization.php b/modules/ppcp-api-client/src/Entity/Authorization.php
index ae8e8f591..007e12475 100644
--- a/modules/ppcp-api-client/src/Entity/Authorization.php
+++ b/modules/ppcp-api-client/src/Entity/Authorization.php
@@ -28,19 +28,29 @@ class Authorization {
*/
private $authorization_status;
+ /**
+ * The fraud processor response (AVS, CVV ...).
+ *
+ * @var FraudProcessorResponse|null
+ */
+ protected $fraud_processor_response;
+
/**
* Authorization constructor.
*
- * @param string $id The id.
- * @param AuthorizationStatus $authorization_status The status.
+ * @param string $id The id.
+ * @param AuthorizationStatus $authorization_status The status.
+ * @param FraudProcessorResponse|null $fraud_processor_response The fraud processor response (AVS, CVV ...).
*/
public function __construct(
string $id,
- AuthorizationStatus $authorization_status
+ AuthorizationStatus $authorization_status,
+ ?FraudProcessorResponse $fraud_processor_response
) {
- $this->id = $id;
- $this->authorization_status = $authorization_status;
+ $this->id = $id;
+ $this->authorization_status = $authorization_status;
+ $this->fraud_processor_response = $fraud_processor_response;
}
/**
@@ -71,15 +81,30 @@ class Authorization {
$this->authorization_status->is( AuthorizationStatus::PENDING );
}
+ /**
+ * Returns the fraud processor response (AVS, CVV ...).
+ *
+ * @return FraudProcessorResponse|null
+ */
+ public function fraud_processor_response() : ?FraudProcessorResponse {
+ return $this->fraud_processor_response;
+ }
+
/**
* Returns the object as array.
*
* @return array
*/
public function to_array(): array {
- return array(
+ $data = array(
'id' => $this->id,
'status' => $this->authorization_status->name(),
);
+
+ if ( $this->fraud_processor_response ) {
+ $data['fraud_processor_response'] = $this->fraud_processor_response->to_array();
+ }
+
+ return $data;
}
}
diff --git a/modules/ppcp-api-client/src/Factory/AuthorizationFactory.php b/modules/ppcp-api-client/src/Factory/AuthorizationFactory.php
index c65e764af..d16aee2fb 100644
--- a/modules/ppcp-api-client/src/Factory/AuthorizationFactory.php
+++ b/modules/ppcp-api-client/src/Factory/AuthorizationFactory.php
@@ -19,6 +19,22 @@ use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
*/
class AuthorizationFactory {
+ /**
+ * The FraudProcessorResponseFactory factory.
+ *
+ * @var FraudProcessorResponseFactory
+ */
+ protected $fraud_processor_response_factory;
+
+ /**
+ * AuthorizationFactory constructor.
+ *
+ * @param FraudProcessorResponseFactory $fraud_processor_response_factory The FraudProcessorResponseFactory factory.
+ */
+ public function __construct( FraudProcessorResponseFactory $fraud_processor_response_factory ) {
+ $this->fraud_processor_response_factory = $fraud_processor_response_factory;
+ }
+
/**
* Returns an Authorization based off a PayPal response.
*
@@ -42,12 +58,17 @@ class AuthorizationFactory {
$reason = $data->status_details->reason ?? null;
+ $fraud_processor_response = isset( $data->processor_response ) ?
+ $this->fraud_processor_response_factory->from_paypal_response( $data->processor_response )
+ : null;
+
return new Authorization(
$data->id,
new AuthorizationStatus(
$data->status,
$reason ? new AuthorizationStatusDetails( $reason ) : null
- )
+ ),
+ $fraud_processor_response
);
}
}
diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php
index 470ddef1c..8220b0a5b 100644
--- a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php
+++ b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php
@@ -51,6 +51,8 @@ class PayPalGateway extends \WC_Payment_Gateway {
const FEES_META_KEY = '_ppcp_paypal_fees';
const REFUND_FEES_META_KEY = '_ppcp_paypal_refund_fees';
const REFUNDS_META_KEY = '_ppcp_refunds';
+ const THREE_D_AUTH_RESULT_META_KEY = '_ppcp_paypal_3DS_auth_result';
+ const FRAUD_RESULT_META_KEY = '_ppcp_paypal_fraud_result';
/**
* The Settings Renderer.
diff --git a/modules/ppcp-wc-gateway/src/Processor/CreditCardOrderInfoHandlingTrait.php b/modules/ppcp-wc-gateway/src/Processor/CreditCardOrderInfoHandlingTrait.php
new file mode 100644
index 000000000..71e96b90a
--- /dev/null
+++ b/modules/ppcp-wc-gateway/src/Processor/CreditCardOrderInfoHandlingTrait.php
@@ -0,0 +1,147 @@
+payment_source();
+ if ( ! $payment_source || $payment_source->name() !== 'card' ) {
+ return;
+ }
+
+ $authentication_result = $payment_source->properties()->authentication_result ?? null;
+ $card_brand = $payment_source->properties()->brand ?? __( 'N/A', 'woocommerce-paypal-payments' );
+
+ if ( $authentication_result ) {
+ $card_authentication_result_factory = new CardAuthenticationResultFactory();
+ $result = $card_authentication_result_factory->from_paypal_response( $authentication_result );
+
+ $three_d_response_order_note_title = __( '3DS authentication result', 'woocommerce-paypal-payments' );
+ /* translators: %1$s is 3DS order note title, %2$s is 3DS order note result markup */
+ $three_d_response_order_note_format = __( '%1$s %2$s', 'woocommerce-paypal-payments' );
+ $three_d_response_order_note_result_format = '
+ - %1$s
+ - %2$s
+ - %3$s
+ - %4$s
+
';
+ $three_d_response_order_note_result = sprintf(
+ $three_d_response_order_note_result_format,
+ /* translators: %s is liability shift */
+ sprintf( __( 'Liability Shift: %s', 'woocommerce-paypal-payments' ), esc_html( $result->liability_shift() ) ),
+ /* 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() ) ),
+ /* translators: %s is card brand */
+ sprintf( __( 'Card Brand: %s', 'woocommerce-paypal-payments' ), esc_html( $card_brand ) )
+ );
+ $three_d_response_order_note = sprintf(
+ $three_d_response_order_note_format,
+ esc_html( $three_d_response_order_note_title ),
+ wp_kses_post( $three_d_response_order_note_result )
+ );
+
+ $wc_order->add_order_note( $three_d_response_order_note );
+
+ $meta_details = array_merge( $result->to_array(), array( 'card_brand' => $card_brand ) );
+ $wc_order->update_meta_data( PayPalGateway::THREE_D_AUTH_RESULT_META_KEY, $meta_details );
+ $wc_order->save_meta_data();
+
+ /**
+ * Fired when the 3DS information is added to WC order.
+ */
+ do_action( 'woocommerce_paypal_payments_thee_d_secure_added', $wc_order, $order );
+ }
+ }
+
+ /**
+ * Handles the fraud processor response details.
+ *
+ * Adds the order note with the fraud processor response details.
+ * Adds the order meta with the fraud processor response details.
+ *
+ * @param FraudProcessorResponse $fraud The fraud processor response (AVS, CVV ...).
+ * @param Order $order The PayPal order.
+ * @param WC_Order $wc_order The WC order.
+ */
+ protected function handle_fraud( FraudProcessorResponse $fraud, Order $order, WC_Order $wc_order ): void {
+ $payment_source = $order->payment_source();
+ if ( ! $payment_source || $payment_source->name() !== 'card' ) {
+ return;
+ }
+
+ $fraud_responses = $fraud->to_array();
+ $avs_response_order_note_title = __( 'Address Verification Result', 'woocommerce-paypal-payments' );
+ /* translators: %1$s is AVS order note title, %2$s is AVS order note result markup */
+ $avs_response_order_note_format = __( '%1$s %2$s', 'woocommerce-paypal-payments' );
+ $avs_response_order_note_result_format = '';
+ $avs_response_order_note_result = sprintf(
+ $avs_response_order_note_result_format,
+ /* translators: %s is fraud AVS code */
+ sprintf( __( 'AVS: %s', 'woocommerce-paypal-payments' ), esc_html( $fraud_responses['avs_code'] ) ),
+ /* translators: %s is fraud AVS address match */
+ sprintf( __( 'Address Match: %s', 'woocommerce-paypal-payments' ), esc_html( $fraud_responses['address_match'] ) ),
+ /* translators: %s is fraud AVS postal match */
+ sprintf( __( 'Postal Match: %s', 'woocommerce-paypal-payments' ), esc_html( $fraud_responses['postal_match'] ) )
+ );
+ $avs_response_order_note = sprintf(
+ $avs_response_order_note_format,
+ esc_html( $avs_response_order_note_title ),
+ wp_kses_post( $avs_response_order_note_result )
+ );
+ $wc_order->add_order_note( $avs_response_order_note );
+
+ $cvv_response_order_note_format = '';
+ $cvv_response_order_note = sprintf(
+ $cvv_response_order_note_format,
+ /* translators: %s is fraud CVV match */
+ sprintf( __( 'CVV2 Match: %s', 'woocommerce-paypal-payments' ), esc_html( $fraud_responses['cvv_match'] ) )
+ );
+ $wc_order->add_order_note( $cvv_response_order_note );
+
+ $wc_order->update_meta_data( PayPalGateway::FRAUD_RESULT_META_KEY, $fraud_responses );
+ $wc_order->save_meta_data();
+
+ /**
+ * Fired when the fraud result information is added to WC order.
+ */
+ do_action( 'woocommerce_paypal_payments_fraud_result_added', $wc_order, $order );
+ }
+}
diff --git a/modules/ppcp-wc-gateway/src/Processor/PaymentsStatusHandlingTrait.php b/modules/ppcp-wc-gateway/src/Processor/PaymentsStatusHandlingTrait.php
index e1f4f9654..e6186badf 100644
--- a/modules/ppcp-wc-gateway/src/Processor/PaymentsStatusHandlingTrait.php
+++ b/modules/ppcp-wc-gateway/src/Processor/PaymentsStatusHandlingTrait.php
@@ -112,6 +112,10 @@ trait PaymentsStatusHandlingTrait {
'on-hold',
__( 'Awaiting payment.', 'woocommerce-paypal-payments' )
);
+ /**
+ * Fired when PayPal order is authorized.
+ */
+ do_action( 'woocommerce_paypal_payments_order_authorized', $wc_order, $authorization );
break;
case AuthorizationStatus::DENIED:
$wc_order->update_status(
diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php
index 84ea0336b..3dfd70810 100644
--- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php
+++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php
@@ -11,10 +11,11 @@ namespace WooCommerce\PayPalCommerce\WcGateway;
use Psr\Log\LoggerInterface;
use Throwable;
+use WooCommerce\PayPalCommerce\ApiClient\Entity\Authorization;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\RefreshFeatureStatusEndpoint;
-use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
+use WooCommerce\PayPalCommerce\WcGateway\Processor\CreditCardOrderInfoHandlingTrait;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
use WC_Order;
@@ -56,6 +57,8 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
*/
class WCGatewayModule implements ModuleInterface {
+ use CreditCardOrderInfoHandlingTrait;
+
/**
* {@inheritDoc}
*/
@@ -92,7 +95,7 @@ class WCGatewayModule implements ModuleInterface {
add_action(
'woocommerce_paypal_payments_order_captured',
- function ( WC_Order $wc_order, Capture $capture ) {
+ function ( WC_Order $wc_order, Capture $capture ) use ( $c ) {
$breakdown = $capture->seller_receivable_breakdown();
if ( $breakdown ) {
$wc_order->update_meta_data( PayPalGateway::FEES_META_KEY, $breakdown->to_array() );
@@ -104,43 +107,34 @@ class WCGatewayModule implements ModuleInterface {
$wc_order->save_meta_data();
}
+ $order = $c->get( 'session.handler' )->order();
+ if ( ! $order ) {
+ return;
+ }
+
$fraud = $capture->fraud_processor_response();
if ( $fraud ) {
- $fraud_responses = $fraud->to_array();
- $avs_response_order_note_title = __( 'Address Verification Result', 'woocommerce-paypal-payments' );
- /* translators: %1$s is AVS order note title, %2$s is AVS order note result markup */
- $avs_response_order_note_format = __( '%1$s %2$s', 'woocommerce-paypal-payments' );
- $avs_response_order_note_result_format = '';
- $avs_response_order_note_result = sprintf(
- $avs_response_order_note_result_format,
- /* translators: %s is fraud AVS code */
- sprintf( __( 'AVS: %s', 'woocommerce-paypal-payments' ), esc_html( $fraud_responses['avs_code'] ) ),
- /* translators: %s is fraud AVS address match */
- sprintf( __( 'Address Match: %s', 'woocommerce-paypal-payments' ), esc_html( $fraud_responses['address_match'] ) ),
- /* translators: %s is fraud AVS postal match */
- sprintf( __( 'Postal Match: %s', 'woocommerce-paypal-payments' ), esc_html( $fraud_responses['postal_match'] ) )
- );
- $avs_response_order_note = sprintf(
- $avs_response_order_note_format,
- esc_html( $avs_response_order_note_title ),
- wp_kses_post( $avs_response_order_note_result )
- );
- $wc_order->add_order_note( $avs_response_order_note );
-
- $cvv_response_order_note_format = '';
- $cvv_response_order_note = sprintf(
- $cvv_response_order_note_format,
- /* translators: %s is fraud CVV match */
- sprintf( __( 'CVV2 Match: %s', 'woocommerce-paypal-payments' ), esc_html( $fraud_responses['cvv_match'] ) )
- );
- $wc_order->add_order_note( $cvv_response_order_note );
+ $this->handle_fraud( $fraud, $order, $wc_order );
}
+ $this->handle_three_d_secure( $order, $wc_order );
+ },
+ 10,
+ 2
+ );
+
+ add_action(
+ 'woocommerce_paypal_payments_order_authorized',
+ function ( WC_Order $wc_order, Authorization $authorization ) use ( $c ) {
+ $order = $c->get( 'session.handler' )->order();
+ if ( ! $order ) {
+ return;
+ }
+
+ $fraud = $authorization->fraud_processor_response();
+ if ( $fraud ) {
+ $this->handle_fraud( $fraud, $order, $wc_order );
+ }
+ $this->handle_three_d_secure( $order, $wc_order );
},
10,
2
diff --git a/tests/PHPUnit/ApiClient/Entity/AuthorizationTest.php b/tests/PHPUnit/ApiClient/Entity/AuthorizationTest.php
index 8843ce002..c5895a2f4 100644
--- a/tests/PHPUnit/ApiClient/Entity/AuthorizationTest.php
+++ b/tests/PHPUnit/ApiClient/Entity/AuthorizationTest.php
@@ -11,7 +11,7 @@ class AuthorizationTest extends TestCase
public function testIdAndStatus()
{
$authorizationStatus = \Mockery::mock(AuthorizationStatus::class);
- $testee = new Authorization('foo', $authorizationStatus);
+ $testee = new Authorization('foo', $authorizationStatus, null);
$this->assertEquals('foo', $testee->id());
$this->assertEquals($authorizationStatus, $testee->status());
@@ -22,7 +22,7 @@ class AuthorizationTest extends TestCase
$authorizationStatus = \Mockery::mock(AuthorizationStatus::class);
$authorizationStatus->expects('name')->andReturn('CAPTURED');
- $testee = new Authorization('foo', $authorizationStatus);
+ $testee = new Authorization('foo', $authorizationStatus, null);
$expected = [
'id' => 'foo',
diff --git a/tests/PHPUnit/ApiClient/Factory/AuthorizationFactoryTest.php b/tests/PHPUnit/ApiClient/Factory/AuthorizationFactoryTest.php
index 12a76a016..71df98376 100644
--- a/tests/PHPUnit/ApiClient/Factory/AuthorizationFactoryTest.php
+++ b/tests/PHPUnit/ApiClient/Factory/AuthorizationFactoryTest.php
@@ -17,8 +17,9 @@ class AuthorizationFactoryTest extends TestCase
'id' => 'foo',
'status' => 'CAPTURED',
];
+ $fraudProcessorResponseFactory = \Mockery::mock(FraudProcessorResponseFactory::class);
- $testee = new AuthorizationFactory();
+ $testee = new AuthorizationFactory($fraudProcessorResponseFactory);
$result = $testee->from_paypal_response($response);
$this->assertInstanceOf(Authorization::class, $result);
@@ -36,7 +37,9 @@ class AuthorizationFactoryTest extends TestCase
'status' => 'CAPTURED',
];
- $testee = new AuthorizationFactory();
+ $fraudProcessorResponseFactory = \Mockery::mock(FraudProcessorResponseFactory::class);
+
+ $testee = new AuthorizationFactory($fraudProcessorResponseFactory);
$testee->from_paypal_response($response);
}
@@ -47,7 +50,9 @@ class AuthorizationFactoryTest extends TestCase
'id' => 'foo',
];
- $testee = new AuthorizationFactory();
+ $fraudProcessorResponseFactory = \Mockery::mock(FraudProcessorResponseFactory::class);
+
+ $testee = new AuthorizationFactory($fraudProcessorResponseFactory);
$testee->from_paypal_response($response);
}
}
diff --git a/tests/PHPUnit/WcGateway/Processor/AuthorizedPaymentsProcessorTest.php b/tests/PHPUnit/WcGateway/Processor/AuthorizedPaymentsProcessorTest.php
index f65338f67..5dd7c588f 100644
--- a/tests/PHPUnit/WcGateway/Processor/AuthorizedPaymentsProcessorTest.php
+++ b/tests/PHPUnit/WcGateway/Processor/AuthorizedPaymentsProcessorTest.php
@@ -251,7 +251,7 @@ class AuthorizedPaymentsProcessorTest extends TestCase
}
private function createAuthorization(string $id, string $status): Authorization {
- return new Authorization($id, new AuthorizationStatus($status));
+ return new Authorization($id, new AuthorizationStatus($status), null);
}
private function createCapture(string $id, string $status): Capture {