This commit is contained in:
Danny Dudzic 2025-08-29 12:52:36 +02:00 committed by GitHub
commit 17a1b7c865
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 209 additions and 8 deletions

View file

@ -11,6 +11,7 @@ import { snapshotFields } from '../helpers/fieldHelpers';
import useCustomerData from './useCustomerData';
import useShippingAddressChange from './useShippingAddressChange';
import useCardChange from './useCardChange';
import useSessionRestoration from './useSessionRestoration';
/**
* Custom hook to set up AXO functionality.
@ -63,6 +64,9 @@ const useAxoSetup = (
// Set up phone sync handler
usePhoneSyncHandler( paymentComponent );
// Set up session restoration
useSessionRestoration( fastlaneSdk );
// Initialize class toggles on mount
useEffect( () => {
initializeClassToggles();
@ -104,6 +108,7 @@ const useAxoSetup = (
setShippingAddress,
setCardDetails,
paymentComponent,
setCardChangeHandler,
] );
return paypalLoaded;

View file

@ -0,0 +1,87 @@
import { useEffect, useRef } from '@wordpress/element';
import { useDispatch } from '@wordpress/data';
import { setIsEmailLookupCompleted, STORE_NAME } from '../stores/axoStore';
import { log } from '../../../../ppcp-axo/resources/js/Helper/Debug';
/**
* Hook to restore Fastlane session after payment failures using triggerAuthenticationFlow
* Only runs when ppcp_fastlane_error=1 URL parameter is present
* @param {Object} fastlaneSdk - The Fastlane SDK instance
*/
const useSessionRestoration = ( fastlaneSdk ) => {
const { setShippingAddress, setCardDetails, setIsGuest } =
useDispatch( STORE_NAME );
const hasProcessed = useRef( false );
useEffect( () => {
if ( ! fastlaneSdk || hasProcessed.current ) {
return;
}
const urlParams = new URLSearchParams( window.location.search );
const hasErrorParam = urlParams.get( 'ppcp_fastlane_error' ) === '1';
if ( ! hasErrorParam ) {
return;
}
// Remove the error parameter from URL
urlParams.delete( 'ppcp_fastlane_error' );
const newUrl = new URL( window.location );
newUrl.search = urlParams.toString();
window.history.replaceState( {}, '', newUrl );
hasProcessed.current = true;
const restoreSession = async () => {
try {
const emailInput = document.getElementById( 'email' );
if ( emailInput?.value ) {
const lookupResult =
await fastlaneSdk.identity.lookupCustomerByEmail(
emailInput.value
);
wp.data.dispatch( STORE_NAME ).setIsEmailSubmitted( true );
if ( lookupResult?.customerContextId ) {
const customerContextId =
lookupResult.customerContextId;
const authenticatedCustomerResult =
await fastlaneSdk.identity.triggerAuthenticationFlow(
customerContextId
);
if (
authenticatedCustomerResult?.authenticationState ===
'succeeded'
) {
const { profileData } = authenticatedCustomerResult;
setIsGuest( false );
if ( profileData?.shippingAddress ) {
setShippingAddress(
profileData.shippingAddress
);
}
if ( profileData?.card ) {
setCardDetails( profileData.card );
}
setIsEmailLookupCompleted( true );
}
}
}
} catch ( error ) {
log( 'Failed to restore Fastlane session', 'warn' );
}
};
restoreSession();
}, [ fastlaneSdk, setShippingAddress, setCardDetails, setIsGuest ] );
};
export default useSessionRestoration;

View file

