Use capture for 3ds in google

This commit is contained in:
carmenmaymo 2025-03-03 15:34:45 +01:00
parent d66a7521f5
commit ca76d33aa3
No known key found for this signature in database
GPG key ID: 6023F686B0F3102E
8 changed files with 333 additions and 2 deletions

View file

@ -14,6 +14,7 @@ const initiateRedirect = ( successUrl ) => {
}; };
const onApprove = ( context, errorHandler ) => { const onApprove = ( context, errorHandler ) => {
console.log( 'onApprove' );
return ( data, actions ) => { return ( data, actions ) => {
const canCreateOrder = const canCreateOrder =
! context.config.vaultingEnabled || data.paymentSource !== 'venmo'; ! context.config.vaultingEnabled || data.paymentSource !== 'venmo';
@ -48,7 +49,6 @@ const onApprove = ( context, errorHandler ) => {
} ); } );
} }
const orderReceivedUrl = approveData.data?.order_received_url;
initiateRedirect( orderReceivedUrl || context.config.redirect ); initiateRedirect( orderReceivedUrl || context.config.redirect );
} ); } );
}; };

View file

@ -10,7 +10,9 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Button; namespace WooCommerce\PayPalCommerce\Button;
use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveSubscriptionEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveSubscriptionEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\CaptureOrderEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\CartScriptParamsEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\CartScriptParamsEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\GetOrderEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\SimulateCartEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\SimulateCartEndpoint;
use WooCommerce\PayPalCommerce\Button\Helper\CartProductsHelper; use WooCommerce\PayPalCommerce\Button\Helper\CartProductsHelper;
use WooCommerce\PayPalCommerce\Button\Helper\CheckoutFormSaver; use WooCommerce\PayPalCommerce\Button\Helper\CheckoutFormSaver;
@ -282,6 +284,19 @@ return array(
$container->get( 'wcgateway.paypal-gateway' ) $container->get( 'wcgateway.paypal-gateway' )
); );
}, },
'button.endpoint.get-order' => static function( ContainerInterface $container ): GetOrderEndpoint {
return new GetOrderEndpoint(
$container->get( 'button.request-data' ),
$container->get( 'api.endpoint.order' )
);
},
'button.endpoint.capture-order' => static function( ContainerInterface $container ): CaptureOrderEndpoint {
return new CaptureOrderEndpoint(
$container->get( 'button.request-data' ),
$container->get( 'api.endpoint.order' ),
$container->get( 'button.helper.wc-order-creator' ),
);
},
'button.checkout-form-saver' => static function ( ContainerInterface $container ): CheckoutFormSaver { 'button.checkout-form-saver' => static function ( ContainerInterface $container ): CheckoutFormSaver {
return new CheckoutFormSaver( return new CheckoutFormSaver(
$container->get( 'session.handler' ) $container->get( 'session.handler' )

View file

@ -24,10 +24,12 @@ use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
use WooCommerce\PayPalCommerce\Blocks\Endpoint\UpdateShippingEndpoint; use WooCommerce\PayPalCommerce\Blocks\Endpoint\UpdateShippingEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveOrderEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveOrderEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveSubscriptionEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveSubscriptionEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\CaptureOrderEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\CartScriptParamsEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\CartScriptParamsEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\ChangeCartEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\ChangeCartEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\CreateOrderEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\CreateOrderEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\DataClientIdEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\DataClientIdEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\GetOrderEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData; use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData;
use WooCommerce\PayPalCommerce\Button\Endpoint\SaveCheckoutFormEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\SaveCheckoutFormEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\SimulateCartEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\SimulateCartEndpoint;
@ -1143,10 +1145,18 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
'endpoint' => \WC_AJAX::get_endpoint( ChangeCartEndpoint::ENDPOINT ), 'endpoint' => \WC_AJAX::get_endpoint( ChangeCartEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( ChangeCartEndpoint::nonce() ), 'nonce' => wp_create_nonce( ChangeCartEndpoint::nonce() ),
), ),
'get_order' => array(
'endpoint' => \WC_AJAX::get_endpoint( GetOrderEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( GetOrderEndpoint::nonce() ),
),
'create_order' => array( 'create_order' => array(
'endpoint' => \WC_AJAX::get_endpoint( CreateOrderEndpoint::ENDPOINT ), 'endpoint' => \WC_AJAX::get_endpoint( CreateOrderEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( CreateOrderEndpoint::nonce() ), 'nonce' => wp_create_nonce( CreateOrderEndpoint::nonce() ),
), ),
'capture_order' => array(
'endpoint' => \WC_AJAX::get_endpoint( CaptureOrderEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( CaptureOrderEndpoint::nonce() ),
),
'approve_order' => array( 'approve_order' => array(
'endpoint' => \WC_AJAX::get_endpoint( ApproveOrderEndpoint::ENDPOINT ), 'endpoint' => \WC_AJAX::get_endpoint( ApproveOrderEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( ApproveOrderEndpoint::nonce() ), 'nonce' => wp_create_nonce( ApproveOrderEndpoint::nonce() ),

View file

@ -10,7 +10,9 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Button; namespace WooCommerce\PayPalCommerce\Button;
use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveSubscriptionEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveSubscriptionEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\CaptureOrderEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\CartScriptParamsEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\CartScriptParamsEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\GetOrderEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\SaveCheckoutFormEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\SaveCheckoutFormEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\SimulateCartEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\SimulateCartEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\ValidateCheckoutEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\ValidateCheckoutEndpoint;
@ -177,6 +179,26 @@ class ButtonModule implements ServiceModule, ExtendingModule, ExecutableModule {
} }
); );
add_action(
'wc_ajax_' . GetOrderEndpoint::ENDPOINT,
static function () use ( $container ) {
$endpoint = $container->get( 'button.endpoint.get-order' );
assert( $endpoint instanceof GetOrderEndpoint );
$endpoint->handle_request();
}
);
add_action(
'wc_ajax_' . CaptureOrderEndpoint::ENDPOINT,
static function () use ( $container ) {
$endpoint = $container->get( 'button.endpoint.capture-order' );
assert( $endpoint instanceof GetOrderEndpoint );
$endpoint->handle_request();
}
);
add_action( add_action(
'wc_ajax_' . CreateOrderEndpoint::ENDPOINT, 'wc_ajax_' . CreateOrderEndpoint::ENDPOINT,
static function () use ( $container ) { static function () use ( $container ) {

View file

@ -0,0 +1,98 @@
<?php
/**
* Endpoint to handle PayPal Subscription created.
*
* @package WooCommerce\PayPalCommerce\Button\Endpoint
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Button\Endpoint;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
use WooCommerce\PayPalCommerce\Button\Helper\WooCommerceOrderCreator;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
/**
* Class ApproveSubscriptionEndpoint
*/
class CaptureOrderEndpoint implements EndpointInterface {
use ContextTrait;
const ENDPOINT = 'ppc-capture-order';
/**
* The request data helper.
*
* @var RequestData
*/
private $request_data;
/**
* The order endpoint.
*
* @var OrderEndpoint
*/
private $order_endpoint;
/**
* The WooCommerce order creator.
*
* @var WooCommerceOrderCreator
*/
protected $wc_order_creator;
/**
* ApproveSubscriptionEndpoint constructor.
*
* @param RequestData $request_data The request data helper.
* @param OrderEndpoint $order_endpoint The order endpoint.
*/
public function __construct(
RequestData $request_data,
OrderEndpoint $order_endpoint,
WooCommerceOrderCreator $wc_order_creator
) {
$this->request_data = $request_data;
$this->order_endpoint = $order_endpoint;
$this->wc_order_creator = $wc_order_creator;
}
/**
* The nonce.
*
* @return string
*/
public static function nonce(): string {
return self::ENDPOINT;
}
/**
* Handles the request.
*
* @return bool
* @throws RuntimeException When order not found or handling failed.
*/
public function handle_request(): bool {
$data = $this->request_data->read_request( $this->nonce() );
if ( ! isset( $data['order_id'] ) ) {
throw new RuntimeException(
__( 'No order id given', 'woocommerce-paypal-payments' )
);
}
$order = $this->order_endpoint->order( $data['order_id'] );
$order = $this->order_endpoint->capture( $order );
if ($order->status()->is('COMPLETED')) {
$wc_order = $this->wc_order_creator->create_from_paypal_order( $order, WC()->cart );
$order_received_url = $wc_order->get_checkout_order_received_url();
wp_send_json_success( array( 'order_received_url' => $order_received_url ) );
}
wp_send_json_success();
return true;
}
}

View file

@ -0,0 +1,84 @@
<?php
/**
* Endpoint to get the PayPal order.
*
* @package WooCommerce\PayPalCommerce\Button\Endpoint
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Button\Endpoint;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
/**
* Class ApproveSubscriptionEndpoint
*/
class GetOrderEndpoint implements EndpointInterface {
use ContextTrait;
const ENDPOINT = 'ppc-get-order';
/**
* The request data helper.
*
* @var RequestData
*/
private $request_data;
/**
* The order endpoint.
*
* @var OrderEndpoint
*/
private $order_endpoint;
/**
* ApproveSubscriptionEndpoint constructor.
*
* @param RequestData $request_data The request data helper.
* @param OrderEndpoint $order_endpoint The order endpoint.
*/
public function __construct(
RequestData $request_data,
OrderEndpoint $order_endpoint
) {
$this->request_data = $request_data;
$this->order_endpoint = $order_endpoint;
}
/**
* The nonce.
*
* @return string
*/
public static function nonce(): string {
return self::ENDPOINT;
}
/**
* Handles the request.
*
* @return bool
* @throws RuntimeException When order not found or handling failed.
*/
public function handle_request(): bool {
$data = $this->request_data->read_request( $this->nonce() );
if ( ! isset( $data['order_id'] ) ) {
throw new RuntimeException(
__( 'No order id given', 'woocommerce-paypal-payments' )
);
}
$order = $this->order_endpoint->order( $data['order_id'] );
wp_send_json_success(array(
'order' => $order
));
return true;
}
}

View file

@ -55,6 +55,53 @@ class BaseHandler {
return this.actionHandler().configuration().onApprove( data, actions ); return this.actionHandler().configuration().onApprove( data, actions );
} }
captureOrder( data, actions ) {
return fetch( this.ppcpConfig.ajax.get_order.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'same-origin',
body: JSON.stringify( {
nonce: this.ppcpConfig.ajax.get_order.nonce,
order_id: data.orderID,
} ),
} )
.then( ( order ) => {
console.log( 'order', order );
const orderResponse = order.json();
console.log(
orderResponse?.payment_source?.google_pay?.card
?.authentication_result
);
return fetch( this.ppcpConfig.ajax.capture_order.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'same-origin',
body: JSON.stringify( {
nonce: this.ppcpConfig.ajax.capture_order.nonce,
order_id: data.orderID,
} ),
} );
} )
.then( ( response ) => response.json() )
.then( ( captureResponse ) => {
console.log( 'Capture response:', captureResponse );
const orderReceivedUrl =
captureResponse.data?.order_received_url;
console.log( 'orderReceivedUrl', orderReceivedUrl );
setTimeout( () => {
window.location.href = orderReceivedUrl;
}, 200 );
} )
.catch( ( error ) => {
console.error( 'Error:', error );
} );
}
actionHandler() { actionHandler() {
return new CartActionHandler( this.ppcpConfig, this.errorHandler() ); return new CartActionHandler( this.ppcpConfig, this.errorHandler() );
} }

View file

@ -193,6 +193,7 @@ class GooglepayButton extends PaymentButton {
this.onButtonClick = this.onButtonClick.bind( this ); this.onButtonClick = this.onButtonClick.bind( this );
this.log( 'Create instance' ); this.log( 'Create instance' );
this.log( this.ppcpConfig );
} }
/** /**
@ -847,7 +848,31 @@ class GooglepayButton extends PaymentButton {
this.log( 'confirmOrder', confirmOrderResponse ); this.log( 'confirmOrder', confirmOrderResponse );
return 'APPROVED' === confirmOrderResponse?.status; switch ( confirmOrderResponse?.status ) {
case 'APPROVED':
return true;
case 'PAYER_ACTION_REQUIRED':
return 'action_required';
default:
return false;
}
};
/**
* Initiates payer action and handles the 3DS contingency.
*
* @param {string} orderID
*/
const initiatePayerAction = async ( orderID ) => {
this.log( 'initiatePayerAction', orderID );
this.log(
'==== Confirm Payment Completed Payer Action Required ====='
);
await widgetBuilder.paypal
.Googlepay()
.initiatePayerAction( { orderId: orderID } );
this.log( '===== Payer Action Completed =====' );
}; };
/** /**
@ -887,6 +912,28 @@ class GooglepayButton extends PaymentButton {
return isApproved; return isApproved;
}; };
const captureOrderServerSide = async ( orderID ) => {
let isCaptured = true;
this.log( 'context', this.contextHandler );
await this.contextHandler.captureOrder(
{ orderID, payer },
{
restart: () =>
new Promise( ( resolve ) => {
isCaptured = false;
resolve();
} ),
order: {
get: () =>
new Promise( ( resolve ) => {
resolve( null );
} ),
},
}
);
return isCaptured;
};
// Add billing data to session. // Add billing data to session.
moduleStorage.setPayer( payer ); moduleStorage.setPayer( payer );
setPayerData( payer ); setPayerData( payer );
@ -899,6 +946,14 @@ class GooglepayButton extends PaymentButton {
if ( ! isApprovedByPayPal ) { if ( ! isApprovedByPayPal ) {
result = paymentError( 'TRANSACTION FAILED' ); result = paymentError( 'TRANSACTION FAILED' );
} else if ( isApprovedByPayPal === 'action_required' ) {
await initiatePayerAction( orderId );
const success = await captureOrderServerSide( orderId );
if ( success ) {
result = paymentResponse( 'SUCCESS' );
} else {
result = paymentError( 'FAILED TO APPROVE' );
}
} else { } else {
const success = await approveOrderServerSide( orderId ); const success = await approveOrderServerSide( orderId );