Skip to content

Commit

Permalink
POC: payment request functionality with cart token
Browse files Browse the repository at this point in the history
  • Loading branch information
frosso committed Mar 21, 2024
1 parent b31af3a commit e91c5aa
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 51 deletions.
39 changes: 35 additions & 4 deletions client/checkout/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export default class WCPayAPI {
this.stripePlatform = null;
this.request = request;
this.isWooPayRequesting = false;
this.paymentRequestCartInfo = null;
}

createStripe( publishableKey, locale, accountId = '', betas = [] ) {
Expand Down Expand Up @@ -435,6 +436,28 @@ export default class WCPayAPI {
} );
}

async paymentRequestMaybeInitCartInfo() {
if ( this.paymentRequestCartInfo ) {
return Promise.resolve();
}

const response = await window.wp.apiFetch( {
method: 'GET',
path: '/wc/store/v1/cart',
// omitting credentials, to create a new cart object separate from the user's cart.
credentials: 'omit',
// parse: false to ensure we can get the response headers
parse: false,
} );

this.paymentRequestCartInfo = {
nonce: response.headers.get( 'Nonce' ),
cartToken: response.headers.get( 'Cart-Token' ),
};

return Promise.resolve();
}

/**
* Submits shipping address to get available shipping options
* from Payment Request button.
Expand Down Expand Up @@ -487,10 +510,18 @@ export default class WCPayAPI {
* @param {Object} productData Product data.
* @return {Promise} Promise for the request to the server.
*/
paymentRequestAddToCart( productData ) {
return this.request( getPaymentRequestAjaxURL( 'add_to_cart' ), {
security: getPaymentRequestData( 'nonce' )?.add_to_cart,
...productData,
async paymentRequestAddToCart( productData ) {
await this.paymentRequestMaybeInitCartInfo();

// TODO ~FR: this is the juicy stuff
return window.wp.apiFetch( {
method: 'POST',
path: '/wc/store/v1/cart/add-item',
headers: {
Nonce: this.paymentRequestCartInfo.nonce,
'Cart-Token': this.paymentRequestCartInfo.cartToken,
},
data: productData,
} );
}

Expand Down
2 changes: 2 additions & 0 deletions client/payment-request/event-handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from './utils';

export const shippingAddressChangeHandler = async ( api, event ) => {
// TODO ~FR: send updated info to cart with cart token, and update accordingly.
const response = await api.paymentRequestCalculateShippingOptions(
normalizeShippingAddress( event.shippingAddress )
);
Expand All @@ -23,6 +24,7 @@ export const shippingAddressChangeHandler = async ( api, event ) => {
};

export const shippingOptionChangeHandler = async ( api, event ) => {
// TODO ~FR: send updated info to cart with cart token, and update accordingly.
const response = await api.paymentRequestUpdateShippingDetails(
event.shippingOption
);
Expand Down
106 changes: 59 additions & 47 deletions client/payment-request/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { getPaymentRequest, displayLoginConfirmation } from './utils';

jQuery( ( $ ) => {
// Don't load if blocks checkout is being loaded.
// TODO ~FR: investigate need for `has_block`
if (
wcpayPaymentRequestParams.has_block &&
! wcpayPaymentRequestParams.is_pay_for_order
Expand Down Expand Up @@ -91,6 +92,7 @@ jQuery( ( $ ) => {
getAttributes: function () {
const select = $( '.variations_form' ).find( '.variations select' );
const data = {};
const attributes = [];
let count = 0;
let chosen = 0;

Expand All @@ -106,12 +108,27 @@ jQuery( ( $ ) => {

count++;
data[ attributeName ] = value;
attributes.push( {
// TODO ~FR: the Store API accepts the variable attribute's label, rather than an internal identifier:
// https://github.com/woocommerce/woocommerce-blocks/blob/trunk/src/StoreApi/docs/cart.md#add-item
// Unfortunately, I couldn't find a better way to get the label from the page,
// other than querying the variation's relative `label`.
// It's an unfortunate hack - which doesn't even work when labels have special characters in them.
attribute: document.querySelector(
`label[for="${ attributeName.replace(
'attribute_',
''
) }"]`
).innerHTML,
value,
} );
} );

return {
count: count,
count,
chosenCount: chosen,
data: data,
data,
attributes,
};
},

Expand Down Expand Up @@ -184,14 +201,15 @@ jQuery( ( $ ) => {
}

const data = {
product_id: productId,
id: productId,
qty: $( '.quantity .qty' ).val(),
attributes: $( '.variations_form' ).length
? wcpayPaymentRequest.getAttributes().data
variation: $( '.variations_form' ).length
? wcpayPaymentRequest.getAttributes().attributes
: [],
};

// Add extension data to the POST body
// TODO ~FR: investigate functionality with other extensions (e.g.: bookable products).
const formData = $( 'form.cart' ).serializeArray();
$.each( formData, ( i, field ) => {
if ( /^(addon-|wc_)/.test( field.name ) ) {
Expand Down Expand Up @@ -338,9 +356,9 @@ jQuery( ( $ ) => {

/**
* Creates a wrapper around a function that ensures a function can not
* called in rappid succesion. The function can only be executed once and then agin after
* called in rapid succession. The function can only be executed once and then agin after
* the wait time has expired. Even if the wrapper is called multiple times, the wrapped
* function only excecutes once and then blocks until the wait time expires.
* function only executes once and then blocks until the wait time expires.
*
* @param {int} wait Milliseconds wait for the next time a function can be executed.
* @param {Function} func The function to be wrapped.
Expand Down Expand Up @@ -404,9 +422,9 @@ jQuery( ( $ ) => {
},

attachProductPageEventListeners: ( prButton, paymentRequest ) => {
let paymentRequestError = [];
const addToCartButton = $( '.single_add_to_cart_button' );

// TODO ~FR: look at this
prButton.on( 'click', ( evt ) => {
trackPaymentRequestButtonClick( 'product' );

Expand Down Expand Up @@ -442,12 +460,6 @@ jQuery( ( $ ) => {
return;
}

if ( paymentRequestError.length > 0 ) {
evt.preventDefault();
window.alert( paymentRequestError );
return;
}

wcpayPaymentRequest.addToCart();
} );

Expand All @@ -466,7 +478,8 @@ jQuery( ( $ ) => {
$( document.body ).on( 'woocommerce_variation_has_changed', () => {
wcpayPaymentRequest.blockPaymentRequestButton();

$.when( wcpayPaymentRequest.getSelectedProductData() )
wcpayPaymentRequest
.getSelectedProductData()
.then( ( response ) => {
/**
* If the customer aborted the payment request, we need to re init the payment request button to ensure the shipping
Expand Down Expand Up @@ -508,27 +521,27 @@ jQuery( ( $ ) => {
'.qty',
wcpayPaymentRequest.debounce( 250, () => {
wcpayPaymentRequest.blockPaymentRequestButton();
paymentRequestError = [];

$.when(
wcpayPaymentRequest.getSelectedProductData()
).then( ( response ) => {
if (
! wcpayPaymentRequest.paymentAborted &&
wcpayPaymentRequestParams.product
.needs_shipping === response.needs_shipping
) {
paymentRequest.update( {
total: response.total,
displayItems: response.displayItems,
} );
} else {
wcpayPaymentRequest.reInitPaymentRequest(
response
);
}
wcpayPaymentRequest.unblockPaymentRequestButton();
} );

wcpayPaymentRequest
.getSelectedProductData()
.then( ( response ) => {
if (
! wcpayPaymentRequest.paymentAborted &&
wcpayPaymentRequestParams.product
.needs_shipping ===
response.needs_shipping
) {
paymentRequest.update( {
total: response.total,
displayItems: response.displayItems,
} );
} else {
wcpayPaymentRequest.reInitPaymentRequest(
response
);
}
wcpayPaymentRequest.unblockPaymentRequestButton();
} );
} )
);
},
Expand Down Expand Up @@ -613,18 +626,15 @@ jQuery( ( $ ) => {
return;
}

const {
total: { amount: total },
displayItems,
order,
} = wcpayPaymentRequestPayForOrderParams;

wcpayPaymentRequest.startPaymentRequest( {
stripe: api.getStripe(),
requestShipping: false,
total,
displayItems,
handler: payForOrderHandler( order ),
total: wcpayPaymentRequestPayForOrderParams.total.amount,
displayItems:
wcpayPaymentRequestPayForOrderParams.displayItems,
handler: payForOrderHandler(
wcpayPaymentRequestPayForOrderParams.order
),
} );
} else if ( wcpayPaymentRequestParams.is_product_page ) {
wcpayPaymentRequest.startPaymentRequest( {
Expand Down Expand Up @@ -682,7 +692,7 @@ jQuery( ( $ ) => {

// Listen for the WC Bookings wc_bookings_calculate_costs event to complete
// and add the bookable product to the cart, using the response to update the
// payment request request params with correct totals.
// payment request params with correct totals.
$( document ).ajaxComplete( function ( event, xhr, settings ) {
if ( wcBookingFormChanged ) {
if (
Expand All @@ -692,7 +702,9 @@ jQuery( ( $ ) => {
) {
wcpayPaymentRequest.blockPaymentRequestButton();
wcBookingFormChanged = false;
return wcpayPaymentRequest.addToCart().then( ( response ) => {

wcpayPaymentRequest.addToCart().then( ( response ) => {
// TODO ~FR: investigate functionality with other extensions (e.g.: bookable products).
wcpayPaymentRequestParams.product.total = response.total;
wcpayPaymentRequestParams.product.displayItems =
response.displayItems;
Expand Down
2 changes: 2 additions & 0 deletions includes/class-wc-payments-payment-request-button-handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ public function set_session() {
return;
}

// TODO ~FR: still needed?
WC()->session->set_customer_session_cookie( true );
}

Expand Down Expand Up @@ -711,6 +712,7 @@ public function scripts() {
}

$payment_request_params = [
// TODO ~FR: audit of these parameters to determine which ones are still needed.
'ajax_url' => admin_url( 'admin-ajax.php' ),
'wc_ajax_url' => WC_AJAX::get_endpoint( '%%endpoint%%' ),
'stripe' => [
Expand Down

0 comments on commit e91c5aa

Please sign in to comment.