@ -60,6 +60,8 @@ class AxoManager {
this.fastlane = new Fastlane( namespace );
this.$ = jQuery;
this.hasProcessedSessionRestore = false;
this.status = {
active: false,
validEmail: false,
@ -529,7 +531,15 @@ class AxoManager {
log(
`this.lastEmailCheckedIdentity: ${ this.lastEmailCheckedIdentity }`
);
if (
const urlParams = new URLSearchParams( window.location.search );
const hasErrorParam = urlParams.get( 'ppcp_fastlane_error' ) === '1';
if ( hasErrorParam ) {
log(
'Payment failure detected, session restoration will be attempted'
);
} else if (
this.emailInput &&
this.lastEmailCheckedIdentity !== this.emailInput.value
) {
@ -662,6 +672,8 @@ class AxoManager {
await this.renderWatermark();
this.renderEmailSubmitButton();
this.watchEmail();
await this.restoreSessionAfterFailure();
}
async connect() {
@ -1383,6 +1395,94 @@ class AxoManager {
this.$( '#billing_email_field input' ).on( 'input', reEnableInput );
this.$( '#billing_email_field input' ).on( 'click', reEnableInput );
}
async restoreSessionAfterFailure() {
if ( ! this.fastlane || this.hasProcessedSessionRestore ) {
return;
}
const urlParams = new URLSearchParams( window.location.search );
const hasErrorParam = urlParams.get( 'ppcp_fastlane_error' ) === '1';
if ( ! hasErrorParam ) {
return;
}
urlParams.delete( 'ppcp_fastlane_error' );
const newUrl = new URL( window.location );
newUrl.search = urlParams.toString();
window.history.replaceState( {}, '', newUrl );
this.hasProcessedSessionRestore = true;
try {
if ( this.emailInput?.value ) {
log(
`Restoring Fastlane session for email: ${ this.emailInput.value }`
);
const lookupResult =
await this.fastlane.identity.lookupCustomerByEmail(
this.emailInput.value
);
if ( lookupResult?.customerContextId ) {
const authenticatedCustomerResult =
await this.fastlane.identity.triggerAuthenticationFlow(
lookupResult.customerContextId
);
if (
authenticatedCustomerResult?.authenticationState ===
'succeeded'
) {
const { profileData } = authenticatedCustomerResult;
if ( profileData?.shippingAddress ) {
this.setShipping( profileData.shippingAddress );
}
if ( profileData?.card ) {
this.setCard( profileData.card );
this.setStatus( 'hasCard', true );
const cardBillingAddress =
profileData.card?.paymentSource?.card
?.billingAddress;
if ( cardBillingAddress ) {
const billingData = {
address: cardBillingAddress,
};
const phoneNumber =
profileData.shippingAddress?.phoneNumber
?.nationalNumber;
if ( phoneNumber ) {
billingData.phoneNumber = phoneNumber;
}
this.setBilling( billingData );
}
}
this.setStatus( 'validEmail', true );
this.setStatus( 'hasProfile', true );
this.hideGatewaySelection = true;
this.$( '.wc_payment_methods label' ).hide();
this.$( '.wc_payment_methods input' ).hide();
await this.renderWatermark( false );
log( 'Fastlane session successfully restored' );
}
}
}
} catch ( error ) {
log( 'Failed to restore Fastlane session', 'warn' );
console.warn( 'Fastlane session restoration error:', error );
}
}
}
export default AxoManager;

View file

@ -82,7 +82,7 @@ class ReturnUrlEndpoint {
// phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( ! isset( $_GET['token'] ) ) {
wc_add_notice( __( 'Payment session expired. Please try placing your order again.', 'woocommerce-paypal-payments' ), 'error' );
wp_safe_redirect( wc_get_checkout_url() );
wp_safe_redirect( $this->get_checkout_url_with_error() );
exit();
}
$token = sanitize_text_field( wp_unslash( $_GET['token'] ) );
@ -93,7 +93,7 @@ class ReturnUrlEndpoint {
} catch ( Exception $exception ) {
$this->logger->warning( "Return URL endpoint failed to fetch order $token: " . $exception->getMessage() );
wc_add_notice( __( 'Could not retrieve payment information. Please try again.', 'woocommerce-paypal-payments' ), 'error' );
wp_safe_redirect( wc_get_checkout_url() );
wp_safe_redirect( $this->get_checkout_url_with_error() );
exit();
}
@ -104,7 +104,7 @@ class ReturnUrlEndpoint {
} catch ( Exception $e ) {
$this->logger->warning( "3DS completion failed for order $token: " . $e->getMessage() );
wc_add_notice( $this->get_3ds_error_message( $e ), 'error' );
wp_safe_redirect( wc_get_checkout_url() );
wp_safe_redirect( $this->get_checkout_url_with_error() );
exit();
}
}
@ -128,7 +128,7 @@ class ReturnUrlEndpoint {
$this->logger->warning( "Return URL endpoint $token: no WC order ID." );
wc_add_notice( __( 'Order information is missing. Please try placing your order again.', 'woocommerce-paypal-payments' ), 'error' );
wp_safe_redirect( wc_get_checkout_url() );
wp_safe_redirect( $this->get_checkout_url_with_error() );
exit();
}
@ -137,7 +137,7 @@ class ReturnUrlEndpoint {
$this->logger->warning( "Return URL endpoint $token: WC order $wc_order_id not found." );
wc_add_notice( __( 'Order not found. Please try placing your order again.', 'woocommerce-paypal-payments' ), 'error' );
wp_safe_redirect( wc_get_checkout_url() );
wp_safe_redirect( $this->get_checkout_url_with_error() );
exit();
}
@ -150,7 +150,7 @@ class ReturnUrlEndpoint {
$payment_gateway = $this->get_payment_gateway( $wc_order->get_payment_method() );
if ( ! $payment_gateway ) {
wc_add_notice( __( 'Payment gateway is unavailable. Please try again or contact support.', 'woocommerce-paypal-payments' ), 'error' );
wp_safe_redirect( wc_get_checkout_url() );
wp_safe_redirect( $this->get_checkout_url_with_error() );
exit();
}
@ -170,10 +170,19 @@ class ReturnUrlEndpoint {
}
wc_add_notice( __( 'Payment processing failed. Please try again or contact support.', 'woocommerce-paypal-payments' ), 'error' );
wp_safe_redirect( wc_get_checkout_url() );
wp_safe_redirect( $this->get_checkout_url_with_error() );
exit();
}
/**
* Get checkout URL with Fastlane error parameter.
*
* @return string
*/
private function get_checkout_url_with_error(): string {
return add_query_arg( 'ppcp_fastlane_error', '1', wc_get_checkout_url() );
}
/**
* Check if order needs 3DS completion.
*