Add cart cleanup functionality to single page Bookable products

This commit is contained in:
Pedro Silva 2023-07-05 17:10:26 +01:00
parent c00b5906f7
commit 3b5a4b7f23
No known key found for this signature in database
GPG key ID: E2EE20C0669D24B3
5 changed files with 156 additions and 24 deletions

View file

@ -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;
}
}

View file

@ -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()

View file

@ -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;

View file

@ -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;

View file

@ -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;
}
}