Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions modules/ppcp-api-client/src/Factory/ReturnUrlFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@ public function from_context(
array $request_data = array(),
array $custom_query_args = array()
): string {
return add_query_arg(
array_merge(
array( self::PCP_QUERY_ARG => 'button' ),
$custom_query_args
),
$this->wc_url_from_context( $context, $request_data )
$base_url = $this->wc_url_from_context( $context, $request_data );

$hash_params = array_merge(
array( self::PCP_QUERY_ARG => 'button' ),
$custom_query_args
);

return $base_url . '#' . http_build_query( $hash_params );
}

protected function wc_url_from_context( string $context, array $request_data = array() ): string {
Expand Down
16 changes: 15 additions & 1 deletion modules/ppcp-button/resources/js/button.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { loadPaypalScript } from './modules/Helper/ScriptLoading';
import buttonModuleWatcher from './modules/ButtonModuleWatcher';
import MessagesBootstrap from './modules/ContextBootstrap/MessagesBootstrap';
import { apmButtonsInit } from './modules/Helper/ApmButtons';
import CrossBrowserCartRestorer from './modules/Helper/CrossBrowserCartRestorer';

// TODO: could be a good idea to have a separate spinner for each gateway,
// but I think we care mainly about the script loading, so one spinner should be enough.
Expand All @@ -45,6 +46,16 @@ const bootstrap = () => {
);
const spinner = new Spinner();

// Check if we need to handle cross-browser AppSwitch
const crossBrowserCartRestorer = new CrossBrowserCartRestorer(
PayPalCommerceGateway
);

if ( crossBrowserCartRestorer.shouldRestore() ) {
crossBrowserCartRestorer.restore();
return; // Stop bootstrap - we're handling cross-browser order creation
}

const formSaver = new FormSaver(
PayPalCommerceGateway.ajax.save_checkout_form.endpoint,
PayPalCommerceGateway.ajax.save_checkout_form.nonce
Expand Down Expand Up @@ -90,7 +101,10 @@ const bootstrap = () => {
};

const doBasicCheckoutValidation = () => {
if ( PayPalCommerceGateway.basic_checkout_validation_enabled || PayPalCommerceGateway.is_free_trial_cart ) {
if (
PayPalCommerceGateway.basic_checkout_validation_enabled ||
PayPalCommerceGateway.is_free_trial_cart
) {
// A quick fix to get the errors about empty form fields before attempting PayPal order,
// it should solve #513 for most of the users, but it is not a proper solution.
// Currently it is disabled by default because a better solution is now implemented
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import ResumeFlowHelper from './ResumeFlowHelper';
import Spinner from './Spinner';

/**
* Handles cross-browser cart restoration for AppSwitch flows
*/
class CrossBrowserCartRestorer {
constructor( config ) {
this.config = config;
}

/**
* Check if we should restore (create cross-browser order)
*
* @return {boolean} True if this is a cross-browser AppSwitch return
*/
shouldRestore() {
if ( ! this.isAppSwitchReturn() ) {
return false;
}

const cartKey = this.getCartKeyFromHash();
const savedCartHash = this.getSavedCartHashFromHash();

if ( ! cartKey || ! savedCartHash ) {
return false;
}

const currentCartHash = this.config.cart_hash;

// If cart hashes match, no need for cross-browser handling.
return currentCartHash !== savedCartHash;
}

restore() {
const cartKey = this.getCartKeyFromHash();
this.createCrossBrowserOrder( cartKey );
}

createCrossBrowserOrder( cartKey ) {
const endpointConfig = this.config.ajax?.create_cross_browser_order;

if ( ! endpointConfig || ! endpointConfig.endpoint ) {
console.error(
'Create cross-browser order endpoint not configured'
);
return;
}

const spinner = Spinner.fullPage();
spinner.block();

fetch( endpointConfig.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'same-origin',
body: JSON.stringify( {
nonce: endpointConfig.nonce,
cart_key: cartKey,
} ),
} )
.then( ( response ) => response.json() )
.then( ( response ) => {
const { success, data } = response;

if ( ! success ) {
console.error(
'Cross-browser order creation failed:',
data?.message
);
return;
}

if ( ! data.redirect ) {
console.error(
'Missing redirect URL in cross-browser order creation response'
);
return;
}

this.cleanCrossBrowserAppSwitchParams();
window.location.href = data.redirect + window.location.hash;
} )
.catch( ( error ) => {
console.error( 'Error creating cross-browser order:', error );
} )
.finally( () => spinner.unblock() );
}

/**
* Clean cross-browser AppSwitch params from hash while preserving PayPal's params
*/
cleanCrossBrowserAppSwitchParams() {
if ( ! window.location.hash ) {
return;
}

const CROSS_BROWSER_APPSWITCH_PARAMS = [
'pcp-return',
'pcp-cart',
'pcp-cart-hash',
];

const hashString = window.location.hash.substring( 1 );
const params = hashString.split( '&' );

const cleanedParams = params.filter( ( param ) => {
const paramName = param.split( '=' )[ 0 ];
return ! CROSS_BROWSER_APPSWITCH_PARAMS.includes( paramName );
} );

const baseUrl = window.location.pathname + window.location.search;

const newHash = '#' + cleanedParams.join( '&' );
window.history.replaceState( null, '', baseUrl + newHash );
}

isAppSwitchReturn() {
const params = this.getHashParams();
return params[ 'pcp-return' ] === 'button';
}

getCartKeyFromHash() {
const params = this.getHashParams();
return params[ 'pcp-cart' ] || null;
}

getSavedCartHashFromHash() {
const params = this.getHashParams();
return params[ 'pcp-cart-hash' ] || null;
}

getHashParams() {
if ( ! window.location.hash ) {
return {};
}

const hashString = window.location.hash.substring( 1 );
const params = new URLSearchParams( hashString );

return Object.fromEntries( params );
}
}

export default CrossBrowserCartRestorer;
14 changes: 14 additions & 0 deletions modules/ppcp-button/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface;
use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveOrderEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\ChangeCartEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\CreateCrossBrowserOrderEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\CreateOrderEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\DataClientIdEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\GetOrderEndpoint;
Expand Down Expand Up @@ -262,6 +263,19 @@
$context
);
},
'button.endpoint.create-cross-browser-order' => static function ( ContainerInterface $container ): CreateCrossBrowserOrderEndpoint {
$request_data = $container->get( 'button.request-data' );
$cart_data_storage = $container->get( 'button.session.storage.card-data.transient' );
$order_endpoint = $container->get( 'api.endpoint.order' );
$wc_order_creator = $container->get( 'button.helper.wc-order-creator' );

return new CreateCrossBrowserOrderEndpoint(
$request_data,
$cart_data_storage,
$order_endpoint,
$wc_order_creator
);
},
'button.endpoint.approve-subscription' => static function ( ContainerInterface $container ): ApproveSubscriptionEndpoint {
return new ApproveSubscriptionEndpoint(
$container->get( 'button.request-data' ),
Expand Down
6 changes: 6 additions & 0 deletions modules/ppcp-button/src/Assets/SmartButton.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveSubscriptionEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\CartScriptParamsEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\ChangeCartEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\CreateCrossBrowserOrderEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\CreateOrderEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\DataClientIdEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\GetOrderEndpoint;
Expand Down Expand Up @@ -1247,6 +1248,10 @@ public function script_data(): array {
'wp_rest_nonce' => wp_create_nonce( 'wc_store_api' ),
'update_shipping_method' => \WC_AJAX::get_endpoint( 'update_shipping_method' ),
),
'create_cross_browser_order' => array(
'endpoint' => \WC_AJAX::get_endpoint( CreateCrossBrowserOrderEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( CreateCrossBrowserOrderEndpoint::nonce() ),
),
),
'cart_contains_subscription' => $this->subscription_helper->cart_contains_subscription(),
'subscription_plan_id' => $this->subscription_helper->paypal_subscription_id(),
Expand Down Expand Up @@ -1380,6 +1385,7 @@ public function script_data(): array {
'productType' => null,
'manualRenewalEnabled' => $this->subscription_helper->accept_manual_renewals(),
'final_review_enabled' => $this->final_review_enabled,
'cart_hash' => WC()->cart ? WC()->cart->get_cart_hash() : '',
);

if ( is_product() ) {
Expand Down
Loading
Loading