Add Support for product variants on ApplePay

This commit is contained in:
Pedro Silva 2023-10-25 17:52:00 +01:00
parent cb598ccbca
commit 3d293058fb
No known key found for this signature in database
GPG key ID: E2EE20C0669D24B3
11 changed files with 519 additions and 246 deletions

View file

@ -10,6 +10,7 @@ namespace WooCommerce\PayPalCommerce\Button\Endpoint;
use Exception;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\Button\Helper\CartProductsHelper;
/**
* Abstract Class AbstractCartEndpoint
@ -26,11 +27,11 @@ abstract class AbstractCartEndpoint implements EndpointInterface {
protected $cart;
/**
* The product data store.
* The cart products helper.
*
* @var \WC_Data_Store
* @var CartProductsHelper
*/
protected $product_data_store;
protected $cart_products;
/**
* The request data helper.
@ -53,13 +54,6 @@ abstract class AbstractCartEndpoint implements EndpointInterface {
*/
protected $logger_tag = '';
/**
* The added cart item IDs
*
* @var array
*/
private $cart_item_keys = array();
/**
* The nonce.
*
@ -110,44 +104,13 @@ abstract class AbstractCartEndpoint implements EndpointInterface {
protected function add_products( array $products ): bool {
$this->cart->empty_cart( false );
$success = true;
foreach ( $products as $product ) {
// Add extras to POST, they are usually added by custom plugins.
if ( $product['extra'] && is_array( $product['extra'] ) ) {
// Handle cases like field[].
$query = http_build_query( $product['extra'] );
parse_str( $query, $extra );
foreach ( $extra as $key => $value ) {
$_POST[ $key ] = $value;
}
}
if ( $product['product']->is_type( 'booking' ) ) {
$success = $success && $this->add_booking_product(
$product['product'],
$product['booking']
);
} elseif ( $product['product']->is_type( 'variable' ) ) {
$success = $success && $this->add_variable_product(
$product['product'],
$product['quantity'],
$product['variations']
);
} else {
$success = $success && $this->add_product(
$product['product'],
$product['quantity']
);
}
}
if ( ! $success ) {
try {
$this->cart_products->add_products( $products );
} catch ( Exception $e ) {
$this->handle_error();
}
return $success;
return true;
}
/**
@ -193,7 +156,7 @@ abstract class AbstractCartEndpoint implements EndpointInterface {
*/
protected function products_from_request() {
$data = $this->request_data->read_request( $this->nonce() );
$products = $this->products_from_data( $data );
$products = $this->cart_products->products_from_data( $data );
if ( ! $products ) {
wp_send_json_error(
array(
@ -212,141 +175,12 @@ abstract class AbstractCartEndpoint implements EndpointInterface {
return $products;
}
/**
* Returns product information from a data array.
*
* @param array $data The data array.
*
* @return array|null
*/
protected function products_from_data( array $data ): ?array {
$products = array();
if (
! isset( $data['products'] )
|| ! is_array( $data['products'] )
) {
return null;
}
foreach ( $data['products'] as $product ) {
if ( ! isset( $product['quantity'] ) || ! isset( $product['id'] ) ) {
return null;
}
$wc_product = wc_get_product( (int) $product['id'] );
if ( ! $wc_product ) {
return null;
}
$products[] = array(
'product' => $wc_product,
'quantity' => (int) $product['quantity'],
'variations' => $product['variations'] ?? null,
'booking' => $product['booking'] ?? null,
'extra' => $product['extra'] ?? null,
);
}
return $products;
}
/**
* Adds a product to the cart.
*
* @param \WC_Product $product The Product.
* @param int $quantity The Quantity.
*
* @return bool
* @throws Exception When product could not be added.
*/
private function add_product( \WC_Product $product, int $quantity ): bool {
$cart_item_key = $this->cart->add_to_cart( $product->get_id(), $quantity );
if ( $cart_item_key ) {
$this->cart_item_keys[] = $cart_item_key;
}
return false !== $cart_item_key;
}
/**
* Adds variations to the cart.
*
* @param \WC_Product $product The Product.
* @param int $quantity The Quantity.
* @param array $post_variations The variations.
*
* @return bool
* @throws Exception When product could not be added.
*/
private function add_variable_product(
\WC_Product $product,
int $quantity,
array $post_variations
): bool {
$variations = array();
foreach ( $post_variations as $key => $value ) {
$variations[ $value['name'] ] = $value['value'];
}
$variation_id = $this->product_data_store->find_matching_product_variation( $product, $variations );
// ToDo: Check stock status for variation.
$cart_item_key = $this->cart->add_to_cart(
$product->get_id(),
$quantity,
$variation_id,
$variations
);
if ( $cart_item_key ) {
$this->cart_item_keys[] = $cart_item_key;
}
return false !== $cart_item_key;
}
/**
* Adds booking to the cart.
*
* @param \WC_Product $product The Product.
* @param array $data Data used by the booking plugin.
*
* @return bool
* @throws Exception When product could not be added.
*/
private function add_booking_product(
\WC_Product $product,
array $data
): bool {
if ( ! is_callable( 'wc_bookings_get_posted_data' ) ) {
return false;
}
$cart_item_data = array(
'booking' => wc_bookings_get_posted_data( $data, $product ),
);
$cart_item_key = $this->cart->add_to_cart( $product->get_id(), 1, 0, array(), $cart_item_data );
if ( $cart_item_key ) {
$this->cart_item_keys[] = $cart_item_key;
}
return false !== $cart_item_key;
}
/**
* Removes stored cart items from WooCommerce cart.
*
* @return void
*/
protected function remove_cart_items(): void {
foreach ( $this->cart_item_keys as $cart_item_key ) {
if ( ! $cart_item_key ) {
continue;
}
$this->cart->remove_cart_item( $cart_item_key );
}
$this->cart_products->remove_cart_items();
}
}

View file

@ -12,6 +12,7 @@ namespace WooCommerce\PayPalCommerce\Button\Endpoint;
use Exception;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use WooCommerce\PayPalCommerce\Button\Helper\CartProductsHelper;
/**
* Class ChangeCartEndpoint
@ -41,7 +42,7 @@ class ChangeCartEndpoint extends AbstractCartEndpoint {
* @param \WC_Shipping $shipping The current WC shipping object.
* @param RequestData $request_data The request data helper.
* @param PurchaseUnitFactory $purchase_unit_factory The PurchaseUnit factory.
* @param \WC_Data_Store $product_data_store The data store for products.
* @param CartProductsHelper $cart_products The cart products helper.
* @param LoggerInterface $logger The logger.
*/
public function __construct(
@ -49,7 +50,7 @@ class ChangeCartEndpoint extends AbstractCartEndpoint {
\WC_Shipping $shipping,
RequestData $request_data,
PurchaseUnitFactory $purchase_unit_factory,
\WC_Data_Store $product_data_store,
CartProductsHelper $cart_products,
LoggerInterface $logger
) {
@ -57,7 +58,7 @@ class ChangeCartEndpoint extends AbstractCartEndpoint {
$this->shipping = $shipping;
$this->request_data = $request_data;
$this->purchase_unit_factory = $purchase_unit_factory;
$this->product_data_store = $product_data_store;
$this->cart_products = $cart_products;
$this->logger = $logger;
$this->logger_tag = 'updating';
@ -70,6 +71,8 @@ class ChangeCartEndpoint extends AbstractCartEndpoint {
* @throws Exception On error.
*/
protected function handle_data(): bool {
$this->cart_products->set_cart( $this->cart );
$products = $this->products_from_request();
if ( ! $products ) {

View file

@ -13,6 +13,7 @@ use Exception;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Money;
use WooCommerce\PayPalCommerce\Button\Assets\SmartButton;
use WooCommerce\PayPalCommerce\Button\Helper\CartProductsHelper;
/**
* Class SimulateCartEndpoint
@ -38,23 +39,23 @@ class SimulateCartEndpoint extends AbstractCartEndpoint {
/**
* ChangeCartEndpoint constructor.
*
* @param SmartButton $smart_button The SmartButton.
* @param \WC_Cart $cart The current WC cart object.
* @param RequestData $request_data The request data helper.
* @param \WC_Data_Store $product_data_store The data store for products.
* @param LoggerInterface $logger The logger.
* @param SmartButton $smart_button The SmartButton.
* @param \WC_Cart $cart The current WC cart object.
* @param RequestData $request_data The request data helper.
* @param CartProductsHelper $cart_products The cart products helper.
* @param LoggerInterface $logger The logger.
*/
public function __construct(
SmartButton $smart_button,
\WC_Cart $cart,
RequestData $request_data,
\WC_Data_Store $product_data_store,
CartProductsHelper $cart_products,
LoggerInterface $logger
) {
$this->smart_button = $smart_button;
$this->cart = clone $cart;
$this->request_data = $request_data;
$this->product_data_store = $product_data_store;
$this->cart_products = $cart_products;
$this->logger = $logger;
$this->logger_tag = 'simulation';
@ -147,6 +148,7 @@ class SimulateCartEndpoint extends AbstractCartEndpoint {
// Store a reference to the real cart.
$this->real_cart = WC()->cart;
WC()->cart = $this->cart;
$this->cart_products->set_cart( $this->cart );
}
/**

View file

@ -0,0 +1,286 @@
<?php
/**
* Handles the adding of products to WooCommerce cart.
*
* @package WooCommerce\PayPalCommerce\Button\Helper
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Button\Helper;
use Exception;
use WC_Cart;
use WC_Data_Store;
/**
* Class CartProductsHelper
*/
class CartProductsHelper {
/**
* The cart
*
* @var ?WC_Cart
*/
private $cart;
/**
* The product data store.
*
* @var WC_Data_Store
*/
protected $product_data_store;
/**
* The added cart item IDs
*
* @var array
*/
private $cart_item_keys = array();
/**
* CheckoutFormSaver constructor.
*
* @param WC_Data_Store $product_data_store The data store for products.
*/
public function __construct(
WC_Data_Store $product_data_store
) {
$this->product_data_store = $product_data_store;
}
/**
* Sets a new cart instance.
*
* @param WC_Cart $cart
* @return void
*/
public function set_cart( WC_Cart $cart ): void {
$this->cart = $cart;
}
/**
* Returns products information from a data array.
*
* @param array $data The data array.
*
* @return array|null
*/
public function products_from_data( array $data ): ?array {
$products = array();
if (
! isset( $data['products'] )
|| ! is_array( $data['products'] )
) {
return null;
}
foreach ( $data['products'] as $product ) {
$product = $this->products_from_data( $product );
if ( $product ) {
$products[] = $product;
}
}
return $products;
}
/**
* Returns product information from a data array.
*
* @param array $product
* @return array|null
*/
public function product_from_data( array $product ): ?array {
if ( ! isset( $product['quantity'] ) || ! isset( $product['id'] ) ) {
return null;
}
$wc_product = wc_get_product( (int) $product['id'] );
if ( ! $wc_product ) {
return null;
}
return array(
'product' => $wc_product,
'quantity' => (int) $product['quantity'],
'variations' => $product['variations'] ?? null,
'booking' => $product['booking'] ?? null,
'extra' => $product['extra'] ?? null,
);
}
/**
* Adds products to cart.
*
* @param array $products Array of products to be added to cart.
* @return bool
* @throws Exception Add to cart methods throw an exception on fail.
*/
public function add_products( array $products ): bool {
$success = true;
foreach ( $products as $product ) {
// Add extras to POST, they are usually added by custom plugins.
if ( $product['extra'] && is_array( $product['extra'] ) ) {
// Handle cases like field[].
$query = http_build_query( $product['extra'] );
parse_str( $query, $extra );
foreach ( $extra as $key => $value ) {
$_POST[ $key ] = $value;
}
}
if ( $product['product']->is_type( 'booking' ) ) {
$success = $success && $this->add_booking_product(
$product['product'],
$product['booking']
);
} elseif ( $product['product']->is_type( 'variable' ) ) {
$success = $success && $this->add_variable_product(
$product['product'],
$product['quantity'],
$product['variations']
);
} else {
$success = $success && $this->add_product(
$product['product'],
$product['quantity']
);
}
}
if ( ! $success ) {
throw new Exception( 'Error adding products to cart.' );
}
return true;
}
/**
* Adds a product to the cart.
*
* @param \WC_Product $product The Product.
* @param int $quantity The Quantity.
*
* @return bool
* @throws Exception When product could not be added.
*/
public function add_product( \WC_Product $product, int $quantity ): bool {
$this->validate_cart();
$cart_item_key = $this->cart->add_to_cart( $product->get_id(), $quantity );
if ( $cart_item_key ) {
$this->cart_item_keys[] = $cart_item_key;
}
return false !== $cart_item_key;
}
/**
* Adds variations to the cart.
*
* @param \WC_Product $product The Product.
* @param int $quantity The Quantity.
* @param array $post_variations The variations.
*
* @return bool
* @throws Exception When product could not be added.
*/
public function add_variable_product(
\WC_Product $product,
int $quantity,
array $post_variations
): bool {
$this->validate_cart();
$variations = array();
foreach ( $post_variations as $key => $value ) {
$variations[ $value['name'] ] = $value['value'];
}
$variation_id = $this->product_data_store->find_matching_product_variation( $product, $variations );
// ToDo: Check stock status for variation.
$cart_item_key = $this->cart->add_to_cart(
$product->get_id(),
$quantity,
$variation_id,
$variations
);
if ( $cart_item_key ) {
$this->cart_item_keys[] = $cart_item_key;
}
return false !== $cart_item_key;
}
/**
* Adds booking to the cart.
*
* @param \WC_Product $product The Product.
* @param array $data Data used by the booking plugin.
*
* @return bool
* @throws Exception When product could not be added.
*/
public function add_booking_product(
\WC_Product $product,
array $data
): bool {
$this->validate_cart();
if ( ! is_callable( 'wc_bookings_get_posted_data' ) ) {
return false;
}
$cart_item_data = array(
'booking' => wc_bookings_get_posted_data( $data, $product ),
);
$cart_item_key = $this->cart->add_to_cart( $product->get_id(), 1, 0, array(), $cart_item_data );
if ( $cart_item_key ) {
$this->cart_item_keys[] = $cart_item_key;
}
return false !== $cart_item_key;
}
/**
* Removes stored cart items from WooCommerce cart.
*
* @return void
* @throws Exception
*/
public function remove_cart_items(): void {
$this->validate_cart();
foreach ( $this->cart_item_keys as $cart_item_key ) {
if ( ! $cart_item_key ) {
continue;
}
$this->cart->remove_cart_item( $cart_item_key );
}
}
/**
* @return array
*/
public function cart_item_keys(): array {
return $this->cart_item_keys;
}
/**
* Throws the cart not set exception.
*
* @return void
* @throws Exception
*/
private function validate_cart(): void {
if ( ! $this->cart ) {
throw new Exception( 'Cart not set.' );
}
}
}