-
Notifications
You must be signed in to change notification settings - Fork 69
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: tokenized cart PRBs on PDPs (#8644)
- Loading branch information
Showing
24 changed files
with
1,814 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
Significance: minor | ||
Type: add | ||
|
||
feat: tokenized cart PRBs on PDPs via feature flag. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
/* global jQuery */ | ||
|
||
let $wcpayPaymentRequestContainer = null; | ||
|
||
const paymentRequestButtonUi = { | ||
init: ( { $container } ) => { | ||
$wcpayPaymentRequestContainer = $container; | ||
}, | ||
|
||
getElements: () => { | ||
return jQuery( | ||
'.wcpay-payment-request-wrapper,#wcpay-payment-request-button-separator' | ||
); | ||
}, | ||
|
||
blockButton: () => { | ||
// check if element isn't already blocked before calling block() to avoid blinking overlay issues | ||
// blockUI.isBlocked is either undefined or 0 when element is not blocked | ||
if ( $wcpayPaymentRequestContainer.data( 'blockUI.isBlocked' ) ) { | ||
return; | ||
} | ||
|
||
$wcpayPaymentRequestContainer.block( { message: null } ); | ||
}, | ||
|
||
unblockButton: () => { | ||
paymentRequestButtonUi.show(); | ||
$wcpayPaymentRequestContainer.unblock(); | ||
}, | ||
|
||
showButton: ( paymentRequestButton ) => { | ||
if ( $wcpayPaymentRequestContainer.length ) { | ||
paymentRequestButtonUi.show(); | ||
paymentRequestButton.mount( '#wcpay-payment-request-button' ); | ||
} | ||
}, | ||
|
||
hide: () => { | ||
paymentRequestButtonUi.getElements().hide(); | ||
}, | ||
|
||
show: () => { | ||
paymentRequestButtonUi.getElements().show(); | ||
}, | ||
}; | ||
|
||
export default paymentRequestButtonUi; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
/* global jQuery */ | ||
|
||
/** | ||
* External dependencies | ||
*/ | ||
import apiFetch from '@wordpress/api-fetch'; | ||
import { applyFilters } from '@wordpress/hooks'; | ||
|
||
/** | ||
* Internal dependencies | ||
*/ | ||
import { getPaymentRequestData } from './frontend-utils'; | ||
|
||
export default class PaymentRequestCartApi { | ||
// Used on product pages to interact with an anonymous cart. | ||
// This anonymous cart is separate from the customer's cart, which might contain additional products. | ||
// This functionality is also useful to calculate product/shipping pricing (and shipping needs) | ||
// for compatibility scenarios with other plugins (like WC Bookings, Product Add-Ons, WC Deposits, etc.). | ||
cartRequestHeaders = {}; | ||
|
||
/** | ||
* Creates an order from the cart object. | ||
* See https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/src/StoreApi/docs/checkout.md#process-order-and-payment | ||
* | ||
* @param {{ | ||
* billing_address: Object, | ||
* shipping_address: Object, | ||
* customer_note: string?, | ||
* payment_method: string, | ||
* payment_data: Array, | ||
* }} paymentData Additional payment data to place the order. | ||
* @return {Promise} Result of the order creation request. | ||
*/ | ||
async placeOrder( paymentData ) { | ||
return await apiFetch( { | ||
method: 'POST', | ||
path: '/wc/store/v1/checkout', | ||
credentials: 'omit', | ||
headers: { | ||
'X-WooPayments-Express-Payment-Request': true, | ||
'X-WooPayments-Express-Payment-Request-Nonce': | ||
getPaymentRequestData( 'nonce' ).tokenized_cart_nonce || | ||
undefined, | ||
...this.cartRequestHeaders, | ||
}, | ||
data: paymentData, | ||
} ); | ||
} | ||
|
||
/** | ||
* Returns the customer's cart object. | ||
* See https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/src/StoreApi/docs/cart.md#get-cart | ||
* | ||
* @return {Promise} Cart response object. | ||
*/ | ||
async getCart() { | ||
return await apiFetch( { | ||
method: 'GET', | ||
path: '/wc/store/v1/cart', | ||
headers: { | ||
...this.cartRequestHeaders, | ||
}, | ||
} ); | ||
} | ||
|
||
/** | ||
* Creates and returns a new cart object. The response type is the same as `getCart()`. | ||
* | ||
* @return {Promise} Cart response object. | ||
*/ | ||
async createAnonymousCart() { | ||
const response = await 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.cartRequestHeaders = { | ||
Nonce: response.headers.get( 'Nonce' ), | ||
'Cart-Token': response.headers.get( 'Cart-Token' ), | ||
'X-WooPayments-Express-Payment-Request-Nonce': response.headers.get( | ||
'X-WooPayments-Express-Payment-Request-Nonce' | ||
), | ||
}; | ||
} | ||
|
||
/** | ||
* Update customer data and return the full cart response, or an error. | ||
* See https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/src/StoreApi/docs/cart.md#update-customer | ||
* | ||
* @param {{ | ||
* billing_address: Object?, | ||
* shipping_address: Object?, | ||
* }} customerData Customer data to update. | ||
* @return {Promise} Cart Response on success, or an Error Response on failure. | ||
*/ | ||
async updateCustomer( customerData ) { | ||
return await apiFetch( { | ||
method: 'POST', | ||
path: '/wc/store/v1/cart/update-customer', | ||
credentials: 'omit', | ||
headers: { | ||
'X-WooPayments-Express-Payment-Request': true, | ||
'X-WooPayments-Express-Payment-Request-Nonce': | ||
getPaymentRequestData( 'nonce' ).tokenized_cart_nonce || | ||
undefined, | ||
...this.cartRequestHeaders, | ||
}, | ||
data: customerData, | ||
} ); | ||
} | ||
|
||
/** | ||
* Selects an available shipping rate for a package, then returns the full cart response, or an error | ||
* See https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/src/StoreApi/docs/cart.md#select-shipping-rate | ||
* | ||
* @param {{rate_id: string, package_id: integer}} shippingRate The selected shipping rate. | ||
* @return {Promise} Cart Response on success, or an Error Response on failure. | ||
*/ | ||
async selectShippingRate( shippingRate ) { | ||
return await apiFetch( { | ||
method: 'POST', | ||
path: '/wc/store/v1/cart/select-shipping-rate', | ||
credentials: 'omit', | ||
headers: { | ||
...this.cartRequestHeaders, | ||
}, | ||
data: shippingRate, | ||
} ); | ||
} | ||
|
||
/** | ||
* Add an item to the cart and return the full cart response, or an error. | ||
* See https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/src/StoreApi/docs/cart.md#add-item | ||
* | ||
* @return {Promise} Cart Response on success, or an Error Response on failure. | ||
*/ | ||
async addProductToCart() { | ||
const productData = { | ||
// can be modified in case of variable products, WC bookings plugin, etc. | ||
id: jQuery( '.single_add_to_cart_button' ).val(), | ||
quantity: parseInt( jQuery( '.quantity .qty' ).val(), 10 ) || 1, | ||
// can be modified in case of variable products, WC bookings plugin, etc. | ||
variation: [], | ||
}; | ||
|
||
return await apiFetch( { | ||
method: 'POST', | ||
path: '/wc/store/v1/cart/add-item', | ||
credentials: 'omit', | ||
headers: { | ||
...this.cartRequestHeaders, | ||
}, | ||
data: applyFilters( | ||
'wcpay.payment-request.cart-add-item', | ||
productData | ||
), | ||
} ); | ||
} | ||
|
||
/** | ||
* Removes all items from the cart and clears the cart headers. | ||
* See https://github.com/woocommerce/woocommerce/blob/trunk/plugins/woocommerce/src/StoreApi/docs/cart.md#remove-item | ||
* | ||
* @return {undefined} | ||
*/ | ||
async emptyCart() { | ||
try { | ||
const cartData = await apiFetch( { | ||
method: 'GET', | ||
path: '/wc/store/v1/cart', | ||
credentials: 'omit', | ||
headers: { | ||
...this.cartRequestHeaders, | ||
}, | ||
} ); | ||
|
||
const removeItemsPromises = cartData.items.map( ( item ) => { | ||
return apiFetch( { | ||
method: 'POST', | ||
path: '/wc/store/v1/cart/remove-item', | ||
credentials: 'omit', | ||
headers: { | ||
...this.cartRequestHeaders, | ||
}, | ||
data: { | ||
key: item.key, | ||
}, | ||
} ); | ||
} ); | ||
|
||
await Promise.all( removeItemsPromises ); | ||
} catch ( e ) { | ||
// let's ignore the error, it's likely not going to be relevant. | ||
} | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
client/tokenized-payment-request/compatibility/wc-deposits.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
/* global jQuery */ | ||
jQuery( ( $ ) => { | ||
// WooCommerce Deposits support. | ||
// Trigger the "woocommerce_variation_has_changed" event when the deposit option is changed. | ||
$( 'input[name=wc_deposit_option],input[name=wc_deposit_payment_plan]' ).on( | ||
'change', | ||
() => { | ||
$( 'form' ) | ||
.has( | ||
'input[name=wc_deposit_option],input[name=wc_deposit_payment_plan]' | ||
) | ||
.trigger( 'woocommerce_variation_has_changed' ); | ||
} | ||
); | ||
} ); |
37 changes: 37 additions & 0 deletions
37
client/tokenized-payment-request/compatibility/wc-order-attribution.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/* global jQuery */ | ||
|
||
/** | ||
* External dependencies | ||
*/ | ||
import { addFilter } from '@wordpress/hooks'; | ||
|
||
addFilter( | ||
'wcpay.payment-request.cart-place-order-extension-data', | ||
'automattic/wcpay/payment-request', | ||
( extensionData ) => { | ||
const orderAttributionValues = jQuery( | ||
'#wcpay-express-checkout__order-attribution-inputs input' | ||
); | ||
|
||
if ( ! orderAttributionValues.length ) { | ||
return extensionData; | ||
} | ||
|
||
const orderAttributionData = {}; | ||
orderAttributionValues.each( function () { | ||
const name = jQuery( this ) | ||
.attr( 'name' ) | ||
.replace( 'wc_order_attribution_', '' ); | ||
const value = jQuery( this ).val(); | ||
|
||
if ( name && value ) { | ||
orderAttributionData[ name ] = value; | ||
} | ||
} ); | ||
|
||
return { | ||
...extensionData, | ||
'woocommerce/order-attribution': orderAttributionData, | ||
}; | ||
} | ||
); |
80 changes: 80 additions & 0 deletions
80
client/tokenized-payment-request/compatibility/wc-product-variations.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
/* global jQuery */ | ||
|
||
/** | ||
* External dependencies | ||
*/ | ||
import { addFilter, doAction } from '@wordpress/hooks'; | ||
import paymentRequestButtonUi from '../button-ui'; | ||
import { waitForAction } from '../frontend-utils'; | ||
|
||
jQuery( ( $ ) => { | ||
$( document.body ).on( 'woocommerce_variation_has_changed', async () => { | ||
try { | ||
paymentRequestButtonUi.blockButton(); | ||
|
||
doAction( 'wcpay.payment-request.update-button-data' ); | ||
await waitForAction( 'wcpay.payment-request.update-button-data' ); | ||
|
||
paymentRequestButtonUi.unblockButton(); | ||
} catch ( e ) { | ||
paymentRequestButtonUi.hide(); | ||
} | ||
} ); | ||
} ); | ||
|
||
addFilter( | ||
'wcpay.payment-request.cart-add-item', | ||
'automattic/wcpay/payment-request', | ||
( productData ) => { | ||
const $variationInformation = jQuery( '.single_variation_wrap' ); | ||
if ( ! $variationInformation.length ) { | ||
return productData; | ||
} | ||
|
||
const productId = $variationInformation | ||
.find( 'input[name="product_id"]' ) | ||
.val(); | ||
return { | ||
...productData, | ||
id: parseInt( productId, 10 ), | ||
}; | ||
} | ||
); | ||
addFilter( | ||
'wcpay.payment-request.cart-add-item', | ||
'automattic/wcpay/payment-request', | ||
( productData ) => { | ||
const $variationsForm = jQuery( '.variations_form' ); | ||
if ( ! $variationsForm.length ) { | ||
return productData; | ||
} | ||
|
||
const attributes = []; | ||
const $variationSelectElements = $variationsForm.find( | ||
'.variations select' | ||
); | ||
$variationSelectElements.each( function () { | ||
const $select = jQuery( this ); | ||
const attributeName = | ||
$select.data( 'attribute_name' ) || $select.attr( 'name' ); | ||
|
||
attributes.push( { | ||
// 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 | ||
// It's an unfortunate hack that doesn't work when labels have special characters in them. | ||
attribute: document.querySelector( | ||
`label[for="${ attributeName.replace( | ||
'attribute_', | ||
'' | ||
) }"]` | ||
).innerHTML, | ||
value: $select.val() || '', | ||
} ); | ||
} ); | ||
|
||
return { | ||
...productData, | ||
variation: [ ...productData.variation, ...attributes ], | ||
}; | ||
} | ||
); |
Oops, something went wrong.