diff --git a/changelog.txt b/changelog.txt
index 5bb4c9623..a840377a9 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,6 +1,6 @@
*** Changelog ***
-= 2.4.0 - xxxx-xx-xx =
+= 2.4.0 - 2023-10-31 =
* Fix - Mini-Cart Bug cause of wrong DOM-Structure in v2.3.1 #1735
* Fix - ACDC disappearing after plugin updates #1751
* Fix - Subscription module hooks #1748
diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php
index 7cf82b61c..e04fbba5f 100644
--- a/modules/ppcp-api-client/services.php
+++ b/modules/ppcp-api-client/services.php
@@ -606,6 +606,37 @@ return array(
'SGD',
'USD',
),
+ 'BE' => array(
+ 'EUR',
+ 'USD',
+ 'CAD',
+ 'GBP',
+ 'PLN',
+ 'SEK',
+ 'CHF',
+ ),
+ 'BG' => array(
+ 'EUR',
+ 'USD',
+ ),
+ 'CY' => array(
+ 'EUR',
+ 'USD',
+ 'CAD',
+ 'GBP',
+ 'AUD',
+ 'CZK',
+ 'DKK',
+ 'NOK',
+ 'PLN',
+ 'SEK',
+ 'CHF',
+ ),
+ 'CZ' => array(
+ 'EUR',
+ 'USD',
+ 'CZK',
+ ),
'DE' => array(
'AUD',
'CAD',
@@ -624,6 +655,16 @@ return array(
'SGD',
'USD',
),
+ 'DK' => array(
+ 'EUR',
+ 'USD',
+ 'DKK',
+ 'NOK',
+ ),
+ 'EE' => array(
+ 'EUR',
+ 'USD',
+ ),
'ES' => array(
'AUD',
'CAD',
@@ -642,6 +683,10 @@ return array(
'SGD',
'USD',
),
+ 'FI' => array(
+ 'EUR',
+ 'USD',
+ ),
'FR' => array(
'AUD',
'CAD',
@@ -678,6 +723,16 @@ return array(
'SGD',
'USD',
),
+ 'GR' => array(
+ 'EUR',
+ 'USD',
+ 'GBP',
+ ),
+ 'HU' => array(
+ 'EUR',
+ 'USD',
+ 'HUF',
+ ),
'IT' => array(
'AUD',
'CAD',
@@ -696,6 +751,32 @@ return array(
'SGD',
'USD',
),
+ 'LT' => array(
+ 'EUR',
+ 'USD',
+ 'CAD',
+ 'GBP',
+ 'JPY',
+ 'AUD',
+ 'CZK',
+ 'DKK',
+ 'HUF',
+ 'PLN',
+ 'SEK',
+ 'CHF',
+ 'NZD',
+ 'NOK',
+ ),
+ 'LU' => array(
+ 'EUR',
+ 'USD',
+ ),
+ 'LV' => array(
+ 'EUR',
+ 'USD',
+ 'CAD',
+ 'GBP',
+ ),
'US' => array(
'AUD',
'CAD',
@@ -722,9 +803,81 @@ return array(
'SGD',
'USD',
),
+ 'MT' => array(
+ 'EUR',
+ 'USD',
+ 'CAD',
+ 'GBP',
+ 'JPY',
+ 'AUD',
+ 'CZK',
+ 'DKK',
+ 'HUF',
+ 'NOK',
+ 'PLN',
+ 'SEK',
+ 'CHF',
+ ),
'MX' => array(
'MXN',
),
+ 'NL' => array(
+ 'EUR',
+ 'GBP',
+ 'AUD',
+ 'CZK',
+ 'HUF',
+ 'CHF',
+ 'CAD',
+ 'USD',
+ ),
+ 'NO' => array(
+ 'EUR',
+ 'USD',
+ 'CAD',
+ 'GBP',
+ 'NOK',
+ ),
+ 'PL' => array(
+ 'EUR',
+ 'USD',
+ 'CAD',
+ 'GBP',
+ 'AUD',
+ 'DKK',
+ 'PLN',
+ 'SEK',
+ 'CZK',
+ ),
+ 'PT' => array(
+ 'EUR',
+ 'USD',
+ 'CAD',
+ 'GBP',
+ 'CZK',
+ ),
+ 'RO' => array(
+ 'EUR',
+ 'USD',
+ 'GBP',
+ ),
+ 'SE' => array(
+ 'EUR',
+ 'USD',
+ 'NOK',
+ 'SEK',
+ ),
+ 'SI' => array(
+ 'EUR',
+ 'USD',
+ ),
+ 'SK' => array(
+ 'EUR',
+ 'USD',
+ 'GBP',
+ 'CZK',
+ 'HUF',
+ ),
'JP' => array(
'AUD',
'CAD',
@@ -762,16 +915,51 @@ return array(
'visa' => array(),
'amex' => array( 'AUD' ),
),
+ 'BE' => array(
+ 'mastercard' => array(),
+ 'visa' => array(),
+ 'amex' => array( 'EUR', 'USD', 'CAD' ),
+ ),
+ 'BG' => array(
+ 'mastercard' => array(),
+ 'visa' => array(),
+ 'amex' => array( 'EUR' ),
+ ),
+ 'CY' => array(
+ 'mastercard' => array(),
+ 'visa' => array(),
+ 'amex' => array( 'EUR' ),
+ ),
+ 'CZ' => array(
+ 'mastercard' => array(),
+ 'visa' => array(),
+ 'amex' => array( 'CZK' ),
+ ),
'DE' => array(
'mastercard' => array(),
'visa' => array(),
'amex' => array( 'EUR' ),
),
+ 'DK' => array(
+ 'mastercard' => array(),
+ 'visa' => array(),
+ 'amex' => array( 'DKK' ),
+ ),
+ 'EE' => array(
+ 'mastercard' => array(),
+ 'visa' => array(),
+ 'amex' => array(),
+ ),
'ES' => array(
'mastercard' => array(),
'visa' => array(),
'amex' => array( 'EUR' ),
),
+ 'FI' => array(
+ 'mastercard' => array(),
+ 'visa' => array(),
+ 'amex' => array( 'EUR' ),
+ ),
'FR' => array(
'mastercard' => array(),
'visa' => array(),
@@ -782,6 +970,16 @@ return array(
'visa' => array(),
'amex' => array( 'GBP', 'USD' ),
),
+ 'GR' => array(
+ 'mastercard' => array(),
+ 'visa' => array(),
+ 'amex' => array( 'EUR' ),
+ ),
+ 'HU' => array(
+ 'mastercard' => array(),
+ 'visa' => array(),
+ 'amex' => array( 'HUF' ),
+ ),
'IT' => array(
'mastercard' => array(),
'visa' => array(),
@@ -799,11 +997,71 @@ return array(
'amex' => array( 'CAD' ),
'jcb' => array( 'CAD' ),
),
+ 'LT' => array(
+ 'mastercard' => array(),
+ 'visa' => array(),
+ 'amex' => array( 'EUR' ),
+ ),
+ 'LU' => array(
+ 'mastercard' => array(),
+ 'visa' => array(),
+ 'amex' => array( 'EUR' ),
+ ),
+ 'LV' => array(
+ 'mastercard' => array(),
+ 'visa' => array(),
+ 'amex' => array( 'EUR', 'USD' ),
+ ),
+ 'MT' => array(
+ 'mastercard' => array(),
+ 'visa' => array(),
+ 'amex' => array( 'EUR' ),
+ ),
'MX' => array(
'mastercard' => array(),
'visa' => array(),
'amex' => array(),
),
+ 'NL' => array(
+ 'mastercard' => array(),
+ 'visa' => array(),
+ 'amex' => array( 'EUR', 'USD' ),
+ ),
+ 'NO' => array(
+ 'mastercard' => array(),
+ 'visa' => array(),
+ 'amex' => array( 'NOK' ),
+ ),
+ 'PL' => array(
+ 'mastercard' => array(),
+ 'visa' => array(),
+ 'amex' => array( 'EUR', 'USD', 'GBP', 'PLN' ),
+ ),
+ 'PT' => array(
+ 'mastercard' => array(),
+ 'visa' => array(),
+ 'amex' => array( 'EUR', 'USD', 'CAD', 'GBP' ),
+ ),
+ 'RO' => array(
+ 'mastercard' => array(),
+ 'visa' => array(),
+ 'amex' => array( 'EUR', 'USD' ),
+ ),
+ 'SE' => array(
+ 'mastercard' => array(),
+ 'visa' => array(),
+ 'amex' => array( 'EUR', 'SEK' ),
+ ),
+ 'SI' => array(
+ 'mastercard' => array(),
+ 'visa' => array(),
+ 'amex' => array( 'EUR' ),
+ ),
+ 'SK' => array(
+ 'mastercard' => array(),
+ 'visa' => array(),
+ 'amex' => array( 'EUR', 'GBP' ),
+ ),
'JP' => array(
'mastercard' => array(),
'visa' => array(),
diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js
index 15615d7ee..19c03c5d2 100644
--- a/modules/ppcp-applepay/resources/js/ApplepayButton.js
+++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js
@@ -23,18 +23,17 @@ class ApplepayButton {
this.ppcpConfig
);
- //PRODUCT DETAIL PAGE
- this.refreshContextData();
-
this.updated_contact_info = []
this.selectedShippingMethod = []
- this.nonce = document.getElementById('woocommerce-process-checkout-nonce').value
+ this.nonce = document.getElementById('woocommerce-process-checkout-nonce')?.value
this.log = function() {
if ( this.buttonConfig.is_debug ) {
console.log('[ApplePayButton]', ...arguments);
}
}
+
+ this.refreshContextData();
}
init(config) {
@@ -260,6 +259,8 @@ class ApplepayButton {
case 'product':
// Refresh product data that makes the price change.
this.productQuantity = document.querySelector('input.qty').value;
+ this.products = this.contextHandler.products();
+ this.log('Products updated', this.products);
break;
}
}
@@ -389,6 +390,7 @@ class ApplepayButton {
return {
action: 'ppcp_update_shipping_contact',
product_id: product_id,
+ products: JSON.stringify(this.products),
caller_page: 'productDetail',
product_quantity: this.productQuantity,
simplified_contact: event.shippingContact,
@@ -419,6 +421,7 @@ class ApplepayButton {
action: 'ppcp_update_shipping_method',
shipping_method: event.shippingMethod,
product_id: product_id,
+ products: JSON.stringify(this.products),
caller_page: 'productDetail',
product_quantity: this.productQuantity,
simplified_contact: this.updated_contact_info,
@@ -456,6 +459,7 @@ class ApplepayButton {
action: 'ppcp_create_order',
'caller_page': this.context,
'product_id': this.buttonConfig.product.id ?? null,
+ 'products': JSON.stringify(this.products),
'product_quantity': this.productQuantity ?? null,
'shipping_contact': shippingContact,
'billing_contact': billingContact,
diff --git a/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js b/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js
index ddb7ad531..f9adb7774 100644
--- a/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js
+++ b/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js
@@ -49,12 +49,20 @@ class SingleProductHandler extends BaseHandler {
}
createOrder() {
+ return this.actionHandler().configuration().createOrder();
+ }
+
+ products() {
+ return this.actionHandler().getProducts();
+ }
+
+ actionHandler() {
const errorHandler = new ErrorHandler(
this.ppcpConfig.labels.error.generic,
document.querySelector('.woocommerce-notices-wrapper')
);
- const actionHandler = new SingleProductActionHandler(
+ return new SingleProductActionHandler(
this.ppcpConfig,
new UpdateCart(
this.ppcpConfig.ajax.change_cart.endpoint,
@@ -63,8 +71,6 @@ class SingleProductHandler extends BaseHandler {
document.querySelector('form.cart'),
errorHandler,
);
-
- return actionHandler.configuration().createOrder();
}
}
diff --git a/modules/ppcp-applepay/services.php b/modules/ppcp-applepay/services.php
index 919f6a25b..a49ab7fad 100644
--- a/modules/ppcp-applepay/services.php
+++ b/modules/ppcp-applepay/services.php
@@ -100,7 +100,6 @@ return array(
return new DataToAppleButtonScripts( $container->get( 'applepay.sdk_script_url' ), $container->get( 'wcgateway.settings' ) );
},
'applepay.button' => static function ( ContainerInterface $container ): ApplePayButton {
-
return new ApplePayButton(
$container->get( 'wcgateway.settings' ),
$container->get( 'woocommerce.logger.woocommerce' ),
@@ -108,7 +107,8 @@ return array(
$container->get( 'applepay.url' ),
$container->get( 'ppcp.asset-version' ),
$container->get( 'applepay.data_to_scripts' ),
- $container->get( 'wcgateway.settings.status' )
+ $container->get( 'wcgateway.settings.status' ),
+ $container->get( 'button.helper.cart-products' )
);
},
'applepay.blocks-payment-method' => static function ( ContainerInterface $container ): PaymentMethodTypeInterface {
diff --git a/modules/ppcp-applepay/src/ApplepayModule.php b/modules/ppcp-applepay/src/ApplepayModule.php
index c63750fbc..b81329811 100644
--- a/modules/ppcp-applepay/src/ApplepayModule.php
+++ b/modules/ppcp-applepay/src/ApplepayModule.php
@@ -12,6 +12,7 @@ namespace WooCommerce\PayPalCommerce\Applepay;
use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry;
use WooCommerce\PayPalCommerce\Applepay\Assets\ApplePayButton;
use WooCommerce\PayPalCommerce\Applepay\Assets\AppleProductStatus;
+use WooCommerce\PayPalCommerce\Applepay\Assets\PropertiesDictionary;
use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface;
use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface;
use WooCommerce\PayPalCommerce\Applepay\Helper\AvailabilityNotice;
@@ -40,6 +41,7 @@ class ApplepayModule implements ModuleInterface {
* {@inheritDoc}
*/
public function run( ContainerInterface $c ): void {
+ $module = $this;
// Clears product status when appropriate.
add_action(
@@ -51,38 +53,65 @@ class ApplepayModule implements ModuleInterface {
}
);
- // Check if the module is applicable, correct country, currency, ... etc.
- if ( ! $c->get( 'applepay.eligible' ) ) {
- return;
- }
+ add_action(
+ 'init',
+ static function () use ( $c, $module ) {
- // Load the button handler.
- $apple_payment_method = $c->get( 'applepay.button' );
- // add onboarding and referrals hooks.
- assert( $apple_payment_method instanceof ApplepayButton );
- $apple_payment_method->initialize();
+ // Check if the module is applicable, correct country, currency, ... etc.
+ if ( ! $c->get( 'applepay.eligible' ) ) {
+ return;
+ }
- // Show notice if there are product availability issues.
- $availability_notice = $c->get( 'applepay.availability_notice' );
- assert( $availability_notice instanceof AvailabilityNotice );
- $availability_notice->execute();
+ // Load the button handler.
+ $apple_payment_method = $c->get( 'applepay.button' );
+ // add onboarding and referrals hooks.
+ assert( $apple_payment_method instanceof ApplepayButton );
+ $apple_payment_method->initialize();
- // Return if server not supported.
- if ( ! $c->get( 'applepay.server_supported' ) ) {
- return;
- }
+ // Show notice if there are product availability issues.
+ $availability_notice = $c->get( 'applepay.availability_notice' );
+ assert( $availability_notice instanceof AvailabilityNotice );
+ $availability_notice->execute();
- // Check if this merchant can activate / use the buttons.
- // We allow non referral merchants as they can potentially still use ApplePay, we just have no way of checking the capability.
- if ( ( ! $c->get( 'applepay.available' ) ) && $c->get( 'applepay.is_referral' ) ) {
- return;
- }
+ // Return if server not supported.
+ if ( ! $c->get( 'applepay.server_supported' ) ) {
+ return;
+ }
- $this->load_assets( $c, $apple_payment_method );
- $this->handle_validation_file( $c );
- $this->render_buttons( $c, $apple_payment_method );
+ // Check if this merchant can activate / use the buttons.
+ // We allow non referral merchants as they can potentially still use ApplePay, we just have no way of checking the capability.
+ if ( ( ! $c->get( 'applepay.available' ) ) && $c->get( 'applepay.is_referral' ) ) {
+ return;
+ }
- $apple_payment_method->bootstrap_ajax_request();
+ $module->load_assets( $c, $apple_payment_method );
+ $module->handle_validation_file( $c );
+ $module->render_buttons( $c, $apple_payment_method );
+
+ $apple_payment_method->bootstrap_ajax_request();
+ }
+ );
+
+ add_filter(
+ 'nonce_user_logged_out',
+ /**
+ * Prevents nonce from being changed for non logged in users.
+ *
+ * @param int $uid The uid.
+ * @param string|int $action The action.
+ * @return int
+ *
+ * @psalm-suppress MissingClosureParamType
+ */
+ function ( $uid, $action ) {
+ if ( $action === PropertiesDictionary::NONCE_ACTION ) {
+ return 0;
+ }
+ return $uid;
+ },
+ 100,
+ 2
+ );
}
/**
diff --git a/modules/ppcp-applepay/src/Assets/ApplePayButton.php b/modules/ppcp-applepay/src/Assets/ApplePayButton.php
index 693c5bf04..fe7313a85 100644
--- a/modules/ppcp-applepay/src/Assets/ApplePayButton.php
+++ b/modules/ppcp-applepay/src/Assets/ApplePayButton.php
@@ -14,6 +14,7 @@ use Psr\Log\LoggerInterface;
use WC_Cart;
use WC_Order;
use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface;
+use WooCommerce\PayPalCommerce\Button\Helper\CartProductsHelper;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
@@ -25,18 +26,21 @@ use WooCommerce\PayPalCommerce\Webhooks\Handler\RequestHandlerTrait;
*/
class ApplePayButton implements ButtonInterface {
use RequestHandlerTrait;
+
/**
* The settings.
*
* @var Settings
*/
private $settings;
+
/**
* The logger.
*
* @var LoggerInterface
*/
private $logger;
+
/**
* The response templates.
*
@@ -58,12 +62,14 @@ class ApplePayButton implements ButtonInterface {
* @var string
*/
protected $id;
+
/**
* The method title.
*
* @var string
*/
protected $method_title;
+
/**
* The processor for orders.
*
@@ -84,18 +90,21 @@ class ApplePayButton implements ButtonInterface {
* @var string
*/
private $version;
+
/**
* The module URL.
*
* @var string
*/
private $module_url;
+
/**
* The data to send to the ApplePay button script.
*
* @var DataToAppleButtonScripts
*/
private $script_data;
+
/**
* The Settings status helper.
*
@@ -103,6 +112,13 @@ class ApplePayButton implements ButtonInterface {
*/
private $settings_status;
+ /**
+ * The cart products helper.
+ *
+ * @var CartProductsHelper
+ */
+ protected $cart_products;
+
/**
* PayPalPaymentMethod constructor.
*
@@ -113,6 +129,7 @@ class ApplePayButton implements ButtonInterface {
* @param string $version The module version.
* @param DataToAppleButtonScripts $data The data to send to the ApplePay button script.
* @param SettingsStatus $settings_status The settings status helper.
+ * @param CartProductsHelper $cart_products The cart products helper.
*/
public function __construct(
Settings $settings,
@@ -121,7 +138,8 @@ class ApplePayButton implements ButtonInterface {
string $module_url,
string $version,
DataToAppleButtonScripts $data,
- SettingsStatus $settings_status
+ SettingsStatus $settings_status,
+ CartProductsHelper $cart_products
) {
$this->settings = $settings;
$this->response_templates = new ResponsesToApple();
@@ -133,6 +151,7 @@ class ApplePayButton implements ButtonInterface {
$this->version = $version;
$this->script_data = $data;
$this->settings_status = $settings_status;
+ $this->cart_products = $cart_products;
}
/**
@@ -822,15 +841,24 @@ class ApplePayButton implements ButtonInterface {
*
* @param ApplePayDataObjectHttp $applepay_request_data_object The request data object.
* @return bool | string The cart item key after adding to the new cart.
- * @throws \Exception If cannot be added to cart.
+ * @throws \Exception If it cannot be added to cart.
*/
public function prepare_cart( ApplePayDataObjectHttp $applepay_request_data_object ): string {
$this->save_old_cart();
- $cart = WC()->cart;
- return $cart->add_to_cart(
- (int) $applepay_request_data_object->product_id(),
- (int) $applepay_request_data_object->product_quantity()
+ $this->cart_products->set_cart( WC()->cart );
+
+ $product = $this->cart_products->product_from_data(
+ array(
+ 'id' => (int) $applepay_request_data_object->product_id(),
+ 'quantity' => (int) $applepay_request_data_object->product_quantity(),
+ 'variations' => $applepay_request_data_object->product_variations(),
+ 'extra' => $applepay_request_data_object->product_extra(),
+ 'booking' => $applepay_request_data_object->product_booking(),
+ )
);
+
+ $this->cart_products->add_products( array( $product ) );
+ return $this->cart_products->cart_item_keys()[0];
}
/**
diff --git a/modules/ppcp-applepay/src/Assets/ApplePayDataObjectHttp.php b/modules/ppcp-applepay/src/Assets/ApplePayDataObjectHttp.php
index ddc05a9fe..219e3c6d9 100644
--- a/modules/ppcp-applepay/src/Assets/ApplePayDataObjectHttp.php
+++ b/modules/ppcp-applepay/src/Assets/ApplePayDataObjectHttp.php
@@ -38,13 +38,6 @@ class ApplePayDataObjectHttp {
*/
protected $need_shipping;
- /**
- * The product id.
- *
- * @var mixed
- */
- protected $product_id = '';
-
/**
* The caller page.
*
@@ -52,6 +45,13 @@ class ApplePayDataObjectHttp {
*/
protected $caller_page;
+ /**
+ * The product id.
+ *
+ * @var mixed
+ */
+ protected $product_id = '';
+
/**
* The product quantity.
*
@@ -59,6 +59,27 @@ class ApplePayDataObjectHttp {
*/
protected $product_quantity = '';
+ /**
+ * The product variations.
+ *
+ * @var array
+ */
+ protected $product_variations = array();
+
+ /**
+ * The product extra.
+ *
+ * @var array
+ */
+ protected $product_extra = array();
+
+ /**
+ * The product booking.
+ *
+ * @var array
+ */
+ protected $product_booking = array();
+
/**
* The shipping methods.
*
@@ -166,6 +187,9 @@ class ApplePayDataObjectHttp {
if ( ! $data ) {
return;
}
+
+ $data = $this->preprocess_request_data( $data );
+
$result = $this->update_required_data(
$data,
PropertiesDictionary::UPDATE_CONTACT_SINGLE_PROD_REQUIRED_FIELDS,
@@ -198,6 +222,9 @@ class ApplePayDataObjectHttp {
if ( ! $data ) {
return;
}
+
+ $data = $this->preprocess_request_data( $data );
+
$result = $this->update_required_data(
$data,
PropertiesDictionary::UPDATE_METHOD_SINGLE_PROD_REQUIRED_FIELDS,
@@ -226,6 +253,10 @@ class ApplePayDataObjectHttp {
if ( ! $data ) {
return;
}
+
+ $data = $this->append_products_to_data( $data, $_POST );
+ $data = $this->preprocess_request_data( $data );
+
$data[ PropertiesDictionary::CALLER_PAGE ] = $caller_page;
$result = $this->update_required_data(
$data,
@@ -261,6 +292,27 @@ class ApplePayDataObjectHttp {
$this->update_shipping_method( $data );
}
+ /**
+ * Pre-processes request data to transform it to a standard format.
+ *
+ * @param array $data The data.
+ * @return array
+ */
+ protected function preprocess_request_data( array $data ): array {
+ // Fill product variables if a products object is received.
+ if ( is_array( $data[ PropertiesDictionary::PRODUCTS ] ?? null ) ) {
+ $product = $data[ PropertiesDictionary::PRODUCTS ][0];
+
+ $data[ PropertiesDictionary::PRODUCT_ID ] = $product['id'] ?? 0;
+ $data[ PropertiesDictionary::PRODUCT_QUANTITY ] = $product['quantity'] ?? array();
+ $data[ PropertiesDictionary::PRODUCT_VARIATIONS ] = $product['variations'] ?? array();
+ $data[ PropertiesDictionary::PRODUCT_EXTRA ] = $product['extra'] ?? array();
+ $data[ PropertiesDictionary::PRODUCT_BOOKING ] = $product['booking'] ?? array();
+ }
+ unset( $data[ PropertiesDictionary::PRODUCTS ] );
+ return $data;
+ }
+
/**
* Checks if the array contains all required fields and if those
* are not empty.
@@ -300,7 +352,7 @@ class ApplePayDataObjectHttp {
*/
protected function assign_data_object_values( array $data ): void {
foreach ( $data as $key => $value ) {
- // Null values may give origin to type errors. If necessary replace condition this with a specialized field filter.
+ // Null values may give origin to type errors. If necessary replace this condition with a specialized field filter.
if ( null === $value ) {
continue;
}
@@ -547,6 +599,33 @@ class ApplePayDataObjectHttp {
return $this->product_quantity;
}
+ /**
+ * Returns the product variations.
+ *
+ * @return array
+ */
+ public function product_variations(): array {
+ return $this->product_variations;
+ }
+
+ /**
+ * Returns the product extra.
+ *
+ * @return array
+ */
+ public function product_extra(): array {
+ return $this->product_extra;
+ }
+
+ /**
+ * Returns the product booking.
+ *
+ * @return array
+ */
+ public function product_booking(): array {
+ return $this->product_booking;
+ }
+
/**
* Returns the nonce.
*
@@ -580,7 +659,7 @@ class ApplePayDataObjectHttp {
* @return array|false|null
*/
public function get_filtered_request_data() {
- return filter_input_array(
+ $data = filter_input_array(
INPUT_POST,
array(
PropertiesDictionary::CALLER_PAGE => FILTER_SANITIZE_SPECIAL_CHARS,
@@ -604,8 +683,43 @@ class ApplePayDataObjectHttp {
),
PropertiesDictionary::PRODUCT_ID => FILTER_SANITIZE_NUMBER_INT,
PropertiesDictionary::PRODUCT_QUANTITY => FILTER_SANITIZE_NUMBER_INT,
+ PropertiesDictionary::PRODUCT_VARIATIONS => array(
+ 'filter' => FILTER_SANITIZE_SPECIAL_CHARS,
+ 'flags' => FILTER_REQUIRE_ARRAY,
+ ),
+ PropertiesDictionary::PRODUCT_EXTRA => array(
+ 'filter' => FILTER_SANITIZE_SPECIAL_CHARS,
+ 'flags' => FILTER_REQUIRE_ARRAY,
+ ),
+ PropertiesDictionary::PRODUCT_BOOKING => array(
+ 'filter' => FILTER_SANITIZE_SPECIAL_CHARS,
+ 'flags' => FILTER_REQUIRE_ARRAY,
+ ),
)
);
+
+ if ( ! $data ) {
+ return false;
+ }
+
+ return $this->append_products_to_data( $data, $_POST );
+ }
+
+ /**
+ * Appends product to a data array.
+ *
+ * @param array $data The data.
+ * @param array $request_data The request data.
+ * @return array
+ */
+ public function append_products_to_data( array $data, array $request_data ): array {
+ $products = json_decode( wp_unslash( $request_data[ PropertiesDictionary::PRODUCTS ] ?? '' ), true );
+
+ if ( $products ) {
+ $data[ PropertiesDictionary::PRODUCTS ] = $products;
+ }
+
+ return $data;
}
/**
diff --git a/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php b/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php
index 6558ac472..0b15fbe78 100644
--- a/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php
+++ b/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php
@@ -169,37 +169,32 @@ class DataToAppleButtonScripts {
if ( ! $cart ) {
return array();
}
- $nonce = wp_nonce_field( 'woocommerce-process_checkout', 'woocommerce-process-checkout-nonce' );
- $button_markup =
- '
'
- . $nonce
- . '
';
- $type = $this->settings->has( 'applepay_button_type' ) ? $this->settings->get( 'applepay_button_type' ) : '';
- $color = $this->settings->has( 'applepay_button_color' ) ? $this->settings->get( 'applepay_button_color' ) : '';
- $lang = $this->settings->has( 'applepay_button_language' ) ? $this->settings->get( 'applepay_button_language' ) : '';
- $lang = apply_filters( 'woocommerce_paypal_payments_applepay_button_language', $lang );
+
+ $type = $this->settings->has( 'applepay_button_type' ) ? $this->settings->get( 'applepay_button_type' ) : '';
+ $color = $this->settings->has( 'applepay_button_color' ) ? $this->settings->get( 'applepay_button_color' ) : '';
+ $lang = $this->settings->has( 'applepay_button_language' ) ? $this->settings->get( 'applepay_button_language' ) : '';
+ $lang = apply_filters( 'woocommerce_paypal_payments_applepay_button_language', $lang );
return array(
- 'sdk_url' => $this->sdk_url,
- 'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG ? true : false,
- 'button' => array(
+ 'sdk_url' => $this->sdk_url,
+ 'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG ? true : false,
+ 'button' => array(
'wrapper' => 'applepay-container',
'mini_cart_wrapper' => 'applepay-container-minicart',
'type' => $type,
'color' => $color,
'lang' => $lang,
),
- 'product' => array(
+ 'product' => array(
'needShipping' => $cart->needs_shipping(),
'subtotal' => $cart->get_subtotal(),
),
- 'shop' => array(
+ 'shop' => array(
'countryCode' => $shop_country_code,
'currencyCode' => $currency_code,
'totalLabel' => $total_label,
),
- 'ajax_url' => admin_url( 'admin-ajax.php' ),
- 'buttonMarkup' => $button_markup,
+ 'ajax_url' => admin_url( 'admin-ajax.php' ),
);
}
}
diff --git a/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php b/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php
index fdb374fb7..25227e11b 100644
--- a/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php
+++ b/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php
@@ -14,7 +14,6 @@ namespace WooCommerce\PayPalCommerce\Applepay\Assets;
*/
class PropertiesDictionary {
-
public const BILLING_CONTACT_INVALID = 'billing Contact Invalid';
public const CREATE_ORDER_SINGLE_PROD_REQUIRED_FIELDS =
@@ -62,19 +61,22 @@ class PropertiesDictionary {
self::SIMPLIFIED_CONTACT,
);
- public const PRODUCT_ID = 'product_id';
-
- public const SIMPLIFIED_CONTACT = 'simplified_contact';
-
- public const SHIPPING_METHOD = 'shipping_method';
-
- public const SHIPPING_CONTACT = 'shipping_contact';
+ public const PRODUCTS = 'products';
+ public const PRODUCT_ID = 'product_id';
+ public const PRODUCT_QUANTITY = 'product_quantity';
+ public const PRODUCT_VARIATIONS = 'product_variations';
+ public const PRODUCT_EXTRA = 'product_extra';
+ public const PRODUCT_BOOKING = 'product_booking';
+ public const SIMPLIFIED_CONTACT = 'simplified_contact';
+ public const SHIPPING_METHOD = 'shipping_method';
+ public const SHIPPING_CONTACT = 'shipping_contact';
public const SHIPPING_CONTACT_INVALID = 'shipping Contact Invalid';
+ public const BILLING_CONTACT = 'billing_contact';
- public const NONCE = 'nonce';
-
- public const WCNONCE = 'woocommerce-process-checkout-nonce';
+ public const NONCE = 'nonce';
+ public const NONCE_ACTION = 'woocommerce-process_checkout';
+ public const WCNONCE = 'woocommerce-process-checkout-nonce';
public const CREATE_ORDER_CART_REQUIRED_FIELDS =
array(
@@ -83,25 +85,16 @@ class PropertiesDictionary {
self::SHIPPING_CONTACT,
);
- public const PRODUCT_QUANTITY = 'product_quantity';
-
public const CALLER_PAGE = 'caller_page';
- public const BILLING_CONTACT = 'billing_contact';
-
public const NEED_SHIPPING = 'need_shipping';
public const UPDATE_SHIPPING_CONTACT = 'ppcp_update_shipping_contact';
-
- public const UPDATE_SHIPPING_METHOD = 'ppcp_update_shipping_method';
-
- public const CREATE_ORDER = 'ppcp_create_order';
-
- public const CREATE_ORDER_CART = 'ppcp_create_order_cart';
-
- public const REDIRECT = 'ppcp_redirect';
-
- public const VALIDATE = 'ppcp_validate';
+ public const UPDATE_SHIPPING_METHOD = 'ppcp_update_shipping_method';
+ public const CREATE_ORDER = 'ppcp_create_order';
+ public const CREATE_ORDER_CART = 'ppcp_create_order_cart';
+ public const REDIRECT = 'ppcp_redirect';
+ public const VALIDATE = 'ppcp_validate';
/**
* Returns the possible list of button colors.
diff --git a/modules/ppcp-blocks/resources/js/Helper/Address.js b/modules/ppcp-blocks/resources/js/Helper/Address.js
index d522d208e..6a999f141 100644
--- a/modules/ppcp-blocks/resources/js/Helper/Address.js
+++ b/modules/ppcp-blocks/resources/js/Helper/Address.js
@@ -124,3 +124,29 @@ export const paypalOrderToWcAddresses = (order) => {
return {billingAddress, shippingAddress};
}
+
+/**
+ * Merges two WC addresses.
+ * The objects can contain either the WC form fields or billingAddress, shippingAddress objects.
+ *
+ * @param {Object} address1
+ * @param {Object} address2
+ * @returns {any}
+ */
+export const mergeWcAddress = (address1, address2) => {
+ if ('billingAddress' in address1) {
+ return {
+ billingAddress: mergeWcAddress(address1.billingAddress, address2.billingAddress),
+ shippingAddress: mergeWcAddress(address1.shippingAddress, address2.shippingAddress),
+ }
+ }
+
+ let address2WithoutEmpty = {...address2};
+ Object.keys(address2).forEach(key => {
+ if (address2[key] === '') {
+ delete address2WithoutEmpty[key];
+ }
+ });
+
+ return {...address1, ...address2WithoutEmpty};
+}
diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js
index 85bcd278f..582fdede3 100644
--- a/modules/ppcp-blocks/resources/js/checkout-block.js
+++ b/modules/ppcp-blocks/resources/js/checkout-block.js
@@ -1,6 +1,6 @@
import {useEffect, useState} from '@wordpress/element';
import {registerExpressPaymentMethod, registerPaymentMethod} from '@woocommerce/blocks-registry';
-import {paypalAddressToWc, paypalOrderToWcAddresses} from "./Helper/Address";
+import {mergeWcAddress, paypalAddressToWc, paypalOrderToWcAddresses} from "./Helper/Address";
import {loadPaypalScript} from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading'
import buttonModuleWatcher from "../../../ppcp-button/resources/js/modules/ButtonModuleWatcher";
@@ -24,6 +24,22 @@ const PayPalComponent = ({
const [paypalOrder, setPaypalOrder] = useState(null);
+ useEffect(() => {
+ // fill the form if in continuation (for product or mini-cart buttons)
+ if (!config.scriptData.continuation || !config.scriptData.continuation.order || window.ppcpContinuationFilled) {
+ return;
+ }
+ const paypalAddresses = paypalOrderToWcAddresses(config.scriptData.continuation.order);
+ const wcAddresses = wp.data.select('wc/store/cart').getCustomerData();
+ const addresses = mergeWcAddress(wcAddresses, paypalAddresses);
+ wp.data.dispatch('wc/store/cart').setBillingAddress(addresses.billingAddress);
+ if (shippingData.needsShipping) {
+ wp.data.dispatch('wc/store/cart').setShippingAddress(addresses.shippingAddress);
+ }
+ // this useEffect should run only once, but adding this in case of some kind of full re-rendering
+ window.ppcpContinuationFilled = true;
+ }, [])
+
const [loaded, setLoaded] = useState(false);
useEffect(() => {
if (!loaded) {
diff --git a/modules/ppcp-blocks/src/PayPalPaymentMethod.php b/modules/ppcp-blocks/src/PayPalPaymentMethod.php
index 1da58914d..3c407f2a2 100644
--- a/modules/ppcp-blocks/src/PayPalPaymentMethod.php
+++ b/modules/ppcp-blocks/src/PayPalPaymentMethod.php
@@ -167,6 +167,11 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType {
$script_data['continuation']['cancel'] = array(
'html' => $this->cancellation_view->render_session_cancellation( $url, $this->session_handler->funding_source() ),
);
+
+ $order = $this->session_handler->order();
+ if ( $order ) {
+ $script_data['continuation']['order'] = $order->to_array();
+ }
}
return array(
diff --git a/modules/ppcp-button/services.php b/modules/ppcp-button/services.php
index 8661f54a6..0ef77144d 100644
--- a/modules/ppcp-button/services.php
+++ b/modules/ppcp-button/services.php
@@ -12,6 +12,7 @@ namespace WooCommerce\PayPalCommerce\Button;
use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveSubscriptionEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\CartScriptParamsEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\SimulateCartEndpoint;
+use WooCommerce\PayPalCommerce\Button\Helper\CartProductsHelper;
use WooCommerce\PayPalCommerce\Button\Helper\CheckoutFormSaver;
use WooCommerce\PayPalCommerce\Button\Endpoint\SaveCheckoutFormEndpoint;
use WooCommerce\PayPalCommerce\Button\Validation\CheckoutFormValidator;
@@ -128,24 +129,24 @@ return array(
if ( ! \WC()->cart ) {
throw new RuntimeException( 'cant initialize endpoint at this moment' );
}
- $smart_button = $container->get( 'button.smart-button' );
- $cart = WC()->cart;
- $request_data = $container->get( 'button.request-data' );
- $data_store = \WC_Data_Store::load( 'product' );
- $logger = $container->get( 'woocommerce.logger.woocommerce' );
- return new SimulateCartEndpoint( $smart_button, $cart, $request_data, $data_store, $logger );
+ $smart_button = $container->get( 'button.smart-button' );
+ $cart = WC()->cart;
+ $request_data = $container->get( 'button.request-data' );
+ $cart_products = $container->get( 'button.helper.cart-products' );
+ $logger = $container->get( 'woocommerce.logger.woocommerce' );
+ return new SimulateCartEndpoint( $smart_button, $cart, $request_data, $cart_products, $logger );
},
'button.endpoint.change-cart' => static function ( ContainerInterface $container ): ChangeCartEndpoint {
if ( ! \WC()->cart ) {
throw new RuntimeException( 'cant initialize endpoint at this moment' );
}
- $cart = WC()->cart;
- $shipping = WC()->shipping();
- $request_data = $container->get( 'button.request-data' );
+ $cart = WC()->cart;
+ $shipping = WC()->shipping();
+ $request_data = $container->get( 'button.request-data' );
$purchase_unit_factory = $container->get( 'api.factory.purchase-unit' );
- $data_store = \WC_Data_Store::load( 'product' );
- $logger = $container->get( 'woocommerce.logger.woocommerce' );
- return new ChangeCartEndpoint( $cart, $shipping, $request_data, $purchase_unit_factory, $data_store, $logger );
+ $cart_products = $container->get( 'button.helper.cart-products' );
+ $logger = $container->get( 'woocommerce.logger.woocommerce' );
+ return new ChangeCartEndpoint( $cart, $shipping, $request_data, $purchase_unit_factory, $cart_products, $logger );
},
'button.endpoint.create-order' => static function ( ContainerInterface $container ): CreateOrderEndpoint {
$request_data = $container->get( 'button.request-data' );
@@ -251,6 +252,12 @@ return array(
$container->get( 'woocommerce.logger.woocommerce' )
);
},
+
+ 'button.helper.cart-products' => static function ( ContainerInterface $container ): CartProductsHelper {
+ $data_store = \WC_Data_Store::load( 'product' );
+ return new CartProductsHelper( $data_store );
+ },
+
'button.helper.three-d-secure' => static function ( ContainerInterface $container ): ThreeDSecure {
$logger = $container->get( 'woocommerce.logger.woocommerce' );
return new ThreeDSecure( $logger );
diff --git a/modules/ppcp-button/src/Endpoint/AbstractCartEndpoint.php b/modules/ppcp-button/src/Endpoint/AbstractCartEndpoint.php
index 3133cf544..604c25124 100644
--- a/modules/ppcp-button/src/Endpoint/AbstractCartEndpoint.php
+++ b/modules/ppcp-button/src/Endpoint/AbstractCartEndpoint.php
@@ -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();
}
-
}
diff --git a/modules/ppcp-button/src/Endpoint/ChangeCartEndpoint.php b/modules/ppcp-button/src/Endpoint/ChangeCartEndpoint.php
index 95979d1b2..701076bed 100644
--- a/modules/ppcp-button/src/Endpoint/ChangeCartEndpoint.php
+++ b/modules/ppcp-button/src/Endpoint/ChangeCartEndpoint.php
@@ -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 ) {
diff --git a/modules/ppcp-button/src/Endpoint/SimulateCartEndpoint.php b/modules/ppcp-button/src/Endpoint/SimulateCartEndpoint.php
index 25ac4ba91..1040cc680 100644
--- a/modules/ppcp-button/src/Endpoint/SimulateCartEndpoint.php
+++ b/modules/ppcp-button/src/Endpoint/SimulateCartEndpoint.php
@@ -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,24 +39,24 @@ 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->logger = $logger;
+ $this->smart_button = $smart_button;
+ $this->cart = clone $cart;
+ $this->request_data = $request_data;
+ $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 );
}
/**
diff --git a/modules/ppcp-button/src/Helper/CartProductsHelper.php b/modules/ppcp-button/src/Helper/CartProductsHelper.php
new file mode 100644
index 000000000..804c26598
--- /dev/null
+++ b/modules/ppcp-button/src/Helper/CartProductsHelper.php
@@ -0,0 +1,285 @@
+product_data_store = $product_data_store;
+ }
+
+ /**
+ * Sets a new cart instance.
+ *
+ * @param WC_Cart $cart The 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->product_from_data( $product );
+ if ( $product ) {
+ $products[] = $product;
+ }
+ }
+ return $products;
+ }
+
+ /**
+ * Returns product information from a data array.
+ *
+ * @param array $product The product data array, usually provided by the product page form.
+ * @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 {
+ if ( ! $this->cart ) {
+ throw new Exception( 'Cart not set.' );
+ }
+
+ $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 {
+ if ( ! $this->cart ) {
+ throw new Exception( 'Cart not set.' );
+ }
+
+ $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 {
+ if ( ! $this->cart ) {
+ throw new Exception( 'Cart not set.' );
+ }
+
+ 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 Throws if there's a failure removing the cart items.
+ */
+ public function remove_cart_items(): void {
+ if ( ! $this->cart ) {
+ throw new Exception( 'Cart not set.' );
+ }
+
+ foreach ( $this->cart_item_keys as $cart_item_key ) {
+ if ( ! $cart_item_key ) {
+ continue;
+ }
+ $this->cart->remove_cart_item( $cart_item_key );
+ }
+ }
+
+ /**
+ * Returns the cart item keys of the items added to cart.
+ *
+ * @return array
+ */
+ public function cart_item_keys(): array {
+ return $this->cart_item_keys;
+ }
+
+}
diff --git a/modules/ppcp-button/src/Helper/ContextTrait.php b/modules/ppcp-button/src/Helper/ContextTrait.php
index babdcc28c..12a60c384 100644
--- a/modules/ppcp-button/src/Helper/ContextTrait.php
+++ b/modules/ppcp-button/src/Helper/ContextTrait.php
@@ -86,7 +86,7 @@ trait ContextTrait {
return $context;
}
- if ( is_shop() ) {
+ if ( is_shop() || is_product_category() ) {
return 'shop';
}
diff --git a/modules/ppcp-compat/resources/js/tracking-compat.js b/modules/ppcp-compat/resources/js/tracking-compat.js
index b62158ff1..f6d2c80e4 100644
--- a/modules/ppcp-compat/resources/js/tracking-compat.js
+++ b/modules/ppcp-compat/resources/js/tracking-compat.js
@@ -9,7 +9,9 @@ document.addEventListener(
const loadLocation = location.href + " " + orderTrackingContainerSelector + ">*";
const gzdSyncEnabled = config.gzd_sync_enabled;
const wcShipmentSyncEnabled = config.wc_shipment_sync_enabled;
+ const wcShippingTaxSyncEnabled = config.wc_shipping_tax_sync_enabled;
const wcShipmentSaveButton = document.querySelector('#woocommerce-shipment-tracking .button-save-form');
+ const wcShipmentTaxBuyLabelButtonSelector = '.components-modal__screen-overlay .label-purchase-modal__sidebar .purchase-section button.components-button';
const toggleLoaderVisibility = function() {
const loader = document.querySelector('.ppcp-tracking-loader');
@@ -45,5 +47,20 @@ document.addEventListener(
waitForTrackingUpdate(jQuery('#shipment-tracking-form'));
})
}
+
+ if (wcShippingTaxSyncEnabled && typeof(wcShippingTaxSyncEnabled) != 'undefined' && wcShippingTaxSyncEnabled != null) {
+ document.addEventListener('click', function(event) {
+ const wcShipmentTaxBuyLabelButton = event.target.closest(wcShipmentTaxBuyLabelButtonSelector);
+
+ if (wcShipmentTaxBuyLabelButton) {
+ toggleLoaderVisibility();
+ setTimeout(function () {
+ jQuery(orderTrackingContainerSelector).load(loadLocation, "", function(){
+ toggleLoaderVisibility();
+ });
+ }, 10000);
+ }
+ });
+ }
},
);
diff --git a/modules/ppcp-compat/services.php b/modules/ppcp-compat/services.php
index 035851b04..364686a84 100644
--- a/modules/ppcp-compat/services.php
+++ b/modules/ppcp-compat/services.php
@@ -65,6 +65,12 @@ return array(
'compat.ywot.is_supported_plugin_version_active' => function (): bool {
return function_exists( 'yith_ywot_init' );
},
+ 'compat.shipstation.is_supported_plugin_version_active' => function (): bool {
+ return function_exists( 'woocommerce_shipstation_init' );
+ },
+ 'compat.wc_shipping_tax.is_supported_plugin_version_active' => function (): bool {
+ return class_exists( 'WC_Connect_Loader' );
+ },
'compat.module.url' => static function ( ContainerInterface $container ): string {
/**
@@ -84,6 +90,7 @@ return array(
$container->get( 'ppcp.asset-version' ),
$container->get( 'compat.gzd.is_supported_plugin_version_active' ),
$container->get( 'compat.wc_shipment_tracking.is_supported_plugin_version_active' ),
+ $container->get( 'compat.wc_shipping_tax.is_supported_plugin_version_active' ),
$container->get( 'api.bearer' )
);
},
diff --git a/modules/ppcp-compat/src/Assets/CompatAssets.php b/modules/ppcp-compat/src/Assets/CompatAssets.php
index c1febf93f..85e0bde7b 100644
--- a/modules/ppcp-compat/src/Assets/CompatAssets.php
+++ b/modules/ppcp-compat/src/Assets/CompatAssets.php
@@ -47,6 +47,13 @@ class CompatAssets {
*/
protected $is_wc_shipment_active;
+ /**
+ * Whether WC Shipping & Tax plugin is active
+ *
+ * @var bool
+ */
+ private $is_wc_shipping_tax_active;
+
/**
* The bearer.
*
@@ -61,6 +68,7 @@ class CompatAssets {
* @param string $version The assets version.
* @param bool $is_gzd_active Whether Germanized plugin is active.
* @param bool $is_wc_shipment_active Whether WC Shipments plugin is active.
+ * @param bool $is_wc_shipping_tax_active Whether WC Shipping & Tax plugin is active.
* @param Bearer $bearer The bearer.
*/
public function __construct(
@@ -68,14 +76,16 @@ class CompatAssets {
string $version,
bool $is_gzd_active,
bool $is_wc_shipment_active,
+ bool $is_wc_shipping_tax_active,
Bearer $bearer
) {
- $this->module_url = $module_url;
- $this->version = $version;
- $this->is_gzd_active = $is_gzd_active;
- $this->is_wc_shipment_active = $is_wc_shipment_active;
- $this->bearer = $bearer;
+ $this->module_url = $module_url;
+ $this->version = $version;
+ $this->is_gzd_active = $is_gzd_active;
+ $this->is_wc_shipment_active = $is_wc_shipment_active;
+ $this->is_wc_shipping_tax_active = $is_wc_shipping_tax_active;
+ $this->bearer = $bearer;
}
/**
@@ -99,6 +109,7 @@ class CompatAssets {
array(
'gzd_sync_enabled' => apply_filters( 'woocommerce_paypal_payments_sync_gzd_tracking', true ) && $this->is_gzd_active,
'wc_shipment_sync_enabled' => apply_filters( 'woocommerce_paypal_payments_sync_wc_shipment_tracking', true ) && $this->is_wc_shipment_active,
+ 'wc_shipping_tax_sync_enabled' => apply_filters( 'woocommerce_paypal_payments_sync_wc_shipping_tax', true ) && $this->is_wc_shipping_tax_active,
)
);
}
diff --git a/modules/ppcp-compat/src/CompatModule.php b/modules/ppcp-compat/src/CompatModule.php
index 790c3edb0..edb4f0e67 100644
--- a/modules/ppcp-compat/src/CompatModule.php
+++ b/modules/ppcp-compat/src/CompatModule.php
@@ -9,22 +9,13 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Compat;
-use Vendidero\Germanized\Shipments\ShipmentItem;
-use WooCommerce\PayPalCommerce\OrderTracking\Shipment\ShipmentFactoryInterface;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
-use Exception;
use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
-use Psr\Log\LoggerInterface;
-use Vendidero\Germanized\Shipments\Shipment;
-use WC_Order;
use WooCommerce\PayPalCommerce\Compat\Assets\CompatAssets;
-use WooCommerce\PayPalCommerce\OrderTracking\Endpoint\OrderTrackingEndpoint;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
-use WP_REST_Request;
-use WP_REST_Response;
/**
* Class CompatModule
@@ -124,230 +115,11 @@ class CompatModule implements ModuleInterface {
* @return void
*/
protected function initialize_tracking_compat_layer( ContainerInterface $c ): void {
- $is_gzd_active = $c->get( 'compat.gzd.is_supported_plugin_version_active' );
- $is_wc_shipment_tracking_active = $c->get( 'compat.wc_shipment_tracking.is_supported_plugin_version_active' );
- $is_ywot_active = $c->get( 'compat.ywot.is_supported_plugin_version_active' );
+ $order_tracking_integrations = $c->get( 'order-tracking.integrations' );
- if ( $is_gzd_active ) {
- $this->initialize_gzd_compat_layer( $c );
- }
-
- if ( $is_wc_shipment_tracking_active ) {
- $this->initialize_wc_shipment_tracking_compat_layer( $c );
- }
-
- if ( $is_ywot_active ) {
- $this->initialize_ywot_compat_layer( $c );
- }
- }
-
- /**
- * Sets up the Germanized for WooCommerce
- * plugin compatibility layer.
- *
- * @link https://wordpress.org/plugins/woocommerce-germanized/
- *
- * @param ContainerInterface $c The Container.
- * @return void
- */
- protected function initialize_gzd_compat_layer( ContainerInterface $c ): void {
- add_action(
- 'woocommerce_gzd_shipment_status_shipped',
- function( int $shipment_id, Shipment $shipment ) use ( $c ) {
- if ( ! apply_filters( 'woocommerce_paypal_payments_sync_gzd_tracking', true ) ) {
- return;
- }
-
- $wc_order = $shipment->get_order();
-
- if ( ! is_a( $wc_order, WC_Order::class ) ) {
- return;
- }
-
- $order_id = $wc_order->get_id();
- $transaction_id = $wc_order->get_transaction_id();
- $tracking_number = $shipment->get_tracking_id();
- $carrier = $shipment->get_shipping_provider();
- $items = array_map(
- function ( ShipmentItem $item ): int {
- return $item->get_order_item_id();
- },
- $shipment->get_items()
- );
-
- if ( ! $tracking_number || ! $carrier || ! $transaction_id ) {
- return;
- }
-
- $this->create_tracking( $c, $order_id, $transaction_id, $tracking_number, $carrier, $items );
- },
- 500,
- 2
- );
- }
-
- /**
- * Sets up the Shipment Tracking
- * plugin compatibility layer.
- *
- * @link https://woocommerce.com/document/shipment-tracking/
- *
- * @param ContainerInterface $c The Container.
- * @return void
- */
- protected function initialize_wc_shipment_tracking_compat_layer( ContainerInterface $c ): void {
- add_action(
- 'wp_ajax_wc_shipment_tracking_save_form',
- function() use ( $c ) {
- check_ajax_referer( 'create-tracking-item', 'security', true );
-
- if ( ! apply_filters( 'woocommerce_paypal_payments_sync_wc_shipment_tracking', true ) ) {
- return;
- }
-
- $order_id = (int) wc_clean( wp_unslash( $_POST['order_id'] ?? '' ) );
- $wc_order = wc_get_order( $order_id );
- if ( ! is_a( $wc_order, WC_Order::class ) ) {
- return;
- }
-
- $transaction_id = $wc_order->get_transaction_id();
- $tracking_number = wc_clean( wp_unslash( $_POST['tracking_number'] ?? '' ) );
- $carrier = wc_clean( wp_unslash( $_POST['tracking_provider'] ?? '' ) );
- $carrier_other = wc_clean( wp_unslash( $_POST['custom_tracking_provider'] ?? '' ) );
- $carrier = $carrier ?: $carrier_other ?: '';
-
- if ( ! $tracking_number || ! is_string( $tracking_number ) || ! $carrier || ! is_string( $carrier ) || ! $transaction_id ) {
- return;
- }
-
- $this->create_tracking( $c, $order_id, $transaction_id, $tracking_number, $carrier, array() );
- }
- );
-
- add_filter(
- 'woocommerce_rest_prepare_order_shipment_tracking',
- function( WP_REST_Response $response, array $tracking_item, WP_REST_Request $request ) use ( $c ): WP_REST_Response {
- if ( ! apply_filters( 'woocommerce_paypal_payments_sync_wc_shipment_tracking', true ) ) {
- return $response;
- }
-
- $callback = $request->get_attributes()['callback']['1'] ?? '';
- if ( $callback !== 'create_item' ) {
- return $response;
- }
-
- $order_id = $tracking_item['order_id'] ?? 0;
- $wc_order = wc_get_order( $order_id );
- if ( ! is_a( $wc_order, WC_Order::class ) ) {
- return $response;
- }
-
- $transaction_id = $wc_order->get_transaction_id();
- $tracking_number = $tracking_item['tracking_number'] ?? '';
- $carrier = $tracking_item['tracking_provider'] ?? '';
- $carrier_other = $tracking_item['custom_tracking_provider'] ?? '';
- $carrier = $carrier ?: $carrier_other ?: '';
-
- if ( ! $tracking_number || ! $carrier || ! $transaction_id ) {
- return $response;
- }
-
- $this->create_tracking( $c, $order_id, $transaction_id, $tracking_number, $carrier, array() );
-
- return $response;
- },
- 10,
- 3
- );
- }
-
- /**
- * Sets up the YITH WooCommerce Order & Shipment Tracking
- * plugin compatibility layer.
- *
- * @link https://wordpress.org/plugins/yith-woocommerce-order-tracking/
- *
- * @param ContainerInterface $c The Container.
- * @return void
- */
- protected function initialize_ywot_compat_layer( ContainerInterface $c ): void {
- add_action(
- 'woocommerce_process_shop_order_meta',
- function( int $order_id ) use ( $c ) {
- if ( ! apply_filters( 'woocommerce_paypal_payments_sync_ywot_tracking', true ) ) {
- return;
- }
-
- $wc_order = wc_get_order( $order_id );
- if ( ! is_a( $wc_order, WC_Order::class ) ) {
- return;
- }
-
- $transaction_id = $wc_order->get_transaction_id();
- // phpcs:ignore WordPress.Security.NonceVerification.Missing
- $tracking_number = wc_clean( wp_unslash( $_POST['ywot_tracking_code'] ?? '' ) );
- // phpcs:ignore WordPress.Security.NonceVerification.Missing
- $carrier = wc_clean( wp_unslash( $_POST['ywot_carrier_name'] ?? '' ) );
-
- if ( ! $tracking_number || ! is_string( $tracking_number ) || ! $carrier || ! is_string( $carrier ) || ! $transaction_id ) {
- return;
- }
-
- $this->create_tracking( $c, $order_id, $transaction_id, $tracking_number, $carrier, array() );
- },
- 500,
- 1
- );
- }
-
- /**
- * Creates PayPal tracking.
- *
- * @param ContainerInterface $c The Container.
- * @param int $wc_order_id The WC order ID.
- * @param string $transaction_id The transaction ID.
- * @param string $tracking_number The tracking number.
- * @param string $carrier The shipment carrier.
- * @param int[] $line_items The list of shipment line item IDs.
- * @return void
- */
- protected function create_tracking(
- ContainerInterface $c,
- int $wc_order_id,
- string $transaction_id,
- string $tracking_number,
- string $carrier,
- array $line_items
- ) {
- $endpoint = $c->get( 'order-tracking.endpoint.controller' );
- assert( $endpoint instanceof OrderTrackingEndpoint );
-
- $logger = $c->get( 'woocommerce.logger.woocommerce' );
- assert( $logger instanceof LoggerInterface );
-
- $shipment_factory = $c->get( 'order-tracking.shipment.factory' );
- assert( $shipment_factory instanceof ShipmentFactoryInterface );
-
- try {
- $ppcp_shipment = $shipment_factory->create_shipment(
- $wc_order_id,
- $transaction_id,
- $tracking_number,
- 'SHIPPED',
- 'OTHER',
- $carrier,
- $line_items
- );
-
- $tracking_information = $endpoint->get_tracking_information( $wc_order_id, $tracking_number );
-
- $tracking_information
- ? $endpoint->update_tracking_information( $ppcp_shipment, $wc_order_id )
- : $endpoint->add_tracking_information( $ppcp_shipment, $wc_order_id );
-
- } catch ( Exception $exception ) {
- $logger->error( "Couldn't sync tracking information: " . $exception->getMessage() );
+ foreach ( $order_tracking_integrations as $integration ) {
+ assert( $integration instanceof Integration );
+ $integration->integrate();
}
}
diff --git a/modules/ppcp-compat/src/Integration.php b/modules/ppcp-compat/src/Integration.php
new file mode 100644
index 000000000..f8aaf8bc6
--- /dev/null
+++ b/modules/ppcp-compat/src/Integration.php
@@ -0,0 +1,18 @@
+get( 'googlepay.eligible' ) ) {
- return;
- }
-
- // Load the button handler.
- $button = $c->get( 'googlepay.button' );
- assert( $button instanceof ButtonInterface );
- $button->initialize();
-
- // Show notice if there are product availability issues.
- $availability_notice = $c->get( 'googlepay.availability_notice' );
- assert( $availability_notice instanceof AvailabilityNotice );
- $availability_notice->execute();
-
- // Check if this merchant can activate / use the buttons.
- // We allow non referral merchants as they can potentially still use GooglePay, we just have no way of checking the capability.
- if ( ( ! $c->get( 'googlepay.available' ) ) && $c->get( 'googlepay.is_referral' ) ) {
- return;
- }
-
- // Initializes button rendering.
add_action(
- 'wp',
- static function () use ( $c, $button ) {
- if ( is_admin() ) {
- return;
- }
- $button->render();
- }
- );
-
- // Enqueue frontend scripts.
- add_action(
- 'wp_enqueue_scripts',
- static function () use ( $c, $button ) {
- $smart_button = $c->get( 'button.smart-button' );
- assert( $smart_button instanceof SmartButtonInterface );
- if ( $smart_button->should_load_ppcp_script() ) {
- $button->enqueue();
- }
-
- if ( has_block( 'woocommerce/checkout' ) || has_block( 'woocommerce/cart' ) ) {
- /**
- * Should add this to the ButtonInterface.
- *
- * @psalm-suppress UndefinedInterfaceMethod
- */
- $button->enqueue_styles();
- }
- }
- );
-
- // Enqueue backend scripts.
- add_action(
- 'admin_enqueue_scripts',
- static function () use ( $c, $button ) {
- if ( ! is_admin() ) {
- return;
- }
- /**
- * Should add this to the ButtonInterface.
- *
- * @psalm-suppress UndefinedInterfaceMethod
- */
- $button->enqueue_admin();
- }
- );
-
- // Registers buttons on blocks pages.
- add_action(
- 'woocommerce_blocks_payment_method_type_registration',
- function( PaymentMethodRegistry $payment_method_registry ) use ( $c, $button ): void {
- if ( $button->is_enabled() ) {
- $payment_method_registry->register( $c->get( 'googlepay.blocks-payment-method' ) );
- }
- }
- );
-
- // Adds GooglePay component to the backend button preview settings.
- add_action(
- 'woocommerce_paypal_payments_admin_gateway_settings',
- function( array $settings ) use ( $c, $button ): array {
- if ( is_array( $settings['components'] ) ) {
- $settings['components'][] = 'googlepay';
- }
- return $settings;
- }
- );
-
- // Initialize AJAX endpoints.
- add_action(
- 'wc_ajax_' . UpdatePaymentDataEndpoint::ENDPOINT,
+ 'init',
static function () use ( $c ) {
- $endpoint = $c->get( 'googlepay.endpoint.update-payment-data' );
- assert( $endpoint instanceof UpdatePaymentDataEndpoint );
- $endpoint->handle_request();
+
+ // Check if the module is applicable, correct country, currency, ... etc.
+ if ( ! $c->get( 'googlepay.eligible' ) ) {
+ return;
+ }
+
+ // Load the button handler.
+ $button = $c->get( 'googlepay.button' );
+ assert( $button instanceof ButtonInterface );
+ $button->initialize();
+
+ // Show notice if there are product availability issues.
+ $availability_notice = $c->get( 'googlepay.availability_notice' );
+ assert( $availability_notice instanceof AvailabilityNotice );
+ $availability_notice->execute();
+
+ // Check if this merchant can activate / use the buttons.
+ // We allow non referral merchants as they can potentially still use GooglePay, we just have no way of checking the capability.
+ if ( ( ! $c->get( 'googlepay.available' ) ) && $c->get( 'googlepay.is_referral' ) ) {
+ return;
+ }
+
+ // Initializes button rendering.
+ add_action(
+ 'wp',
+ static function () use ( $c, $button ) {
+ if ( is_admin() ) {
+ return;
+ }
+ $button->render();
+ }
+ );
+
+ // Enqueue frontend scripts.
+ add_action(
+ 'wp_enqueue_scripts',
+ static function () use ( $c, $button ) {
+ $smart_button = $c->get( 'button.smart-button' );
+ assert( $smart_button instanceof SmartButtonInterface );
+ if ( $smart_button->should_load_ppcp_script() ) {
+ $button->enqueue();
+ }
+
+ if ( has_block( 'woocommerce/checkout' ) || has_block( 'woocommerce/cart' ) ) {
+ /**
+ * Should add this to the ButtonInterface.
+ *
+ * @psalm-suppress UndefinedInterfaceMethod
+ */
+ $button->enqueue_styles();
+ }
+ }
+ );
+
+ // Enqueue backend scripts.
+ add_action(
+ 'admin_enqueue_scripts',
+ static function () use ( $c, $button ) {
+ if ( ! is_admin() ) {
+ return;
+ }
+
+ /**
+ * Should add this to the ButtonInterface.
+ *
+ * @psalm-suppress UndefinedInterfaceMethod
+ */
+ $button->enqueue_admin();
+ }
+ );
+
+ // Registers buttons on blocks pages.
+ add_action(
+ 'woocommerce_blocks_payment_method_type_registration',
+ function( PaymentMethodRegistry $payment_method_registry ) use ( $c, $button ): void {
+ if ( $button->is_enabled() ) {
+ $payment_method_registry->register( $c->get( 'googlepay.blocks-payment-method' ) );
+ }
+ }
+ );
+
+ // Adds GooglePay component to the backend button preview settings.
+ add_action(
+ 'woocommerce_paypal_payments_admin_gateway_settings',
+ function( array $settings ) use ( $c ): array {
+ if ( is_array( $settings['components'] ) ) {
+ $settings['components'][] = 'googlepay';
+ }
+ return $settings;
+ }
+ );
+
+ // Initialize AJAX endpoints.
+ add_action(
+ 'wc_ajax_' . UpdatePaymentDataEndpoint::ENDPOINT,
+ static function () use ( $c ) {
+ $endpoint = $c->get( 'googlepay.endpoint.update-payment-data' );
+ assert( $endpoint instanceof UpdatePaymentDataEndpoint );
+ $endpoint->handle_request();
+ }
+ );
+
}
);
}
diff --git a/modules/ppcp-order-tracking/resources/css/order-edit-page.scss b/modules/ppcp-order-tracking/resources/css/order-edit-page.scss
index c9a112ead..fc0c511b2 100644
--- a/modules/ppcp-order-tracking/resources/css/order-edit-page.scss
+++ b/modules/ppcp-order-tracking/resources/css/order-edit-page.scss
@@ -49,6 +49,8 @@
h4 {
display: inline-block;
margin: 10px 0px;
+ max-width: 83%;
+ overflow: hidden;
}
button {
diff --git a/modules/ppcp-order-tracking/services.php b/modules/ppcp-order-tracking/services.php
index 144460571..37154cd65 100644
--- a/modules/ppcp-order-tracking/services.php
+++ b/modules/ppcp-order-tracking/services.php
@@ -9,15 +9,16 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\OrderTracking;
-use WC_Order;
-use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
-use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
+use WooCommerce\PayPalCommerce\OrderTracking\Integration\GermanizedShipmentIntegration;
+use WooCommerce\PayPalCommerce\OrderTracking\Integration\ShipmentTrackingIntegration;
+use WooCommerce\PayPalCommerce\OrderTracking\Integration\ShipStationIntegration;
+use WooCommerce\PayPalCommerce\OrderTracking\Integration\WcShippingTaxIntegration;
+use WooCommerce\PayPalCommerce\OrderTracking\Integration\YithShipmentIntegration;
use WooCommerce\PayPalCommerce\OrderTracking\Shipment\ShipmentFactoryInterface;
use WooCommerce\PayPalCommerce\OrderTracking\Shipment\ShipmentFactory;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\OrderTracking\Assets\OrderEditPageAssets;
use WooCommerce\PayPalCommerce\OrderTracking\Endpoint\OrderTrackingEndpoint;
-use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
return array(
'order-tracking.assets' => function( ContainerInterface $container ) : OrderEditPageAssets {
@@ -92,4 +93,39 @@ return array(
'order-tracking.is-merchant-country-us' => static function ( ContainerInterface $container ): bool {
return $container->get( 'api.shop.country' ) === 'US';
},
+ 'order-tracking.integrations' => static function ( ContainerInterface $container ): array {
+ $shipment_factory = $container->get( 'order-tracking.shipment.factory' );
+ $logger = $container->get( 'woocommerce.logger.woocommerce' );
+ $endpoint = $container->get( 'order-tracking.endpoint.controller' );
+
+ $is_gzd_active = $container->get( 'compat.gzd.is_supported_plugin_version_active' );
+ $is_wc_shipment_active = $container->get( 'compat.wc_shipment_tracking.is_supported_plugin_version_active' );
+ $is_yith_ywot_active = $container->get( 'compat.ywot.is_supported_plugin_version_active' );
+ $is_ship_station_active = $container->get( 'compat.shipstation.is_supported_plugin_version_active' );
+ $is_wc_shipping_tax_active = $container->get( 'compat.wc_shipping_tax.is_supported_plugin_version_active' );
+
+ $integrations = array();
+
+ if ( $is_gzd_active ) {
+ $integrations[] = new GermanizedShipmentIntegration( $shipment_factory, $logger, $endpoint );
+ }
+
+ if ( $is_wc_shipment_active ) {
+ $integrations[] = new ShipmentTrackingIntegration( $shipment_factory, $logger, $endpoint );
+ }
+
+ if ( $is_yith_ywot_active ) {
+ $integrations[] = new YithShipmentIntegration( $shipment_factory, $logger, $endpoint );
+ }
+
+ if ( $is_ship_station_active ) {
+ $integrations[] = new ShipStationIntegration( $shipment_factory, $logger, $endpoint );
+ }
+
+ if ( $is_wc_shipping_tax_active ) {
+ $integrations[] = new WcShippingTaxIntegration( $shipment_factory, $logger, $endpoint );
+ }
+
+ return $integrations;
+ },
);
diff --git a/modules/ppcp-order-tracking/src/Integration/GermanizedShipmentIntegration.php b/modules/ppcp-order-tracking/src/Integration/GermanizedShipmentIntegration.php
new file mode 100644
index 000000000..7e44fd765
--- /dev/null
+++ b/modules/ppcp-order-tracking/src/Integration/GermanizedShipmentIntegration.php
@@ -0,0 +1,123 @@
+shipment_factory = $shipment_factory;
+ $this->logger = $logger;
+ $this->endpoint = $endpoint;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function integrate(): void {
+
+ add_action(
+ 'woocommerce_gzd_shipment_status_shipped',
+ function( int $shipment_id, Shipment $shipment ) {
+ if ( ! apply_filters( 'woocommerce_paypal_payments_sync_gzd_tracking', true ) ) {
+ return;
+ }
+
+ $wc_order = $shipment->get_order();
+
+ if ( ! is_a( $wc_order, WC_Order::class ) ) {
+ return;
+ }
+
+ $wc_order_id = $wc_order->get_id();
+ $transaction_id = $wc_order->get_transaction_id();
+ $tracking_number = $shipment->get_tracking_id();
+ $carrier = $shipment->get_shipping_provider();
+
+ $items = array_map(
+ function ( ShipmentItem $item ): int {
+ return $item->get_order_item_id();
+ },
+ $shipment->get_items()
+ );
+
+ if ( ! $tracking_number || ! $carrier || ! $transaction_id ) {
+ return;
+ }
+
+ try {
+ $ppcp_shipment = $this->shipment_factory->create_shipment(
+ $wc_order_id,
+ $transaction_id,
+ $tracking_number,
+ 'SHIPPED',
+ 'OTHER',
+ $carrier,
+ $items
+ );
+
+ $tracking_information = $this->endpoint->get_tracking_information( $wc_order_id, $tracking_number );
+
+ $tracking_information
+ ? $this->endpoint->update_tracking_information( $ppcp_shipment, $wc_order_id )
+ : $this->endpoint->add_tracking_information( $ppcp_shipment, $wc_order_id );
+
+ } catch ( Exception $exception ) {
+ $this->logger->error( "Couldn't sync tracking information: " . $exception->getMessage() );
+ }
+ },
+ 500,
+ 2
+ );
+ }
+}
diff --git a/modules/ppcp-order-tracking/src/Integration/ShipStationIntegration.php b/modules/ppcp-order-tracking/src/Integration/ShipStationIntegration.php
new file mode 100644
index 000000000..8d294f724
--- /dev/null
+++ b/modules/ppcp-order-tracking/src/Integration/ShipStationIntegration.php
@@ -0,0 +1,117 @@
+shipment_factory = $shipment_factory;
+ $this->logger = $logger;
+ $this->endpoint = $endpoint;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function integrate(): void {
+
+ add_action(
+ 'woocommerce_shipstation_shipnotify',
+ /**
+ * Param type for $wc_order can be different.
+ *
+ * @psalm-suppress MissingClosureParamType
+ */
+ function( $wc_order, array $data ) {
+ if ( ! apply_filters( 'woocommerce_paypal_payments_sync_ship_station_tracking', true ) ) {
+ return;
+ }
+
+ if ( ! is_a( $wc_order, WC_Order::class ) ) {
+ return;
+ }
+
+ $order_id = $wc_order->get_id();
+ $transaction_id = $wc_order->get_transaction_id();
+ $tracking_number = $data['tracking_number'] ?? '';
+ $carrier = $data['carrier'] ?? '';
+
+ if ( ! $tracking_number || ! is_string( $tracking_number ) || ! $carrier || ! is_string( $carrier ) || ! $transaction_id ) {
+ return;
+ }
+
+ try {
+ $ppcp_shipment = $this->shipment_factory->create_shipment(
+ $order_id,
+ $transaction_id,
+ $tracking_number,
+ 'SHIPPED',
+ 'OTHER',
+ $carrier,
+ array()
+ );
+
+ $tracking_information = $this->endpoint->get_tracking_information( $order_id, $tracking_number );
+
+ $tracking_information
+ ? $this->endpoint->update_tracking_information( $ppcp_shipment, $order_id )
+ : $this->endpoint->add_tracking_information( $ppcp_shipment, $order_id );
+
+ } catch ( Exception $exception ) {
+ $this->logger->error( "Couldn't sync tracking information: " . $exception->getMessage() );
+ }
+ },
+ 500,
+ 1
+ );
+ }
+}
diff --git a/modules/ppcp-order-tracking/src/Integration/ShipmentTrackingIntegration.php b/modules/ppcp-order-tracking/src/Integration/ShipmentTrackingIntegration.php
new file mode 100644
index 000000000..6c506a5e8
--- /dev/null
+++ b/modules/ppcp-order-tracking/src/Integration/ShipmentTrackingIntegration.php
@@ -0,0 +1,174 @@
+shipment_factory = $shipment_factory;
+ $this->logger = $logger;
+ $this->endpoint = $endpoint;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function integrate(): void {
+
+ add_action(
+ 'wp_ajax_wc_shipment_tracking_save_form',
+ function() {
+ check_ajax_referer( 'create-tracking-item', 'security', true );
+
+ if ( ! apply_filters( 'woocommerce_paypal_payments_sync_wc_shipment_tracking', true ) ) {
+ return;
+ }
+
+ $order_id = (int) wc_clean( wp_unslash( $_POST['order_id'] ?? '' ) );
+ $wc_order = wc_get_order( $order_id );
+ if ( ! is_a( $wc_order, WC_Order::class ) ) {
+ return;
+ }
+
+ $transaction_id = $wc_order->get_transaction_id();
+ $tracking_number = wc_clean( wp_unslash( $_POST['tracking_number'] ?? '' ) );
+ $carrier = wc_clean( wp_unslash( $_POST['tracking_provider'] ?? '' ) );
+ $carrier_other = wc_clean( wp_unslash( $_POST['custom_tracking_provider'] ?? '' ) );
+ $carrier = $carrier ?: $carrier_other ?: '';
+
+ if ( ! $tracking_number || ! is_string( $tracking_number ) || ! $carrier || ! is_string( $carrier ) || ! $transaction_id ) {
+ return;
+ }
+
+ $this->sync_tracking( $order_id, $transaction_id, $tracking_number, $carrier );
+ }
+ );
+
+ /**
+ * Support the case when tracking is added via REST.
+ */
+ add_filter(
+ 'woocommerce_rest_prepare_order_shipment_tracking',
+ function( WP_REST_Response $response, array $tracking_item, WP_REST_Request $request ): WP_REST_Response {
+ if ( ! apply_filters( 'woocommerce_paypal_payments_sync_wc_shipment_tracking', true ) ) {
+ return $response;
+ }
+
+ $callback = $request->get_attributes()['callback']['1'] ?? '';
+ if ( $callback !== 'create_item' ) {
+ return $response;
+ }
+
+ $order_id = $tracking_item['order_id'] ?? 0;
+ $wc_order = wc_get_order( $order_id );
+ if ( ! is_a( $wc_order, WC_Order::class ) ) {
+ return $response;
+ }
+
+ $transaction_id = $wc_order->get_transaction_id();
+ $tracking_number = $tracking_item['tracking_number'] ?? '';
+ $carrier = $tracking_item['tracking_provider'] ?? '';
+ $carrier_other = $tracking_item['custom_tracking_provider'] ?? '';
+ $carrier = $carrier ?: $carrier_other ?: '';
+
+ if ( ! $tracking_number || ! $carrier || ! $transaction_id ) {
+ return $response;
+ }
+
+ $this->sync_tracking( $order_id, $transaction_id, $tracking_number, $carrier );
+
+ return $response;
+ },
+ 10,
+ 3
+ );
+ }
+
+ /**
+ * Syncs (add | update) the PayPal tracking with given info.
+ *
+ * @param int $wc_order_id The WC order ID.
+ * @param string $transaction_id The transaction ID.
+ * @param string $tracking_number The tracking number.
+ * @param string $carrier The shipment carrier.
+ * @return void
+ */
+ protected function sync_tracking(
+ int $wc_order_id,
+ string $transaction_id,
+ string $tracking_number,
+ string $carrier
+ ) {
+ try {
+ $ppcp_shipment = $this->shipment_factory->create_shipment(
+ $wc_order_id,
+ $transaction_id,
+ $tracking_number,
+ 'SHIPPED',
+ 'OTHER',
+ $carrier,
+ array()
+ );
+
+ $tracking_information = $this->endpoint->get_tracking_information( $wc_order_id, $tracking_number );
+
+ $tracking_information
+ ? $this->endpoint->update_tracking_information( $ppcp_shipment, $wc_order_id )
+ : $this->endpoint->add_tracking_information( $ppcp_shipment, $wc_order_id );
+
+ } catch ( Exception $exception ) {
+ $this->logger->error( "Couldn't sync tracking information: " . $exception->getMessage() );
+ }
+ }
+}
diff --git a/modules/ppcp-order-tracking/src/Integration/WcShippingTaxIntegration.php b/modules/ppcp-order-tracking/src/Integration/WcShippingTaxIntegration.php
new file mode 100644
index 000000000..a808c6ade
--- /dev/null
+++ b/modules/ppcp-order-tracking/src/Integration/WcShippingTaxIntegration.php
@@ -0,0 +1,159 @@
+shipment_factory = $shipment_factory;
+ $this->logger = $logger;
+ $this->endpoint = $endpoint;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function integrate(): void {
+
+ add_filter(
+ 'rest_post_dispatch',
+ function( WP_HTTP_Response $response, WP_REST_Server $server, WP_REST_Request $request ): WP_HTTP_Response {
+ if ( ! apply_filters( 'woocommerce_paypal_payments_sync_wc_shipping_tax', true ) ) {
+ return $response;
+ }
+
+ $params = $request->get_params();
+ $order_id = (int) ( $params['order_id'] ?? 0 );
+ $label_id = (int) ( $params['label_ids'] ?? 0 );
+
+ if ( ! $order_id || "/wc/v1/connect/label/{$order_id}/{$label_id}" !== $request->get_route() ) {
+ return $response;
+ }
+
+ $data = $response->get_data() ?? array();
+ $labels = $data['labels'] ?? array();
+
+ foreach ( $labels as $label ) {
+ $tracking_number = $label['tracking'] ?? '';
+ if ( ! $tracking_number ) {
+ continue;
+ }
+
+ $wc_order = wc_get_order( $order_id );
+ if ( ! is_a( $wc_order, WC_Order::class ) ) {
+ continue;
+ }
+
+ $transaction_id = $wc_order->get_transaction_id();
+ $carrier = $label['carrier_id'] ?? $label['service_name'] ?? '';
+ $items = array_map( 'intval', $label['product_ids'] ?? array() );
+
+ if ( ! $carrier || ! $transaction_id ) {
+ continue;
+ }
+
+ $this->sync_tracking( $order_id, $transaction_id, $tracking_number, $carrier, $items );
+ }
+
+ return $response;
+ },
+ 10,
+ 3
+ );
+
+ }
+
+ /**
+ * Syncs (add | update) the PayPal tracking with given info.
+ *
+ * @param int $wc_order_id The WC order ID.
+ * @param string $transaction_id The transaction ID.
+ * @param string $tracking_number The tracking number.
+ * @param string $carrier The shipment carrier.
+ * @param int[] $items The list of line items IDs.
+ * @return void
+ */
+ protected function sync_tracking(
+ int $wc_order_id,
+ string $transaction_id,
+ string $tracking_number,
+ string $carrier,
+ array $items
+ ) {
+ try {
+ $ppcp_shipment = $this->shipment_factory->create_shipment(
+ $wc_order_id,
+ $transaction_id,
+ $tracking_number,
+ 'SHIPPED',
+ 'OTHER',
+ $carrier,
+ $items
+ );
+
+ $tracking_information = $this->endpoint->get_tracking_information( $wc_order_id, $tracking_number );
+
+ $tracking_information
+ ? $this->endpoint->update_tracking_information( $ppcp_shipment, $wc_order_id )
+ : $this->endpoint->add_tracking_information( $ppcp_shipment, $wc_order_id );
+
+ } catch ( Exception $exception ) {
+ $this->logger->error( "Couldn't sync tracking information: " . $exception->getMessage() );
+ }
+ }
+}
diff --git a/modules/ppcp-order-tracking/src/Integration/YithShipmentIntegration.php b/modules/ppcp-order-tracking/src/Integration/YithShipmentIntegration.php
new file mode 100644
index 000000000..57a36babf
--- /dev/null
+++ b/modules/ppcp-order-tracking/src/Integration/YithShipmentIntegration.php
@@ -0,0 +1,114 @@
+shipment_factory = $shipment_factory;
+ $this->logger = $logger;
+ $this->endpoint = $endpoint;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function integrate(): void {
+
+ add_action(
+ 'woocommerce_process_shop_order_meta',
+ function( int $order_id ) {
+ if ( ! apply_filters( 'woocommerce_paypal_payments_sync_ywot_tracking', true ) ) {
+ return;
+ }
+
+ $wc_order = wc_get_order( $order_id );
+ if ( ! is_a( $wc_order, WC_Order::class ) ) {
+ return;
+ }
+
+ $transaction_id = $wc_order->get_transaction_id();
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
+ $tracking_number = wc_clean( wp_unslash( $_POST['ywot_tracking_code'] ?? '' ) );
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
+ $carrier = wc_clean( wp_unslash( $_POST['ywot_carrier_name'] ?? '' ) );
+
+ if ( ! $tracking_number || ! is_string( $tracking_number ) || ! $carrier || ! is_string( $carrier ) || ! $transaction_id ) {
+ return;
+ }
+
+ try {
+ $ppcp_shipment = $this->shipment_factory->create_shipment(
+ $order_id,
+ $transaction_id,
+ $tracking_number,
+ 'SHIPPED',
+ 'OTHER',
+ $carrier,
+ array()
+ );
+
+ $tracking_information = $this->endpoint->get_tracking_information( $order_id, $tracking_number );
+
+ $tracking_information
+ ? $this->endpoint->update_tracking_information( $ppcp_shipment, $order_id )
+ : $this->endpoint->add_tracking_information( $ppcp_shipment, $order_id );
+
+ } catch ( Exception $exception ) {
+ $this->logger->error( "Couldn't sync tracking information: " . $exception->getMessage() );
+ }
+ },
+ 500,
+ 1
+ );
+ }
+}
diff --git a/modules/ppcp-order-tracking/src/OrderTrackingModule.php b/modules/ppcp-order-tracking/src/OrderTrackingModule.php
index 94dc1ffd0..ae6bfecc2 100644
--- a/modules/ppcp-order-tracking/src/OrderTrackingModule.php
+++ b/modules/ppcp-order-tracking/src/OrderTrackingModule.php
@@ -102,7 +102,7 @@ class OrderTrackingModule implements ModuleInterface {
__( 'PayPal Package Tracking', 'woocommerce-paypal-payments' ),
array( $meta_box_renderer, 'render' ),
$screen,
- 'normal'
+ 'side'
);
},
10,
diff --git a/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php
index a4d49e3e9..a979618bc 100644
--- a/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php
+++ b/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php
@@ -171,13 +171,17 @@ class CardButtonGateway extends \WC_Payment_Gateway {
$this->payment_token_repository = $payment_token_repository;
$this->logger = $logger;
- if ( $this->onboarded ) {
- $this->supports = array( 'refunds' );
- }
- if ( $this->gateways_enabled() ) {
- $this->supports = array(
- 'refunds',
- 'products',
+ $this->supports = array(
+ 'refunds',
+ 'products',
+ );
+
+ if (
+ ( $this->config->has( 'vault_enabled' ) && $this->config->get( 'vault_enabled' ) )
+ || ( $this->config->has( 'subscriptions_mode' ) && $this->config->get( 'subscriptions_mode' ) === 'subscriptions_api' )
+ ) {
+ array_push(
+ $this->supports,
'subscriptions',
'subscription_cancellation',
'subscription_suspension',
@@ -187,7 +191,7 @@ class CardButtonGateway extends \WC_Payment_Gateway {
'subscription_payment_method_change',
'subscription_payment_method_change_customer',
'subscription_payment_method_change_admin',
- 'multiple_subscriptions',
+ 'multiple_subscriptions'
);
}
diff --git a/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php b/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php
index 7f752ef18..3cf401b29 100644
--- a/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php
+++ b/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php
@@ -18,21 +18,6 @@ use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException;
* Trait ProcessPaymentTrait
*/
trait ProcessPaymentTrait {
- /**
- * Checks if PayPal or Credit Card gateways are enabled.
- *
- * @return bool Whether any of the gateways is enabled.
- */
- protected function gateways_enabled(): bool {
- if ( $this->config->has( 'enabled' ) && $this->config->get( 'enabled' ) ) {
- return true;
- }
- if ( $this->config->has( 'dcc_enabled' ) && $this->config->get( 'dcc_enabled' ) ) {
- return true;
- }
- return false;
- }
-
/**
* Handles the payment failure.
*
diff --git a/readme.txt b/readme.txt
index 8e13325fd..7df738d23 100644
--- a/readme.txt
+++ b/readme.txt
@@ -180,7 +180,7 @@ If you encounter issues with the PayPal buttons not appearing after an update, p
== Changelog ==
-= 2.4.0 - xxxx-xx-xx =
+= 2.4.0 - 2023-10-31 =
* Fix - Mini-Cart Bug cause of wrong DOM-Structure in v2.3.1 #1735
* Fix - ACDC disappearing after plugin updates #1751
* Fix - Subscription module hooks #1748
diff --git a/tests/PHPUnit/Button/Endpoint/ChangeCartEndpointTest.php b/tests/PHPUnit/Button/Endpoint/ChangeCartEndpointTest.php
index 64b74d91d..427e9ce79 100644
--- a/tests/PHPUnit/Button/Endpoint/ChangeCartEndpointTest.php
+++ b/tests/PHPUnit/Button/Endpoint/ChangeCartEndpointTest.php
@@ -6,6 +6,7 @@ namespace WooCommerce\PayPalCommerce\Button\Endpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
+use WooCommerce\PayPalCommerce\Button\Helper\CartProductsHelper;
use WooCommerce\PayPalCommerce\TestCase;
use Mockery;
use WooCommerce\WooCommerce\Logging\Logger\NullLogger;
@@ -91,12 +92,16 @@ class ChangeCartEndpointTest extends TestCase
->expects('from_wc_cart')
->andReturn($pu);
+ $productsHelper = new CartProductsHelper(
+ $dataStore
+ );
+
$testee = new ChangeCartEndpoint(
$cart,
$shipping,
$requestData,
$purchase_unit_factory,
- $dataStore,
+ $productsHelper,
new NullLogger()
);