mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-06 18:16:38 +08:00
Add cart cleanup functionality to single page Bookable products
This commit is contained in:
parent
c00b5906f7
commit
3b5a4b7f23
5 changed files with 156 additions and 24 deletions
|
@ -73,6 +73,13 @@ class Item {
|
|||
*/
|
||||
protected $tax_rate;
|
||||
|
||||
/**
|
||||
* The tax rate.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $cart_item_key;
|
||||
|
||||
/**
|
||||
* Item constructor.
|
||||
*
|
||||
|
@ -84,6 +91,7 @@ class Item {
|
|||
* @param string $sku The SKU.
|
||||
* @param string $category The category.
|
||||
* @param float $tax_rate The tax rate.
|
||||
* @param ?string $cart_item_key The cart key for this item.
|
||||
*/
|
||||
public function __construct(
|
||||
string $name,
|
||||
|
@ -93,7 +101,8 @@ class Item {
|
|||
Money $tax = null,
|
||||
string $sku = '',
|
||||
string $category = 'PHYSICAL_GOODS',
|
||||
float $tax_rate = 0
|
||||
float $tax_rate = 0,
|
||||
string $cart_item_key = null
|
||||
) {
|
||||
|
||||
$this->name = $name;
|
||||
|
@ -105,6 +114,7 @@ class Item {
|
|||
$this->category = ( self::DIGITAL_GOODS === $category ) ? self::DIGITAL_GOODS : self::PHYSICAL_GOODS;
|
||||
$this->category = $category;
|
||||
$this->tax_rate = $tax_rate;
|
||||
$this->cart_item_key = $cart_item_key;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -179,6 +189,15 @@ class Item {
|
|||
return round( (float) $this->tax_rate, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cart key for this item.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function cart_item_key():?string {
|
||||
return $this->cart_item_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object as array.
|
||||
*
|
||||
|
@ -202,6 +221,10 @@ class Item {
|
|||
$item['tax_rate'] = (string) $this->tax_rate();
|
||||
}
|
||||
|
||||
if ( $this->cart_item_key() ) {
|
||||
$item['cart_item_key'] = (string) $this->cart_item_key();
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ class ItemFactory {
|
|||
$items = array_map(
|
||||
function ( array $item ): Item {
|
||||
$product = $item['data'];
|
||||
$cart_item_key = $item['key'] ?? null;
|
||||
|
||||
/**
|
||||
* The WooCommerce product.
|
||||
|
@ -61,7 +62,9 @@ class ItemFactory {
|
|||
$this->prepare_description( $product->get_description() ),
|
||||
null,
|
||||
$product->get_sku(),
|
||||
( $product->is_virtual() ) ? Item::DIGITAL_GOODS : Item::PHYSICAL_GOODS
|
||||
( $product->is_virtual() ) ? Item::DIGITAL_GOODS : Item::PHYSICAL_GOODS,
|
||||
0,
|
||||
$cart_item_key
|
||||
);
|
||||
},
|
||||
$cart->get_cart_contents()
|
||||
|
|
|
@ -3,6 +3,8 @@ import BookingProduct from "../Entity/BookingProduct";
|
|||
import onApprove from '../OnApproveHandler/onApproveForContinue';
|
||||
import {payerData} from "../Helper/PayerData";
|
||||
import {PaymentMethods} from "../Helper/CheckoutMethodState";
|
||||
import CartJanitor from "../Helper/CartJanitor";
|
||||
import FormHelper from "../Helper/FormHelper";
|
||||
|
||||
class SingleProductActionHandler {
|
||||
|
||||
|
@ -16,6 +18,7 @@ class SingleProductActionHandler {
|
|||
this.updateCart = updateCart;
|
||||
this.formElement = formElement;
|
||||
this.errorHandler = errorHandler;
|
||||
this.cartJanitor = null;
|
||||
}
|
||||
|
||||
subscriptionsConfiguration() {
|
||||
|
@ -74,29 +77,36 @@ class SingleProductActionHandler {
|
|||
createOrder: this.createOrder(),
|
||||
onApprove: onApprove(this, this.errorHandler),
|
||||
onError: (error) => {
|
||||
this.refreshMiniCart();
|
||||
|
||||
if (this.isBookingProduct() && error.message) {
|
||||
this.errorHandler.clear();
|
||||
this.errorHandler.message(error.message);
|
||||
return;
|
||||
}
|
||||
this.errorHandler.genericError();
|
||||
},
|
||||
onCancel: () => {
|
||||
// Could be used for every product type,
|
||||
// but only clean the cart for Booking products for now.
|
||||
if (this.isBookingProduct()) {
|
||||
this.cleanCart();
|
||||
} else {
|
||||
this.refreshMiniCart();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createOrder()
|
||||
{
|
||||
this.cartJanitor = null;
|
||||
|
||||
let getProducts = (() => {
|
||||
if ( this.isBookingProduct() ) {
|
||||
return () => {
|
||||
|
||||
const getPrefixedFields = (formElement, prefix) => {
|
||||
let fields = {};
|
||||
for(const element of formElement.elements) {
|
||||
if( element.name.startsWith(prefix) ) {
|
||||
fields[element.name] = element.value;
|
||||
}
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
const id = document.querySelector('[name="add-to-cart"]').value;
|
||||
return [new BookingProduct(id, 1, getPrefixedFields(this.formElement, "wc_bookings_field"))];
|
||||
return [new BookingProduct(id, 1, FormHelper.getPrefixedFields(this.formElement, "wc_bookings_field"))];
|
||||
}
|
||||
} else if ( this.isGroupedProduct() ) {
|
||||
return () => {
|
||||
|
@ -129,6 +139,8 @@ class SingleProductActionHandler {
|
|||
this.errorHandler.clear();
|
||||
|
||||
const onResolve = (purchase_units) => {
|
||||
this.cartJanitor = (new CartJanitor()).addFromPurchaseUnits(purchase_units);
|
||||
|
||||
const payer = payerData();
|
||||
const bnCode = typeof this.config.bn_codes[this.config.context] !== 'undefined' ?
|
||||
this.config.bn_codes[this.config.context] : '';
|
||||
|
@ -193,5 +205,17 @@ class SingleProductActionHandler {
|
|||
return !!this.formElement.querySelector('.wc-booking-product-id');
|
||||
}
|
||||
|
||||
cleanCart() {
|
||||
this.cartJanitor.removeFromCart().then(() => {
|
||||
this.refreshMiniCart();
|
||||
}).catch(error => {
|
||||
this.refreshMiniCart();
|
||||
});
|
||||
}
|
||||
|
||||
refreshMiniCart() {
|
||||
jQuery(document.body).trigger('wc_fragment_refresh');
|
||||
}
|
||||
|
||||
}
|
||||
export default SingleProductActionHandler;
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
class CartJanitor {
|
||||
|
||||
constructor(cartItemKeys = [])
|
||||
{
|
||||
this.endpoint = wc_cart_fragments_params.wc_ajax_url.toString().replace('%%endpoint%%', 'remove_from_cart');
|
||||
this.cartItemKeys = cartItemKeys;
|
||||
}
|
||||
|
||||
addFromPurchaseUnits(purchaseUnits) {
|
||||
for (const purchaseUnit of purchaseUnits || []) {
|
||||
for (const item of purchaseUnit.items || []) {
|
||||
if (!item.cart_item_key) {
|
||||
continue;
|
||||
}
|
||||
this.cartItemKeys.push(item.cart_item_key);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
removeFromCart()
|
||||
{
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.cartItemKeys || !this.cartItemKeys.length) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
const numRequests = this.cartItemKeys.length;
|
||||
let numResponses = 0;
|
||||
|
||||
const tryToResolve = () => {
|
||||
numResponses++;
|
||||
if (numResponses >= numRequests) {
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
|
||||
for (const cartItemKey of this.cartItemKeys) {
|
||||
const params = new URLSearchParams();
|
||||
params.append('cart_item_key', cartItemKey);
|
||||
|
||||
if (!cartItemKey) {
|
||||
tryToResolve();
|
||||
continue;
|
||||
}
|
||||
|
||||
fetch(this.endpoint, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
body: params
|
||||
}).then(function (res) {
|
||||
return res.json();
|
||||
}).then(() => {
|
||||
tryToResolve();
|
||||
}).catch(() => {
|
||||
tryToResolve();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default CartJanitor;
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
/**
|
||||
* Common Form utility methods
|
||||
*/
|
||||
export default class FormHelper {
|
||||
|
||||
static getPrefixedFields(formElement, prefix) {
|
||||
let fields = {};
|
||||
for(const element of formElement.elements) {
|
||||
if( element.name.startsWith(prefix) ) {
|
||||
fields[element.name] = element.value;
|
||||
}
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue