From 224288c42c4b056c15436598583e2dfe4106984b Mon Sep 17 00:00:00 2001
From: James Allan
Date: Mon, 18 Sep 2023 09:39:59 +1000
Subject: [PATCH 01/98] Fix issues when the Stripe Billing `is_migrating()`
function would return false if the one of the actions was actively running
(#7227)
---
changelog/fix-subscription-migration-in-progress-check | 5 +++++
.../class-wc-payments-subscriptions-migrator.php | 6 +++---
2 files changed, 8 insertions(+), 3 deletions(-)
create mode 100644 changelog/fix-subscription-migration-in-progress-check
diff --git a/changelog/fix-subscription-migration-in-progress-check b/changelog/fix-subscription-migration-in-progress-check
new file mode 100644
index 00000000000..650e1b1d627
--- /dev/null
+++ b/changelog/fix-subscription-migration-in-progress-check
@@ -0,0 +1,5 @@
+Significance: patch
+Type: dev
+Comment: This change fixes a bug in unreleased code. No changelog entry needed.
+
+
diff --git a/includes/subscriptions/class-wc-payments-subscriptions-migrator.php b/includes/subscriptions/class-wc-payments-subscriptions-migrator.php
index 5165653bd39..d8fa2b7738a 100644
--- a/includes/subscriptions/class-wc-payments-subscriptions-migrator.php
+++ b/includes/subscriptions/class-wc-payments-subscriptions-migrator.php
@@ -764,13 +764,13 @@ public function get_stripe_billing_subscription_count() {
/**
* Determines if a migration is currently in progress.
*
- * A migration is considered to be in progress if either the initial migration action or an individual subscription
- * actions are scheduled.
+ * A migration is considered to be in progress if the initial migration action or an individual subscription
+ * action (or retry) is scheduled.
*
* @return bool True if a migration is in progress, false otherwise.
*/
public function is_migrating() {
- return is_numeric( as_next_scheduled_action( $this->scheduled_hook ) ) || is_numeric( as_next_scheduled_action( $this->migrate_hook ) ) || is_numeric( as_next_scheduled_action( $this->migrate_hook . '_retry' ) );
+ return (bool) as_next_scheduled_action( $this->scheduled_hook ) || (bool) as_next_scheduled_action( $this->migrate_hook ) || (bool) as_next_scheduled_action( $this->migrate_hook . '_retry' );
}
/**
From 1664440bbbcc5b65e017116ddddc06976f6e3646 Mon Sep 17 00:00:00 2001
From: Jesse Pearson
Date: Mon, 18 Sep 2023 11:52:13 -0300
Subject: [PATCH 02/98] Disable automatic currency switching and switcher
widgets on pay_for_order page (#7152)
Co-authored-by: Taha Paksu <3295+tpaksu@users.noreply.github.com>
---
...disable-currency-switcher-on-pay-for-order | 4 ++
includes/multi-currency/Compatibility.php | 33 ++++++++++++++++-
.../WooCommerceSubscriptions.php | 6 +--
.../multi-currency/CurrencySwitcherBlock.php | 10 ++++-
.../multi-currency/CurrencySwitcherWidget.php | 2 +-
includes/multi-currency/MultiCurrency.php | 4 +-
.../test-class-woocommerce-subscriptions.php | 22 +++++------
.../test-class-compatibility.php | 27 ++++++++++++++
.../test-class-currency-switcher-block.php | 37 ++++++++++++++++++-
.../test-class-currency-switcher-widget.php | 16 ++++----
.../test-class-multi-currency.php | 37 +++++++++++++++++++
11 files changed, 167 insertions(+), 31 deletions(-)
create mode 100644 changelog/fix-5031-disable-currency-switcher-on-pay-for-order
diff --git a/changelog/fix-5031-disable-currency-switcher-on-pay-for-order b/changelog/fix-5031-disable-currency-switcher-on-pay-for-order
new file mode 100644
index 00000000000..1a8faa30591
--- /dev/null
+++ b/changelog/fix-5031-disable-currency-switcher-on-pay-for-order
@@ -0,0 +1,4 @@
+Significance: minor
+Type: fix
+
+Disable automatic currency switching and switcher widgets on pay_for_order page.
diff --git a/includes/multi-currency/Compatibility.php b/includes/multi-currency/Compatibility.php
index 31ecbfcb0f9..c88fc94cdcd 100644
--- a/includes/multi-currency/Compatibility.php
+++ b/includes/multi-currency/Compatibility.php
@@ -87,12 +87,41 @@ public function override_selected_currency() {
}
/**
- * Checks to see if the widgets should be hidden.
+ * Deprecated method, please use should_disable_currency_switching.
*
* @return bool False if it shouldn't be hidden, true if it should.
*/
public function should_hide_widgets(): bool {
- return apply_filters( MultiCurrency::FILTER_PREFIX . 'should_hide_widgets', false );
+ wc_deprecated_function( __FUNCTION__, '6.5.0', 'Compatibility::should_disable_currency_switching' );
+ return $this->should_disable_currency_switching();
+ }
+
+ /**
+ * Checks to see if currency switching should be disabled, such as the widgets and the automatic geolocation switching.
+ *
+ * @return bool False if no, true if yes.
+ */
+ public function should_disable_currency_switching(): bool {
+ $return = false;
+
+ /**
+ * If the pay_for_order parameter is set, we disable currency switching.
+ *
+ * WooCommerce itself handles all the heavy lifting and verification on the Order Pay page, we just need to
+ * make sure the currency switchers are not displayed. This is due to once the order is created, the currency
+ * itself should remain static.
+ */
+ if ( isset( $_GET['pay_for_order'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ $return = true;
+ }
+
+ // If someone has hooked into the deprecated filter, throw a notice and then apply the filtering.
+ if ( has_action( MultiCurrency::FILTER_PREFIX . 'should_hide_widgets' ) ) {
+ wc_deprecated_hook( MultiCurrency::FILTER_PREFIX . 'should_hide_widgets', '6.5.0', MultiCurrency::FILTER_PREFIX . 'should_disable_currency_switching' );
+ $return = apply_filters( MultiCurrency::FILTER_PREFIX . 'should_hide_widgets', $return );
+ }
+
+ return apply_filters( MultiCurrency::FILTER_PREFIX . 'should_disable_currency_switching', $return );
}
/**
diff --git a/includes/multi-currency/Compatibility/WooCommerceSubscriptions.php b/includes/multi-currency/Compatibility/WooCommerceSubscriptions.php
index e63c7ea54c3..74cc36ecc15 100644
--- a/includes/multi-currency/Compatibility/WooCommerceSubscriptions.php
+++ b/includes/multi-currency/Compatibility/WooCommerceSubscriptions.php
@@ -51,7 +51,7 @@ protected function init() {
add_filter( MultiCurrency::FILTER_PREFIX . 'override_selected_currency', [ $this, 'override_selected_currency' ], 50 );
add_filter( MultiCurrency::FILTER_PREFIX . 'should_convert_product_price', [ $this, 'should_convert_product_price' ], 50, 2 );
add_filter( MultiCurrency::FILTER_PREFIX . 'should_convert_coupon_amount', [ $this, 'should_convert_coupon_amount' ], 50, 2 );
- add_filter( MultiCurrency::FILTER_PREFIX . 'should_hide_widgets', [ $this, 'should_hide_widgets' ], 50 );
+ add_filter( MultiCurrency::FILTER_PREFIX . 'should_disable_currency_switching', [ $this, 'should_disable_currency_switching' ], 50 );
}
}
}
@@ -254,13 +254,13 @@ public function should_convert_coupon_amount( bool $return, $coupon ): bool {
}
/**
- * Checks to see if the widgets should be hidden.
+ * Checks to see if currency switching should be disabled.
*
* @param bool $return Whether widgets should be hidden or not. Default is false.
*
* @return bool
*/
- public function should_hide_widgets( bool $return ): bool {
+ public function should_disable_currency_switching( bool $return ): bool {
// If it's already true, return it.
if ( $return ) {
return $return;
diff --git a/includes/multi-currency/CurrencySwitcherBlock.php b/includes/multi-currency/CurrencySwitcherBlock.php
index 6caf8bf7515..92a6e7e2ad1 100644
--- a/includes/multi-currency/CurrencySwitcherBlock.php
+++ b/includes/multi-currency/CurrencySwitcherBlock.php
@@ -117,7 +117,13 @@ public function init_block_widget() {
* @return string The content to be displayed inside the block widget.
*/
public function render_block_widget( $block_attributes, $content ): string {
- if ( $this->compatibility->should_hide_widgets() ) {
+ if ( $this->compatibility->should_disable_currency_switching() ) {
+ return '';
+ }
+
+ $enabled_currencies = $this->multi_currency->get_enabled_currencies();
+
+ if ( 1 === count( $enabled_currencies ) ) {
return '';
}
@@ -133,7 +139,7 @@ public function render_block_widget( $block_attributes, $content ): string {
$widget_content .= '';
$widget_content .= '
{ storeSettings.site_theme === 'Storefront' ? (
-
- ) : null }
-
- { createInterpolateElement(
- sprintf(
- /* translators: %s: url to the widgets page */
- __(
- 'A currency switcher is also available in your widgets. ' +
- 'Configure now',
+ <>
+ ,
- }
- ) }
-
+ ) }
+ />
+
+ { createInterpolateElement(
+ sprintf(
+ /* translators: %s: url to the widgets page */
+ __(
+ 'A currency switcher is also available in your widgets. ' +
+ '
Configure now',
+ 'woocommerce-payments'
+ ),
+ 'widgets.php'
+ ),
+ {
+ linkToWidgets: (
+ // eslint-disable-next-line jsx-a11y/anchor-has-content
+
+ ),
+ }
+ ) }
+
+ >
+ ) : null }
Date: Thu, 5 Oct 2023 14:05:53 +0200
Subject: [PATCH 67/98] Fix payment method section missing for Affirm and
Afterpay on transaction details page (#7345)
---
...ge-for-affirm-and-afterpay-payment-methods | 4 +
.../payment-method/affirm/index.js | 91 +++++++++++++++++++
.../payment-method/afterpay-clearpay/index.js | 90 ++++++++++++++++++
.../payment-details/payment-method/index.js | 18 ++--
4 files changed, 196 insertions(+), 7 deletions(-)
create mode 100644 changelog/fix-7343-missing-payment-details-section-in-payment-details-page-for-affirm-and-afterpay-payment-methods
create mode 100644 client/payment-details/payment-method/affirm/index.js
create mode 100644 client/payment-details/payment-method/afterpay-clearpay/index.js
diff --git a/changelog/fix-7343-missing-payment-details-section-in-payment-details-page-for-affirm-and-afterpay-payment-methods b/changelog/fix-7343-missing-payment-details-section-in-payment-details-page-for-affirm-and-afterpay-payment-methods
new file mode 100644
index 00000000000..d4be499a665
--- /dev/null
+++ b/changelog/fix-7343-missing-payment-details-section-in-payment-details-page-for-affirm-and-afterpay-payment-methods
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Payment method section missing for Affirm and Afterpay on transaction details page
diff --git a/client/payment-details/payment-method/affirm/index.js b/client/payment-details/payment-method/affirm/index.js
new file mode 100644
index 00000000000..b53a55a0168
--- /dev/null
+++ b/client/payment-details/payment-method/affirm/index.js
@@ -0,0 +1,91 @@
+/** @format **/
+
+/**
+ * External dependencies
+ */
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies.
+ */
+import Detail from '../detail';
+
+/**
+ * Extracts and formats payment method details from a charge.
+ *
+ * @param {Object} charge The charge object.
+ * @return {Object} A flat hash of all necessary values.
+ */
+const formatPaymentMethodDetails = ( charge ) => {
+ const { billing_details: billingDetails, payment_method: id } = charge;
+
+ const { name, email, formatted_address: formattedAddress } = billingDetails;
+
+ return {
+ id,
+ name,
+ email,
+ formattedAddress,
+ };
+};
+
+/**
+ * Placeholders to display while loading.
+ */
+const paymentMethodPlaceholders = {
+ id: 'id placeholder',
+ name: 'name placeholder',
+ email: 'email placeholder',
+ formattedAddress: 'address placeholder',
+};
+
+const CardDetails = ( { charge = {}, isLoading } ) => {
+ const details =
+ charge && charge.payment_method_details
+ ? formatPaymentMethodDetails( charge )
+ : paymentMethodPlaceholders;
+
+ const { id, name, email, formattedAddress } = details;
+
+ return (
+
+
+
+ { !! id ? id : '–' }
+
+
+
+
+
+ { name || '–' }
+
+
+
+ { email || '–' }
+
+
+
+
+
+
+
+ );
+};
+
+export default CardDetails;
diff --git a/client/payment-details/payment-method/afterpay-clearpay/index.js b/client/payment-details/payment-method/afterpay-clearpay/index.js
new file mode 100644
index 00000000000..99b6f20dc26
--- /dev/null
+++ b/client/payment-details/payment-method/afterpay-clearpay/index.js
@@ -0,0 +1,90 @@
+/** @format **/
+
+/**
+ * External dependencies
+ */
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies.
+ */
+import Detail from '../detail';
+
+/**
+ * Extracts and formats payment method details from a charge.
+ *
+ * @param {Object} charge The charge object.
+ * @return {Object} A flat hash of all necessary values.
+ */
+const formatPaymentMethodDetails = ( charge ) => {
+ const { billing_details: billingDetails, payment_method: id } = charge;
+
+ const { name, email, formatted_address: formattedAddress } = billingDetails;
+
+ return {
+ id,
+ name,
+ email,
+ formattedAddress,
+ };
+};
+
+/**
+ * Placeholders to display while loading.
+ */
+const paymentMethodPlaceholders = {
+ id: 'id placeholder',
+ name: 'name placeholder',
+ email: 'email placeholder',
+ formattedAddress: 'address placeholder',
+};
+
+const CardDetails = ( { charge = {}, isLoading } ) => {
+ const details =
+ charge && charge.payment_method_details
+ ? formatPaymentMethodDetails( charge )
+ : paymentMethodPlaceholders;
+
+ const { id, name, email, formattedAddress } = details;
+
+ return (
+
+
+
+ { !! id ? id : '–' }
+
+
+
+
+ { name || '–' }
+
+
+
+ { email || '–' }
+
+
+
+
+
+
+
+ );
+};
+
+export default CardDetails;
diff --git a/client/payment-details/payment-method/index.js b/client/payment-details/payment-method/index.js
index a20981a583b..984ab87772b 100644
--- a/client/payment-details/payment-method/index.js
+++ b/client/payment-details/payment-method/index.js
@@ -3,37 +3,41 @@
/**
* External dependencies
*/
-import { __ } from '@wordpress/i18n';
import { Card, CardBody, CardHeader } from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
/**
* Internal dependencies.
*/
import Loadable from 'components/loadable';
-import CardDetails from './card';
-import CardPresentDetails from './card-present';
+import AffirmDetails from './affirm';
+import AfterpayClearpayDetails from './afterpay-clearpay';
import BancontactDetails from './bancontact';
import BecsDetails from './becs';
+import CardDetails from './card';
+import CardPresentDetails from './card-present';
import EpsDetails from './eps';
import GiropayDetails from './giropay';
import IdealDetails from './ideal';
+import KlarnaDetails from './klarna';
import P24Details from './p24';
import SepaDetails from './sepa';
import SofortDetails from './sofort';
-import KlarnaDetails from './klarna';
const detailsComponentMap = {
- card: CardDetails,
- card_present: CardPresentDetails,
+ affirm: AffirmDetails,
+ afterpay_clearpay: AfterpayClearpayDetails,
au_becs_debit: BecsDetails,
bancontact: BancontactDetails,
+ card: CardDetails,
+ card_present: CardPresentDetails,
eps: EpsDetails,
giropay: GiropayDetails,
ideal: IdealDetails,
+ klarna: KlarnaDetails,
p24: P24Details,
sepa_debit: SepaDetails,
sofort: SofortDetails,
- klarna: KlarnaDetails,
};
const PaymentDetailsPaymentMethod = ( { charge = {}, isLoading } ) => {
From a09f17fa3539e1bd09cc169b3782376c98cdefe0 Mon Sep 17 00:00:00 2001
From: Radoslav Georgiev
Date: Thu, 5 Oct 2023 15:07:22 +0300
Subject: [PATCH 68/98] RPP: Extract gateway methods (#7333)
---
changelog/rpp-extract-gateway-methods | 4 +
...st-payments-payment-intents-controller.php | 34 +-
includes/class-wc-payment-gateway-wcpay.php | 135 +----
includes/class-wc-payments.php | 9 +-
.../GenericServiceProvider.php | 14 +-
src/Internal/Proxy/HooksProxy.php | 12 +
src/Internal/Proxy/LegacyProxy.php | 2 +-
src/Internal/Service/Level3Service.php | 203 +++++++
src/Internal/Service/OrderService.php | 116 +++-
tests/WCPAY_UnitTestCase.php | 2 +-
...st-payments-payment-intents-controller.php | 44 +-
.../test-class-upe-payment-gateway.php | 165 +-----
.../test-class-upe-split-payment-gateway.php | 137 +----
.../src/Internal/Proxy/HooksProxyTest.php | 14 +
.../Internal/Service/Level3ServiceTest.php | 518 +++++++++++++++++
.../src/Internal/Service/OrderServiceTest.php | 186 +++++-
...wc-payment-gateway-wcpay-payment-types.php | 37 +-
...-payment-gateway-wcpay-process-payment.php | 27 +-
...ay-wcpay-subscriptions-process-payment.php | 35 +-
...wc-payment-gateway-wcpay-subscriptions.php | 46 +-
.../test-class-wc-payment-gateway-wcpay.php | 537 ++----------------
21 files changed, 1269 insertions(+), 1008 deletions(-)
create mode 100644 changelog/rpp-extract-gateway-methods
create mode 100644 src/Internal/Service/Level3Service.php
create mode 100644 tests/unit/src/Internal/Service/Level3ServiceTest.php
diff --git a/changelog/rpp-extract-gateway-methods b/changelog/rpp-extract-gateway-methods
new file mode 100644
index 00000000000..90a2391c727
--- /dev/null
+++ b/changelog/rpp-extract-gateway-methods
@@ -0,0 +1,4 @@
+Significance: minor
+Type: dev
+
+Extracting payment metadata and level 3 data generation into services.
diff --git a/includes/admin/class-wc-rest-payments-payment-intents-controller.php b/includes/admin/class-wc-rest-payments-payment-intents-controller.php
index bf511ed5d47..58ea31b456b 100644
--- a/includes/admin/class-wc-rest-payments-payment-intents-controller.php
+++ b/includes/admin/class-wc-rest-payments-payment-intents-controller.php
@@ -9,6 +9,8 @@
use WCPay\Logger;
use WCPay\Exceptions\Rest_Request_Exception;
use WCPay\Constants\Payment_Type;
+use WCPay\Internal\Service\Level3Service;
+use WCPay\Internal\Service\OrderService;
defined( 'ABSPATH' ) || exit;
@@ -24,6 +26,20 @@ class WC_REST_Payments_Payment_Intents_Controller extends WC_Payments_REST_Contr
*/
private $gateway;
+ /**
+ * Order service instance.
+ *
+ * @var OrderService
+ */
+ private $order_service;
+
+ /**
+ * Level3 service instance.
+ *
+ * @var Level3Service
+ */
+ private $level3_service;
+
/**
* Endpoint path.
*
@@ -61,10 +77,20 @@ public function register_routes() {
*
* @param WC_Payments_API_Client $api_client WooCommerce Payments API client.
* @param WC_Payment_Gateway_WCPay $gateway WooCommerce Payments payment gateway.
+ * @param OrderService $order_service The new order servie.
+ * @param Level3Service $level3_service Level3 service instance.
*/
- public function __construct( WC_Payments_API_Client $api_client, WC_Payment_Gateway_WCPay $gateway ) {
+ public function __construct(
+ WC_Payments_API_Client $api_client,
+ WC_Payment_Gateway_WCPay $gateway,
+ OrderService $order_service,
+ Level3Service $level3_service
+ ) {
parent::__construct( $api_client );
- $this->gateway = $gateway;
+
+ $this->gateway = $gateway;
+ $this->order_service = $order_service;
+ $this->level3_service = $level3_service;
}
/**
@@ -101,11 +127,11 @@ public function create_payment_intent( $request ) {
$wcpay_server_request->set_currency_code( $currency );
$wcpay_server_request->set_amount( $amount );
- $metadata = $this->gateway->get_metadata_from_order( $order, Payment_Type::SINGLE() );
+ $metadata = $this->order_service->get_payment_metadata( $order_id, Payment_Type::SINGLE() );
$wcpay_server_request->set_metadata( $metadata );
$wcpay_server_request->set_customer( $request->get_param( 'customer' ) );
- $wcpay_server_request->set_level3( $this->gateway->get_level3_data_from_order( $order ) );
+ $wcpay_server_request->set_level3( $this->level3_service->get_data_from_order( $order_id ) );
$wcpay_server_request->set_payment_method( $request->get_param( 'payment_method' ) );
$wcpay_server_request->set_payment_method_types( [ 'card' ] );
$wcpay_server_request->set_off_session( true );
diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php
index 7dbfb20b537..099a51db2e4 100644
--- a/includes/class-wc-payment-gateway-wcpay.php
+++ b/includes/class-wc-payment-gateway-wcpay.php
@@ -45,6 +45,8 @@
use WCPay\Internal\Payment\Factor;
use WCPay\Internal\Payment\Router;
use WCPay\Internal\Payment\State\CompletedState;
+use WCPay\Internal\Service\Level3Service;
+use WCPay\Internal\Service\OrderService;
/**
* Gateway class for WooPayments
@@ -1614,7 +1616,10 @@ public function set_payment_method_title_for_order( $order, $payment_method_type
*
* @return array Array of keyed metadata values.
*/
- public function get_metadata_from_order( $order, $payment_type ) {
+ protected function get_metadata_from_order( $order, $payment_type ) {
+ $service = wcpay_get_container()->get( OrderService::class );
+ $metadata = $service->get_payment_metadata( $order->get_id(), $payment_type );
+
if ( $this instanceof UPE_Split_Payment_Gateway ) {
$gateway_type = 'split_upe_with_deferred_intent_creation';
} elseif ( $this instanceof UPE_Payment_Gateway ) {
@@ -1622,27 +1627,9 @@ public function get_metadata_from_order( $order, $payment_type ) {
} else {
$gateway_type = 'legacy_card';
}
- $name = sanitize_text_field( $order->get_billing_first_name() ) . ' ' . sanitize_text_field( $order->get_billing_last_name() );
- $email = sanitize_email( $order->get_billing_email() );
- $metadata = [
- 'customer_name' => $name,
- 'customer_email' => $email,
- 'site_url' => esc_url( get_site_url() ),
- 'order_id' => $order->get_id(),
- 'order_number' => $order->get_order_number(),
- 'order_key' => $order->get_order_key(),
- 'payment_type' => $payment_type,
- 'gateway_type' => $gateway_type,
- 'checkout_type' => $order->get_created_via(),
- 'client_version' => WCPAY_VERSION_NUMBER,
- 'subscription_payment' => 'no',
- ];
+ $metadata['gateway_type'] = $gateway_type;
- if ( 'recurring' === (string) $payment_type && function_exists( 'wcs_order_contains_subscription' ) && wcs_order_contains_subscription( $order, 'any' ) ) {
- $metadata['subscription_payment'] = wcs_order_contains_renewal( $order ) ? 'renewal' : 'initial';
- $metadata['payment_context'] = WC_Payments_Features::should_use_stripe_billing() ? 'wcpay_subscription' : 'regular_subscription';
- }
- return apply_filters( 'wcpay_metadata_from_order', $metadata, $order, $payment_type );
+ return $metadata;
}
/**
@@ -2886,81 +2873,7 @@ public function get_shipping_data_from_order( WC_Order $order ): array {
* @return array The level 3 data to send to Stripe.
*/
public function get_level3_data_from_order( WC_Order $order ): array {
- $merchant_country = $this->account->get_account_country();
- // We do not need to send level3 data if merchant account country is non-US.
- if ( 'US' !== $merchant_country ) {
- return [];
- }
-
- // Get the order items. Don't need their keys, only their values.
- // Order item IDs are used as keys in the original order items array.
- $order_items = array_values( $order->get_items( [ 'line_item', 'fee' ] ) );
- $currency = $order->get_currency();
-
- $process_item = static function( $item ) use ( $currency ) {
- // Check to see if it is a WC_Order_Item_Product or a WC_Order_Item_Fee.
- if ( is_a( $item, 'WC_Order_Item_Product' ) ) {
- $subtotal = $item->get_subtotal();
- $product_id = $item->get_variation_id()
- ? $item->get_variation_id()
- : $item->get_product_id();
- $product_code = substr( $product_id, 0, 12 );
- } else {
- $subtotal = $item->get_total();
- $product_code = substr( sanitize_title( $item->get_name() ), 0, 12 );
- }
-
- $description = substr( $item->get_name(), 0, 26 );
- $quantity = ceil( $item->get_quantity() );
- $tax_amount = WC_Payments_Utils::prepare_amount( $item->get_total_tax(), $currency );
- if ( $subtotal >= 0 ) {
- $unit_cost = WC_Payments_Utils::prepare_amount( $subtotal / $quantity, $currency );
- $discount_amount = WC_Payments_Utils::prepare_amount( $subtotal - $item->get_total(), $currency );
- } else {
- // It's possible to create products with negative price - represent it as free one with discount.
- $discount_amount = abs( WC_Payments_Utils::prepare_amount( $subtotal / $quantity, $currency ) );
- $unit_cost = 0;
- }
-
- return (object) [
- 'product_code' => (string) $product_code, // Up to 12 characters that uniquely identify the product.
- 'product_description' => $description, // Up to 26 characters long describing the product.
- 'unit_cost' => $unit_cost, // Cost of the product, in cents, as a non-negative integer.
- 'quantity' => $quantity, // The number of items of this type sold, as a non-negative integer.
- 'tax_amount' => $tax_amount, // The amount of tax this item had added to it, in cents, as a non-negative integer.
- 'discount_amount' => $discount_amount, // The amount an item was discounted—if there was a sale,for example, as a non-negative integer.
- ];
- };
- $items_to_send = array_map( $process_item, $order_items );
-
- if ( count( $items_to_send ) > 200 ) {
- // If more than 200 items are present, bundle the last ones in a single item.
- $items_to_send = array_merge(
- array_slice( $items_to_send, 0, 199 ),
- [ $this->bundle_level3_data_from_items( array_slice( $items_to_send, 200 ) ) ]
- );
- }
-
- $level3_data = [
- 'merchant_reference' => (string) $order->get_id(), // An alphanumeric string of up to characters in length. This unique value is assigned by the merchant to identify the order. Also known as an “Order ID”.
- 'customer_reference' => (string) $order->get_id(),
- 'shipping_amount' => WC_Payments_Utils::prepare_amount( (float) $order->get_shipping_total() + (float) $order->get_shipping_tax(), $currency ), // The shipping cost, in cents, as a non-negative integer.
- 'line_items' => $items_to_send,
- ];
-
- // The customer’s U.S. shipping ZIP code.
- $shipping_address_zip = $order->get_shipping_postcode();
- if ( WC_Payments_Utils::is_valid_us_zip_code( $shipping_address_zip ) ) {
- $level3_data['shipping_address_zip'] = $shipping_address_zip;
- }
-
- // The merchant’s U.S. shipping ZIP code.
- $store_postcode = get_option( 'woocommerce_store_postcode' );
- if ( WC_Payments_Utils::is_valid_us_zip_code( $store_postcode ) ) {
- $level3_data['shipping_from_zip'] = $store_postcode;
- }
-
- return $level3_data;
+ return wcpay_get_container()->get( Level3Service::class )->get_data_from_order( $order->get_id() );
}
/**
@@ -3561,36 +3474,6 @@ protected function should_bump_rate_limiter( string $error_code ): bool {
return in_array( $error_code, [ 'card_declined', 'incorrect_number', 'incorrect_cvc' ], true );
}
- /**
- * Returns a bundle of products passed as an argument. Useful when working with Stripe's level 3 data
- *
- * @param array $items The Stripe's level 3 array of items.
- *
- * @return object A bundle of the products passed.
- */
- public function bundle_level3_data_from_items( array $items ) {
- // Total cost is the sum of each product cost * quantity.
- $items_count = count( $items );
- $total_cost = array_sum(
- array_map(
- function( $cost, $qty ) {
- return $cost * $qty;
- },
- array_column( $items, 'unit_cost' ),
- array_column( $items, 'quantity' )
- )
- );
-
- return (object) [
- 'product_code' => (string) substr( uniqid(), 0, 26 ),
- 'product_description' => "{$items_count} more items",
- 'unit_cost' => $total_cost,
- 'quantity' => 1,
- 'tax_amount' => array_sum( array_column( $items, 'tax_amount' ) ),
- 'discount_amount' => array_sum( array_column( $items, 'discount_amount' ) ),
- ];
- }
-
/**
* Move the email field to the top of the Checkout page.
*
diff --git a/includes/class-wc-payments.php b/includes/class-wc-payments.php
index 4397c4edb10..8d949858b8c 100644
--- a/includes/class-wc-payments.php
+++ b/includes/class-wc-payments.php
@@ -40,6 +40,8 @@
use WCPay\Core\WC_Payments_Customer_Service_API;
use WCPay\Constants\Payment_Method;
use WCPay\Duplicate_Payment_Prevention_Service;
+use WCPay\Internal\Service\Level3Service;
+use WCPay\Internal\Service\OrderService;
use WCPay\WooPay\WooPay_Scheduler;
use WCPay\WooPay\WooPay_Session;
@@ -1073,7 +1075,12 @@ public static function init_rest_api() {
}
include_once WCPAY_ABSPATH . 'includes/admin/class-wc-rest-payments-payment-intents-controller.php';
- $payment_intents_controller = new WC_REST_Payments_Payment_Intents_Controller( self::$api_client, self::get_gateway() );
+ $payment_intents_controller = new WC_REST_Payments_Payment_Intents_Controller(
+ self::$api_client,
+ self::get_gateway(),
+ wcpay_get_container()->get( OrderService::class ),
+ wcpay_get_container()->get( Level3Service::class )
+ );
$payment_intents_controller->register_routes();
include_once WCPAY_ABSPATH . 'includes/admin/class-wc-rest-payments-authorizations-controller.php';
diff --git a/src/Internal/DependencyManagement/ServiceProvider/GenericServiceProvider.php b/src/Internal/DependencyManagement/ServiceProvider/GenericServiceProvider.php
index d759f02db62..a65e4cde721 100644
--- a/src/Internal/DependencyManagement/ServiceProvider/GenericServiceProvider.php
+++ b/src/Internal/DependencyManagement/ServiceProvider/GenericServiceProvider.php
@@ -7,8 +7,12 @@
namespace WCPay\Internal\DependencyManagement\ServiceProvider;
+use WC_Payments_Account;
use WC_Payments_Order_Service;
use WCPay\Internal\DependencyManagement\AbstractServiceProvider;
+use WCPay\Internal\Proxy\HooksProxy;
+use WCPay\Internal\Proxy\LegacyProxy;
+use WCPay\Internal\Service\Level3Service;
use WCPay\Internal\Service\OrderService;
/**
@@ -22,6 +26,7 @@ class GenericServiceProvider extends AbstractServiceProvider {
*/
protected $provides = [
OrderService::class,
+ Level3Service::class,
];
/**
@@ -31,6 +36,13 @@ public function register(): void {
$container = $this->getContainer();
$container->addShared( OrderService::class )
- ->addArgument( WC_Payments_Order_Service::class );
+ ->addArgument( WC_Payments_Order_Service::class )
+ ->addArgument( LegacyProxy::class )
+ ->addArgument( HooksProxy::class );
+
+ $container->addShared( Level3Service::class )
+ ->addArgument( OrderService::class )
+ ->addArgument( WC_Payments_Account::class )
+ ->addArgument( LegacyProxy::class );
}
}
diff --git a/src/Internal/Proxy/HooksProxy.php b/src/Internal/Proxy/HooksProxy.php
index e528c1b38c7..dadb95a5862 100644
--- a/src/Internal/Proxy/HooksProxy.php
+++ b/src/Internal/Proxy/HooksProxy.php
@@ -42,4 +42,16 @@ public function add_filter( $hook_name, $callback, $priority = 10, $accepted_arg
public function add_action( $hook_name, $callback, $priority = 10, $accepted_args = 1 ) {
add_action( $hook_name, $callback, $priority, $accepted_args );
}
+
+ /**
+ * Calls the callback functions that have been added to a filter hook.
+ *
+ * @param string $hook_name The name of the filter hook.
+ * @param mixed $value The value to filter.
+ * @param mixed ...$args Optional. Additional parameters to pass to the callback functions.
+ * @return mixed The filtered value after all hooked functions are applied to it.
+ */
+ public function apply_filters( $hook_name, $value, ...$args ) {
+ return apply_filters( $hook_name, $value, ...$args );
+ }
}
diff --git a/src/Internal/Proxy/LegacyProxy.php b/src/Internal/Proxy/LegacyProxy.php
index 38ea772cc38..9816fcd5878 100644
--- a/src/Internal/Proxy/LegacyProxy.php
+++ b/src/Internal/Proxy/LegacyProxy.php
@@ -10,7 +10,7 @@
/**
* Legacy Proxy
*
- * Used for accessing legacy code, incl. functions, static methods, and globals.
+ * Used for accessing legacy code (everything outside `src`), incl. functions, static methods, and globals.
* Classes are handled through WCPay\Internal\DependencyManagement\DelegateContainer\LegacyContainer.
*/
class LegacyProxy {
diff --git a/src/Internal/Service/Level3Service.php b/src/Internal/Service/Level3Service.php
new file mode 100644
index 00000000000..d4c0e97ca25
--- /dev/null
+++ b/src/Internal/Service/Level3Service.php
@@ -0,0 +1,203 @@
+order_service = $order_service;
+ $this->account = $account;
+ $this->legacy_proxy = $legacy_proxy;
+ }
+
+ /**
+ * Create the level 3 data array to send to Stripe when making a purchase.
+ *
+ * @param int $order_id The order that is being paid for.
+ * @return array The level 3 data to send to the API.
+ * @throws Order_Not_Found_Exception
+ */
+ public function get_data_from_order( int $order_id ): array {
+ $order = $this->order_service->_deprecated_get_order( $order_id );
+
+ $merchant_country = $this->account->get_account_country();
+ // We do not need to send level3 data if merchant account country is non-US.
+ if ( 'US' !== $merchant_country ) {
+ return [];
+ }
+
+ // Get the order items. Don't need their keys, only their values.
+ // Order item IDs are used as keys in the original order items array.
+ $order_items = array_values( $order->get_items( [ 'line_item', 'fee' ] ) );
+ $currency = $order->get_currency();
+
+ $process_item = function( $item ) use ( $currency ) {
+ return $this->process_item( $item, $currency );
+ };
+ $items_to_send = array_map( $process_item, $order_items );
+
+ if ( count( $items_to_send ) > 200 ) {
+ // If more than 200 items are present, bundle the last ones in a single item.
+ $items_to_send = array_merge(
+ array_slice( $items_to_send, 0, 199 ),
+ [ $this->bundle_level3_data_from_items( array_slice( $items_to_send, 200 ) ) ]
+ );
+ }
+
+ $level3_data = [
+ 'merchant_reference' => (string) $order->get_id(), // An alphanumeric string of up to characters in length. This unique value is assigned by the merchant to identify the order. Also known as an “Order ID”.
+ 'customer_reference' => (string) $order->get_id(),
+ 'shipping_amount' => $this->prepare_amount( (float) $order->get_shipping_total() + (float) $order->get_shipping_tax(), $currency ), // The shipping cost, in cents, as a non-negative integer.
+ 'line_items' => $items_to_send,
+ ];
+
+ // The customer’s U.S. shipping ZIP code.
+ $shipping_address_zip = $order->get_shipping_postcode();
+ if ( WC_Payments_Utils::is_valid_us_zip_code( $shipping_address_zip ) ) {
+ $level3_data['shipping_address_zip'] = $shipping_address_zip;
+ }
+
+ // The merchant’s U.S. shipping ZIP code.
+ $store_postcode = $this->legacy_proxy->call_function( 'get_option', 'woocommerce_store_postcode' );
+ if ( WC_Payments_Utils::is_valid_us_zip_code( $store_postcode ) ) {
+ $level3_data['shipping_from_zip'] = $store_postcode;
+ }
+
+ return $level3_data;
+ }
+
+ /**
+ * Processes a single order item.
+ * Based on the queried items, this class should only receive
+ * `WC_Order_Item_Product` or `WC_Order_Item_Fee` line items.
+ *
+ * @param WC_Order_Item_Product|WC_Order_Item_Fee $item Item to process.
+ * @param string $currency Currency to use.
+ * @return \stdClass
+ */
+ private function process_item( WC_Order_Item $item, string $currency ): stdClass {
+ // Check to see if it is a WC_Order_Item_Product or a WC_Order_Item_Fee.
+ if ( $item instanceof WC_Order_Item_Product ) {
+ $subtotal = $item->get_subtotal();
+ $product_id = $item->get_variation_id()
+ ? $item->get_variation_id()
+ : $item->get_product_id();
+ $product_code = substr( $product_id, 0, 12 );
+ } else {
+ $subtotal = $item->get_total();
+ $product_code = substr( sanitize_title( $item->get_name() ), 0, 12 );
+ }
+
+ $description = substr( $item->get_name(), 0, 26 );
+ $quantity = ceil( $item->get_quantity() );
+ $tax_amount = $this->prepare_amount( $item->get_total_tax(), $currency );
+ if ( $subtotal >= 0 ) {
+ $unit_cost = $this->prepare_amount( $subtotal / $quantity, $currency );
+ $discount_amount = $this->prepare_amount( $subtotal - $item->get_total(), $currency );
+ } else {
+ // It's possible to create products with negative price - represent it as free one with discount.
+ $discount_amount = abs( $this->prepare_amount( $subtotal / $quantity, $currency ) );
+ $unit_cost = 0;
+ }
+
+ return (object) [
+ 'product_code' => (string) $product_code, // Up to 12 characters that uniquely identify the product.
+ 'product_description' => $description, // Up to 26 characters long describing the product.
+ 'unit_cost' => $unit_cost, // Cost of the product, in cents, as a non-negative integer.
+ 'quantity' => $quantity, // The number of items of this type sold, as a non-negative integer.
+ 'tax_amount' => $tax_amount, // The amount of tax this item had added to it, in cents, as a non-negative integer.
+ 'discount_amount' => $discount_amount, // The amount an item was discounted—if there was a sale,for example, as a non-negative integer.
+ ];
+ }
+
+ /**
+ * Returns a bundle of products passed as an argument. Useful when working with Stripe's level 3 data
+ *
+ * @param array $items The Stripe's level 3 array of items.
+ *
+ * @return \stdClass A bundle of the products passed.
+ */
+ private function bundle_level3_data_from_items( array $items ) {
+ // Total cost is the sum of each product cost * quantity.
+ $items_count = count( $items );
+ $total_cost = array_sum(
+ array_map(
+ function( $cost, $qty ) {
+ return $cost * $qty;
+ },
+ array_column( $items, 'unit_cost' ),
+ array_column( $items, 'quantity' )
+ )
+ );
+
+ return (object) [
+ 'product_code' => (string) substr( uniqid(), 0, 26 ),
+ 'product_description' => "{$items_count} more items",
+ 'unit_cost' => $total_cost,
+ 'quantity' => 1,
+ 'tax_amount' => array_sum( array_column( $items, 'tax_amount' ) ),
+ 'discount_amount' => array_sum( array_column( $items, 'discount_amount' ) ),
+ ];
+ }
+
+ /**
+ * Returns an API-ready amount based on a currency.
+ *
+ * @param float $amount The base amount.
+ * @param string $currency The currency for the amount.
+ *
+ * @return int The amount in cents.
+ */
+ private function prepare_amount( float $amount, string $currency ): int {
+ return WC_Payments_Utils::prepare_amount( $amount, $currency );
+ }
+}
diff --git a/src/Internal/Service/OrderService.php b/src/Internal/Service/OrderService.php
index e7f7bf6fed9..9b576d99acb 100644
--- a/src/Internal/Service/OrderService.php
+++ b/src/Internal/Service/OrderService.php
@@ -7,8 +7,13 @@
namespace WCPay\Internal\Service;
+use WC_Order;
+use WC_Payments_Features;
use WC_Payments_Order_Service;
+use WCPay\Constants\Payment_Type;
use WCPay\Exceptions\Order_Not_Found_Exception;
+use WCPay\Internal\Proxy\HooksProxy;
+use WCPay\Internal\Proxy\LegacyProxy;
/**
* Service for managing orders.
@@ -28,13 +33,52 @@ class OrderService {
*/
private $legacy_service;
+ /**
+ * Legacy proxy.
+ *
+ * @var LegacyProxy
+ */
+ private $legacy_proxy;
+
+ /**
+ * Hooks proxy.
+ *
+ * @var HooksProxy
+ */
+ private $hooks_proxy;
+
/**
* Class constructor.
*
* @param WC_Payments_Order_Service $legacy_service The legacy order service.
+ * @param LegacyProxy $legacy_proxy Proxy for accessing non-src functionality.
+ * @param HooksProxy $hooks_proxy Proxy for triggering hooks.
*/
- public function __construct( WC_Payments_Order_Service $legacy_service ) {
+ public function __construct(
+ WC_Payments_Order_Service $legacy_service,
+ LegacyProxy $legacy_proxy,
+ HooksProxy $hooks_proxy
+ ) {
$this->legacy_service = $legacy_service;
+ $this->legacy_proxy = $legacy_proxy;
+ $this->hooks_proxy = $hooks_proxy;
+ }
+
+ /**
+ * Retrieves the order object.
+ *
+ * Please restrain from using this method!
+ * It can only be used to (temporarily) provide the order object
+ * to legacy (`includes`) services, which are not adapted to work
+ * with order IDs yet.
+ *
+ * @see https://github.com/Automattic/woocommerce-payments/issues/7367
+ * @param int $order_id ID of the order.
+ * @return WC_Order Order object.
+ * @throws Order_Not_Found_Exception If the order could not be found.
+ */
+ public function _deprecated_get_order( int $order_id ) { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore
+ return $this->get_order( $order_id );
}
/**
@@ -48,4 +92,74 @@ public function __construct( WC_Payments_Order_Service $legacy_service ) {
public function set_payment_method_id( int $order_id, string $payment_method_id ) {
$this->legacy_service->set_payment_method_id_for_order( $order_id, $payment_method_id );
}
+
+ /**
+ * Generates payment metadata from order details.
+ *
+ * @param int $order_id ID of the order.
+ * @param Payment_Type $payment_type Type of the payment (recurring or not).
+ * @return array The metadat athat will be sent to the server.
+ * @throws Order_Not_Found_Exception
+ */
+ public function get_payment_metadata( int $order_id, Payment_Type $payment_type = null ) {
+ $order = $this->get_order( $order_id );
+
+ $name = sanitize_text_field( $order->get_billing_first_name() ) . ' ' . sanitize_text_field( $order->get_billing_last_name() );
+ $email = sanitize_email( $order->get_billing_email() );
+ $metadata = [
+ 'customer_name' => $name,
+ 'customer_email' => $email,
+ 'site_url' => esc_url( get_site_url() ),
+ 'order_id' => $order->get_id(),
+ 'order_number' => $order->get_order_number(),
+ 'order_key' => $order->get_order_key(),
+ 'payment_type' => $payment_type,
+ 'checkout_type' => $order->get_created_via(),
+ 'client_version' => WCPAY_VERSION_NUMBER,
+ 'subscription_payment' => 'no',
+ ];
+
+ if (
+ 'recurring' === (string) $payment_type
+ && $this->legacy_proxy->call_function( 'function_exists', 'wcs_order_contains_subscription' )
+ && $this->legacy_proxy->call_function( 'wcs_order_contains_subscription', $order, 'any' )
+ ) {
+ $use_stripe_billing = $this->legacy_proxy->call_static( WC_Payments_Features::class, 'should_use_stripe_billing' );
+ $is_renewal = $this->legacy_proxy->call_function( 'wcs_order_contains_renewal', $order );
+
+ $metadata['subscription_payment'] = $is_renewal ? 'renewal' : 'initial';
+ $metadata['payment_context'] = $use_stripe_billing ? 'wcpay_subscription' : 'regular_subscription';
+ }
+
+ return $this->hooks_proxy->apply_filters( 'wcpay_metadata_from_order', $metadata, $order, $payment_type );
+ }
+
+ /**
+ * Retrieves the order object.
+ *
+ * This method should be only used internally within this service.
+ * Other `src` methods and services should not access and manipulate
+ * order data directly, utilizing this service instead.
+ *
+ * Unlike the legacy service, this one only accepts integer IDs,
+ * and returns only the `WC_Order` object, no refunds.
+ *
+ * @param int $order_id ID of the order.
+ * @return WC_Order Order object.
+ * @throws Order_Not_Found_Exception If the order could not be found.
+ */
+ private function get_order( int $order_id ): WC_Order {
+ $order = $this->legacy_proxy->call_function( 'wc_get_order', $order_id );
+ if ( ! $order instanceof WC_Order ) {
+ throw new Order_Not_Found_Exception(
+ sprintf(
+ // Translators: %d is the ID of an order.
+ __( 'The requested order (ID %d) was not found.', 'woocommerce-payments' ),
+ $order_id
+ ),
+ 'order_not_found'
+ );
+ }
+ return $order;
+ }
}
diff --git a/tests/WCPAY_UnitTestCase.php b/tests/WCPAY_UnitTestCase.php
index c580aee7758..5e3fc92ccc3 100644
--- a/tests/WCPAY_UnitTestCase.php
+++ b/tests/WCPAY_UnitTestCase.php
@@ -53,7 +53,7 @@ public function createMock( string $original_class_name ): MockObject { // phpcs
* @param WC_Payments_API_Client $api_client_mock Specific API client mock if necessary.
* @param WC_Payments_Http $http_mock Specific HTTP mock if necessary.
*
- * @return Request The mocked request.
+ * @return Request|MockObject The mocked request.
*/
protected function mock_wcpay_request( string $request_class, int $total_api_calls = 1, $request_class_constructor_id = null, $response = null, $api_client_mock = null, $http_mock = null ) {
$http_mock = $http_mock ? $http_mock : $this->createMock( WC_Payments_Http::class );
diff --git a/tests/unit/admin/test-class-wc-rest-payments-payment-intents-controller.php b/tests/unit/admin/test-class-wc-rest-payments-payment-intents-controller.php
index 25fc767055e..731727a3daf 100644
--- a/tests/unit/admin/test-class-wc-rest-payments-payment-intents-controller.php
+++ b/tests/unit/admin/test-class-wc-rest-payments-payment-intents-controller.php
@@ -9,6 +9,8 @@
use WCPay\Core\Server\Request\Create_And_Confirm_Intention;
use WCPay\Exceptions\API_Exception;
use WCPay\Constants\Payment_Type;
+use WCPay\Internal\Service\Level3Service;
+use WCPay\Internal\Service\OrderService;
/**
* WC_REST_Payments_Payment_Intents_Controller unit tests.
@@ -27,6 +29,15 @@ class WC_REST_Payments_Payment_Intents_Controller_Test extends WCPAY_UnitTestCas
*/
private $mock_api_client;
+ /**
+ * @var OrderService|MockObject
+ */
+ private $mock_order_service;
+
+ /**
+ * @var Level3Service|MockObject
+ */
+ private $mock_level3_service;
/**
* @var WC_Payment_Gateway_WCPay|MockObject
@@ -35,18 +46,27 @@ class WC_REST_Payments_Payment_Intents_Controller_Test extends WCPAY_UnitTestCas
public function set_up() {
parent::set_up();
- $this->mock_api_client = $this->createMock( WC_Payments_API_Client::class );
- $this->mock_gateway = $this->createMock( WC_Payment_Gateway_WCPay::class );
- $this->controller = new WC_REST_Payments_Payment_Intents_Controller( $this->mock_api_client, $this->mock_gateway );
+
+ $this->mock_api_client = $this->createMock( WC_Payments_API_Client::class );
+ $this->mock_gateway = $this->createMock( WC_Payment_Gateway_WCPay::class );
+ $this->mock_order_service = $this->createMock( OrderService::class );
+ $this->mock_level3_service = $this->createMock( Level3Service::class );
+
+ $this->controller = new WC_REST_Payments_Payment_Intents_Controller(
+ $this->mock_api_client,
+ $this->mock_gateway,
+ $this->mock_order_service,
+ $this->mock_level3_service
+ );
}
public function test_create_payment_intent_success() {
$current_order = WC_Helper_Order::create_order();
$current_order_id = $current_order->get_id();
- $this->mock_gateway
+ $this->mock_order_service
->expects( $this->once() )
- ->method( 'get_metadata_from_order' )
- ->with( $this->isInstanceOf( WC_Order::class ), Payment_Type::SINGLE() )
+ ->method( 'get_payment_metadata' )
+ ->with( $current_order_id, Payment_Type::SINGLE() )
->willReturn(
[
'order_id' => $current_order->get_id(),
@@ -103,10 +123,10 @@ public function test_create_payment_intent_no_order() {
public function test_create_payment_intent_missing_required_params() {
$current_order = WC_Helper_Order::create_order();
$current_order_id = $current_order->get_id();
- $this->mock_gateway
+ $this->mock_order_service
->expects( $this->once() )
- ->method( 'get_metadata_from_order' )
- ->with( $this->isInstanceOf( WC_Order::class ), Payment_Type::SINGLE() )
+ ->method( 'get_payment_metadata' )
+ ->with( $current_order_id, Payment_Type::SINGLE() )
->willReturn(
[
'order_id' => $current_order->get_id(),
@@ -130,10 +150,10 @@ public function test_create_payment_intent_missing_required_params() {
public function test_create_payment_intent_server_error() {
$current_order = WC_Helper_Order::create_order();
$current_order_id = $current_order->get_id();
- $this->mock_gateway
+ $this->mock_order_service
->expects( $this->once() )
- ->method( 'get_metadata_from_order' )
- ->with( $this->isInstanceOf( WC_Order::class ), Payment_Type::SINGLE() )
+ ->method( 'get_payment_metadata' )
+ ->with( $current_order_id, Payment_Type::SINGLE() )
->willReturn(
[
'order_id' => $current_order->get_id(),
diff --git a/tests/unit/payment-methods/test-class-upe-payment-gateway.php b/tests/unit/payment-methods/test-class-upe-payment-gateway.php
index 07c1e1214d3..fa2b0f3051b 100644
--- a/tests/unit/payment-methods/test-class-upe-payment-gateway.php
+++ b/tests/unit/payment-methods/test-class-upe-payment-gateway.php
@@ -35,6 +35,8 @@
use Exception;
use WCPay\Duplicate_Payment_Prevention_Service;
use WC_Payments_Localization_Service;
+use WCPay\Internal\Service\Level3Service;
+use WCPay\Internal\Service\OrderService;
require_once dirname( __FILE__ ) . '/../helpers/class-wc-helper-site-currency.php';
@@ -304,6 +306,20 @@ public function set_up() {
'wcpay-payment-method' => 'pm_mock',
'payment_method' => UPE_Payment_Gateway::GATEWAY_ID,
];
+
+ // Mock the level3 service to always return an empty array.
+ $mock_level3_service = $this->createMock( Level3Service::class );
+ $mock_level3_service->expects( $this->any() )
+ ->method( 'get_data_from_order' )
+ ->willReturn( [] );
+ wcpay_get_test_container()->replace( Level3Service::class, $mock_level3_service );
+
+ // Mock the order service to always return an empty array for meta.
+ $mock_order_service = $this->createMock( OrderService::class );
+ $mock_order_service->expects( $this->any() )
+ ->method( 'get_payment_metadata' )
+ ->willReturn( [] );
+ wcpay_get_test_container()->replace( OrderService::class, $mock_order_service );
}
/**
@@ -315,6 +331,7 @@ public function tear_down() {
parent::tear_down();
update_option( '_wcpay_feature_upe', '0' );
update_option( '_wcpay_feature_upe_split', '0' );
+ wcpay_get_test_container()->reset_all_replacements();
}
public function test_payment_fields_outputs_fields() {
@@ -383,47 +400,7 @@ public function test_update_payment_intent_adds_customer_save_payment_and_level3
$request->expects( $this->once() )
->method( 'set_metadata' )
- ->with(
- [
- 'customer_name' => 'Jeroen Sormani',
- 'customer_email' => 'admin@example.org',
- 'site_url' => 'http://example.org',
- 'order_id' => $order_id,
- 'order_number' => $order_number,
- 'order_key' => $order->get_order_key(),
- 'payment_type' => Payment_Type::SINGLE(),
- 'gateway_type' => 'legacy_upe',
- 'checkout_type' => '',
- 'client_version' => WCPAY_VERSION_NUMBER,
- 'subscription_payment' => 'no',
- ]
- );
-
- $request->expects( $this->once() )
- ->method( 'set_level3' )
- ->with(
- [
- 'merchant_reference' => (string) $order_id,
- 'customer_reference' => (string) $order_id,
- 'shipping_amount' => 1000.0,
- 'line_items' => [
- (object) [
- 'product_code' => 30,
- 'product_description' => 'Beanie with Logo',
- 'unit_cost' => 1800,
- 'quantity' => 1,
- 'tax_amount' => 270,
- 'discount_amount' => 0,
- 'product_code' => $product_item->get_product_id(),
- 'product_description' => 'Dummy Product',
- 'unit_cost' => 1000.0,
- 'quantity' => 4,
- 'tax_amount' => 0.0,
- 'discount_amount' => 0.0,
- ],
- ],
- ]
- );
+ ->with( [ 'gateway_type' => 'legacy_upe' ] );
$request->expects( $this->once() )
->method( 'format_response' )
@@ -485,47 +462,7 @@ public function test_update_payment_intent_with_selected_upe_payment_method() {
$request->expects( $this->once() )
->method( 'set_metadata' )
- ->with(
- [
- 'customer_name' => 'Jeroen Sormani',
- 'customer_email' => 'admin@example.org',
- 'site_url' => 'http://example.org',
- 'order_id' => $order_id,
- 'order_number' => $order_number,
- 'order_key' => $order->get_order_key(),
- 'payment_type' => Payment_Type::SINGLE(),
- 'gateway_type' => 'legacy_upe',
- 'checkout_type' => '',
- 'client_version' => WCPAY_VERSION_NUMBER,
- 'subscription_payment' => 'no',
- ]
- );
-
- $request->expects( $this->once() )
- ->method( 'set_level3' )
- ->with(
- [
- 'merchant_reference' => (string) $order_id,
- 'shipping_amount' => 1000.0,
- 'line_items' => [
- (object) [
- 'product_code' => 30,
- 'product_description' => 'Beanie with Logo',
- 'unit_cost' => 1800,
- 'quantity' => 1,
- 'tax_amount' => 270,
- 'discount_amount' => 0,
- 'product_code' => $product_item->get_product_id(),
- 'product_description' => 'Dummy Product',
- 'unit_cost' => 1000.0,
- 'quantity' => 4,
- 'tax_amount' => 0.0,
- 'discount_amount' => 0.0,
- ],
- ],
- 'customer_reference' => (string) $order_id,
- ]
- );
+ ->with( [ 'gateway_type' => 'legacy_upe' ] );
$request->expects( $this->once() )
->method( 'format_response' )
@@ -583,47 +520,7 @@ public function test_update_payment_intent_with_payment_country() {
$request->expects( $this->once() )
->method( 'set_metadata' )
- ->with(
- [
- 'customer_name' => 'Jeroen Sormani',
- 'customer_email' => 'admin@example.org',
- 'site_url' => 'http://example.org',
- 'order_id' => $order_id,
- 'order_number' => $order_number,
- 'order_key' => $order->get_order_key(),
- 'payment_type' => Payment_Type::SINGLE(),
- 'gateway_type' => 'legacy_upe',
- 'checkout_type' => '',
- 'client_version' => WCPAY_VERSION_NUMBER,
- 'subscription_payment' => 'no',
- ]
- );
-
- $request->expects( $this->once() )
- ->method( 'set_level3' )
- ->with(
- [
- 'merchant_reference' => (string) $order_id,
- 'shipping_amount' => 1000.0,
- 'line_items' => [
- (object) [
- 'product_code' => 30,
- 'product_description' => 'Beanie with Logo',
- 'unit_cost' => 1800,
- 'quantity' => 1,
- 'tax_amount' => 270,
- 'discount_amount' => 0,
- 'product_code' => $product_item->get_product_id(),
- 'product_description' => 'Dummy Product',
- 'unit_cost' => 1000.0,
- 'quantity' => 4,
- 'tax_amount' => 0.0,
- 'discount_amount' => 0.0,
- ],
- ],
- 'customer_reference' => (string) $order_id,
- ]
- );
+ ->with( [ 'gateway_type' => 'legacy_upe' ] );
$request->expects( $this->once() )
->method( 'format_response' )
@@ -651,13 +548,7 @@ public function test_create_payment_intent_uses_order_amount_if_order() {
$request->expects( $this->once() )
->method( 'set_metadata' )
- ->with(
- $this->callback(
- function( $metadata ) {
- return isset( $metadata['order_number'] );
- }
- )
- );
+ ->with( [ 'order_number' => $order->get_order_number() ] );
$request->expects( $this->once() )
->method( 'set_payment_method_types' );
@@ -764,10 +655,10 @@ public function test_create_payment_intent_with_fingerprint() {
->expects( $this->once() )
->method( 'set_payment_method_types' )
->with( [ 'card' ] );
- $request
- ->expects( $this->once() )
+
+ $request->expects( $this->once() )
->method( 'set_metadata' )
- ->with( [ 'order_number' => $order_id ] );
+ ->with( [ 'order_number' => $order->get_order_number() ] );
$request
->expects( $this->once() )
->method( 'set_fingerprint' )
@@ -1949,13 +1840,7 @@ public function test_process_payment_caches_mimimum_amount_and_displays_error_up
$request->expects( $this->once() )
->method( 'set_metadata' )
- ->with(
- $this->callback(
- function( $metadata ) {
- return is_array( $metadata );
- }
- )
- );
+ ->with( [ 'gateway_type' => 'legacy_upe' ] );
$request->expects( $this->once() )
->method( 'set_level3' )
diff --git a/tests/unit/payment-methods/test-class-upe-split-payment-gateway.php b/tests/unit/payment-methods/test-class-upe-split-payment-gateway.php
index 629699827ed..b5ea19ee3f9 100644
--- a/tests/unit/payment-methods/test-class-upe-split-payment-gateway.php
+++ b/tests/unit/payment-methods/test-class-upe-split-payment-gateway.php
@@ -38,6 +38,8 @@
use WCPay\Payment_Information;
use WC_Payments;
use WC_Payments_Localization_Service;
+use WCPay\Internal\Service\Level3Service;
+use WCPay\Internal\Service\OrderService;
require_once dirname( __FILE__ ) . '/../helpers/class-wc-helper-site-currency.php';
@@ -329,6 +331,20 @@ public function set_up() {
$this->returnValueMap( $get_payment_gateway_by_id_return_value_map )
);
}
+
+ // Mock the level3 service to always return an empty array.
+ $mock_level3_service = $this->createMock( Level3Service::class );
+ $mock_level3_service->expects( $this->any() )
+ ->method( 'get_data_from_order' )
+ ->willReturn( [] );
+ wcpay_get_test_container()->replace( Level3Service::class, $mock_level3_service );
+
+ // Mock the order service to always return an empty array for meta.
+ $mock_order_service = $this->createMock( OrderService::class );
+ $mock_order_service->expects( $this->any() )
+ ->method( 'get_payment_metadata' )
+ ->willReturn( [] );
+ wcpay_get_test_container()->replace( OrderService::class, $mock_order_service );
}
/**
@@ -340,6 +356,7 @@ public function tear_down() {
parent::tear_down();
update_option( '_wcpay_feature_upe_split', '0' );
update_option( '_wcpay_feature_upe_deferred_intent', '0' );
+ wcpay_get_test_container()->reset_all_replacements();
}
/**
@@ -419,7 +436,6 @@ public function test_update_payment_intent_adds_customer_save_payment_and_level3
$order = WC_Helper_Order::create_order();
$order_id = $order->get_id();
$order_number = $order->get_order_number();
- $product_item = current( $order->get_items( 'line_item' ) );
$user = '';
$customer_id = 'cus_mock';
$save_payment_method = true;
@@ -431,42 +447,6 @@ public function test_update_payment_intent_adds_customer_save_payment_and_level3
->expects( $this->never() )
->method( 'create_customer_for_user' );
- $metadata = [
- 'customer_name' => 'Jeroen Sormani',
- 'customer_email' => 'admin@example.org',
- 'site_url' => 'http://example.org',
- 'order_id' => $order_id,
- 'order_number' => $order_number,
- 'order_key' => $order->get_order_key(),
- 'payment_type' => Payment_Type::SINGLE(),
- 'gateway_type' => 'split_upe_with_deferred_intent_creation',
- 'checkout_type' => '',
- 'client_version' => WCPAY_VERSION_NUMBER,
- 'subscription_payment' => 'no',
- ];
-
- $level3 = [
- 'merchant_reference' => (string) $order_id,
- 'customer_reference' => (string) $order_id,
- 'shipping_amount' => 1000.0,
- 'line_items' => [
- (object) [
- 'product_code' => 30,
- 'product_description' => 'Beanie with Logo',
- 'unit_cost' => 1800,
- 'quantity' => 1,
- 'tax_amount' => 270,
- 'discount_amount' => 0,
- 'product_code' => $product_item->get_product_id(),
- 'product_description' => 'Dummy Product',
- 'unit_cost' => 1000.0,
- 'quantity' => 4,
- 'tax_amount' => 0.0,
- 'discount_amount' => 0.0,
- ],
- ],
- ];
-
// Test update_payment_intent on each payment gateway.
foreach ( $this->mock_payment_gateways as $mock_payment_gateway ) {
$request = $this->mock_wcpay_request( Update_Intention::class, 1, $intent->get_id() );
@@ -474,8 +454,7 @@ public function test_update_payment_intent_adds_customer_save_payment_and_level3
$request->expects( $this->once() )->method( 'set_currency_code' )->with( 'usd' );
$request->expects( $this->once() )->method( 'setup_future_usage' );
$request->expects( $this->once() )->method( 'set_customer' )->with( 'cus_mock' );
- $request->expects( $this->once() )->method( 'set_metadata' )->with( $metadata );
- $request->expects( $this->once() )->method( 'set_level3' )->with( $level3 );
+ $request->expects( $this->once() )->method( 'set_metadata' )->with( [ 'gateway_type' => 'split_upe_with_deferred_intent_creation' ] );
$request->expects( $this->once() )
->method( 'format_response' )
->willReturn( $intent );
@@ -506,43 +485,6 @@ public function test_update_payment_intent_with_selected_upe_payment_method() {
->expects( $this->never() )
->method( 'create_customer_for_user' );
- $metadata = [
- 'customer_name' => 'Jeroen Sormani',
- 'customer_email' => 'admin@example.org',
- 'site_url' => 'http://example.org',
- 'order_id' => $order_id,
- 'order_number' => $order_number,
- 'order_key' => $order->get_order_key(),
- 'payment_type' => Payment_Type::SINGLE(),
- 'gateway_type' => 'split_upe_with_deferred_intent_creation',
- 'checkout_type' => '',
- 'client_version' => WCPAY_VERSION_NUMBER,
- 'subscription_payment' => 'no',
-
- ];
-
- $level3 = [
- 'merchant_reference' => (string) $order_id,
- 'shipping_amount' => 1000.0,
- 'line_items' => [
- (object) [
- 'product_code' => 30,
- 'product_description' => 'Beanie with Logo',
- 'unit_cost' => 1800,
- 'quantity' => 1,
- 'tax_amount' => 270,
- 'discount_amount' => 0,
- 'product_code' => $product_item->get_product_id(),
- 'product_description' => 'Dummy Product',
- 'unit_cost' => 1000.0,
- 'quantity' => 4,
- 'tax_amount' => 0.0,
- 'discount_amount' => 0.0,
- ],
- ],
- 'customer_reference' => (string) $order_id,
- ];
-
/**
* In order to test each gateway, we need to setup mock_api_client so that
* its input are mocked in sequence, matching the gateways.
@@ -553,8 +495,7 @@ public function test_update_payment_intent_with_selected_upe_payment_method() {
$request->expects( $this->once() )->method( 'set_currency_code' )->with( 'usd' );
$request->expects( $this->once() )->method( 'setup_future_usage' );
$request->expects( $this->once() )->method( 'set_customer' )->with( 'cus_mock' );
- $request->expects( $this->once() )->method( 'set_metadata' )->with( $metadata );
- $request->expects( $this->once() )->method( 'set_level3' )->with( $level3 );
+ $request->expects( $this->once() )->method( 'set_metadata' )->with( [ 'gateway_type' => 'split_upe_with_deferred_intent_creation' ] );
$request->expects( $this->once() )->method( 'set_payment_method_types' )->with( [ $payment_method_id ] );
$request->expects( $this->once() )
@@ -585,50 +526,13 @@ public function test_update_payment_intent_with_payment_country() {
->expects( $this->never() )
->method( 'create_customer_for_user' );
- $metadata = [
- 'customer_name' => 'Jeroen Sormani',
- 'customer_email' => 'admin@example.org',
- 'site_url' => 'http://example.org',
- 'order_id' => $order_id,
- 'order_number' => $order_number,
- 'order_key' => $order->get_order_key(),
- 'payment_type' => Payment_Type::SINGLE(),
- 'gateway_type' => 'split_upe_with_deferred_intent_creation',
- 'checkout_type' => '',
- 'client_version' => WCPAY_VERSION_NUMBER,
- 'subscription_payment' => 'no',
- ];
-
- $level3 = [
- 'merchant_reference' => (string) $order_id,
- 'shipping_amount' => 1000.0,
- 'line_items' => [
- (object) [
- 'product_code' => 30,
- 'product_description' => 'Beanie with Logo',
- 'unit_cost' => 1800,
- 'quantity' => 1,
- 'tax_amount' => 270,
- 'discount_amount' => 0,
- 'product_code' => $product_item->get_product_id(),
- 'product_description' => 'Dummy Product',
- 'unit_cost' => 1000.0,
- 'quantity' => 4,
- 'tax_amount' => 0.0,
- 'discount_amount' => 0.0,
- ],
- ],
- 'customer_reference' => (string) $order_id,
- ];
-
// Test update_payment_intent on each payment gateway.
foreach ( $this->mock_payment_gateways as $mock_payment_gateway ) {
$request = $this->mock_wcpay_request( Update_Intention::class, 1, $intent->get_id() );
$request->expects( $this->once() )->method( 'set_amount' )->with( 5000 );
$request->expects( $this->once() )->method( 'set_currency_code' )->with( 'usd' );
$request->expects( $this->once() )->method( 'set_customer' )->with( 'cus_mock' );
- $request->expects( $this->once() )->method( 'set_metadata' )->with( $metadata );
- $request->expects( $this->once() )->method( 'set_level3' )->with( $level3 );
+ $request->expects( $this->once() )->method( 'set_metadata' )->with( [ 'gateway_type' => 'split_upe_with_deferred_intent_creation' ] );
$request->expects( $this->once() )->method( 'set_payment_country' )->with( 'US' );
$request->expects( $this->once() )
->method( 'format_response' )
@@ -1249,7 +1153,6 @@ public function test_process_redirect_setup_intent_succeded() {
$save_payment_method = true;
$user = wp_get_current_user();
$intent_status = Intent_Status::SUCCEEDED;
- $intent_metadata = [ 'order_id' => (string) $order_id ];
$client_secret = 'cs_mock';
$customer_id = 'cus_mock';
$intent_id = 'si_mock';
diff --git a/tests/unit/src/Internal/Proxy/HooksProxyTest.php b/tests/unit/src/Internal/Proxy/HooksProxyTest.php
index 2015a05e2fc..59a833f27b6 100644
--- a/tests/unit/src/Internal/Proxy/HooksProxyTest.php
+++ b/tests/unit/src/Internal/Proxy/HooksProxyTest.php
@@ -83,4 +83,18 @@ public function test_add_action() {
$result = do_action( $hook_name, 1, 2, 3 );
$this->assertNull( $result ); // Non-null would be a filter.
}
+
+ public function test_apply_filters() {
+ $hook_name = 'proxy_test_filter';
+
+ $this->helper->expects( $this->once() )
+ ->method( 'action' )
+ ->with( 1, 2, 3 )
+ ->willReturn( 4 );
+
+ $this->sut->add_filter( $hook_name, [ $this->helper, 'action' ], 11, 3 );
+
+ $result = $this->sut->apply_filters( $hook_name, 1, 2, 3 );
+ $this->assertEquals( 4, $result ); // Non-null would be a filter.
+ }
}
diff --git a/tests/unit/src/Internal/Service/Level3ServiceTest.php b/tests/unit/src/Internal/Service/Level3ServiceTest.php
new file mode 100644
index 00000000000..553c54f862c
--- /dev/null
+++ b/tests/unit/src/Internal/Service/Level3ServiceTest.php
@@ -0,0 +1,518 @@
+mock_order_service = $this->createMock( OrderService::class );
+ $this->mock_account = $this->createMock( WC_Payments_Account::class );
+ $this->legacy_proxy = $this->createMock( LegacyProxy::class );
+
+ // Main service under test: Level3Service.
+ $this->sut = new Level3Service(
+ $this->mock_order_service,
+ $this->mock_account,
+ $this->legacy_proxy
+ );
+ }
+
+ protected function create_mock_item( $name, $quantity, $subtotal, $total_tax, $product_id, $variable = false ) {
+ // Setup the item.
+ $mock_item = $this
+ ->getMockBuilder( WC_Order_Item_Product::class )
+ ->disableOriginalConstructor()
+ ->onlyMethods(
+ [
+ 'get_name',
+ 'get_quantity',
+ 'get_subtotal',
+ 'get_total_tax',
+ 'get_total',
+ 'get_variation_id',
+ 'get_product_id',
+ ]
+ )
+ ->getMock();
+
+ $mock_item
+ ->method( 'get_name' )
+ ->will( $this->returnValue( $name ) );
+
+ $mock_item
+ ->method( 'get_quantity' )
+ ->will( $this->returnValue( $quantity ) );
+
+ $mock_item
+ ->method( 'get_total' )
+ ->will( $this->returnValue( $subtotal ) );
+
+ $mock_item
+ ->method( 'get_subtotal' )
+ ->will( $this->returnValue( $subtotal ) );
+
+ $mock_item
+ ->method( 'get_total_tax' )
+ ->will( $this->returnValue( $total_tax ) );
+
+ $mock_item
+ ->method( 'get_variation_id' )
+ ->will( $this->returnValue( $variable ? 789 : false ) );
+
+ $mock_item
+ ->method( 'get_product_id' )
+ ->will( $this->returnValue( $product_id ) );
+
+ return $mock_item;
+ }
+
+ protected function mock_level_3_order(
+ $shipping_postcode,
+ $with_fee = false,
+ $with_negative_price_product = false,
+ $quantity = 1,
+ $basket_size = 1,
+ $product_id = 30,
+ $variable = false
+ ) {
+ $mock_items[] = $this->create_mock_item( 'Beanie with Logo', $quantity, 18, 2.7, $product_id, $variable );
+
+ if ( $with_fee ) {
+ // Setup the fee.
+ $mock_fee = $this
+ ->getMockBuilder( WC_Order_Item_Fee::class )
+ ->disableOriginalConstructor()
+ ->onlyMethods( [ 'get_name', 'get_quantity', 'get_total_tax', 'get_total' ] )
+ ->getMock();
+
+ $mock_fee
+ ->method( 'get_name' )
+ ->will( $this->returnValue( 'fee' ) );
+
+ $mock_fee
+ ->method( 'get_quantity' )
+ ->will( $this->returnValue( 1 ) );
+
+ $mock_fee
+ ->method( 'get_total' )
+ ->will( $this->returnValue( 10 ) );
+
+ $mock_fee
+ ->method( 'get_total_tax' )
+ ->will( $this->returnValue( 1.5 ) );
+
+ $mock_items[] = $mock_fee;
+ }
+
+ if ( $with_negative_price_product ) {
+ $mock_items[] = $this->create_mock_item( 'Negative Product Price', $quantity, -18.99, 2.7, 42 );
+ }
+
+ if ( $basket_size > 1 ) {
+ // Keep the formely created item/fee and add duplicated items to the basket.
+ $mock_items = array_merge( $mock_items, array_fill( 0, $basket_size - 1, $mock_items[0] ) );
+ }
+
+ // Setup the order.
+ $mock_order = $this
+ ->getMockBuilder( WC_Order::class )
+ ->disableOriginalConstructor()
+ ->onlyMethods(
+ [
+ 'get_id',
+ 'get_items',
+ 'get_currency',
+ 'get_shipping_total',
+ 'get_shipping_tax',
+ 'get_shipping_postcode',
+ ]
+ )
+ ->getMock();
+
+ $mock_order
+ ->method( 'get_id' )
+ ->will( $this->returnValue( 210 ) );
+
+ $mock_order
+ ->method( 'get_items' )
+ ->will( $this->returnValue( $mock_items ) );
+
+ $mock_order
+ ->method( 'get_currency' )
+ ->will( $this->returnValue( 'USD' ) );
+
+ $mock_order
+ ->method( 'get_shipping_total' )
+ ->will( $this->returnValue( 30 ) );
+
+ $mock_order
+ ->method( 'get_shipping_tax' )
+ ->will( $this->returnValue( 8 ) );
+
+ $mock_order
+ ->method( 'get_shipping_postcode' )
+ ->will( $this->returnValue( $shipping_postcode ) );
+
+ $this->mock_order_service->expects( $this->once() )
+ ->method( '_deprecated_get_order' )
+ ->with( $this->order_id )
+ ->willReturn( $mock_order );
+ }
+
+ public function test_full_level3_data() {
+ $expected_data = [
+ 'merchant_reference' => '210',
+ 'customer_reference' => '210',
+ 'shipping_amount' => 3800,
+ 'line_items' => [
+ (object) [
+ 'product_code' => 789,
+ 'product_description' => 'Beanie with Logo',
+ 'unit_cost' => 1800,
+ 'quantity' => 1.0,
+ 'tax_amount' => 270,
+ 'discount_amount' => 0,
+ ],
+ ],
+ 'shipping_address_zip' => '98012',
+ 'shipping_from_zip' => '94110',
+ ];
+
+ $this->legacy_proxy->expects( $this->once() )
+ ->method( 'call_function' )
+ ->with( 'get_option', 'woocommerce_store_postcode' )
+ ->willReturn( '94110' );
+
+ $this->mock_account->method( 'get_account_country' )->willReturn( 'US' );
+ $this->mock_level_3_order( '98012', false, false, 1, 1, 30, true );
+ $level_3_data = $this->sut->get_data_from_order( $this->order_id );
+
+ $this->assertEquals( $expected_data, $level_3_data );
+ }
+
+ public function test_full_level3_data_with_product_id_longer_than_12_characters() {
+ $expected_data = [
+ 'merchant_reference' => '210',
+ 'customer_reference' => '210',
+ 'shipping_amount' => 3800,
+ 'line_items' => [
+ (object) [
+ 'product_code' => 123456789123,
+ 'product_description' => 'Beanie with Logo',
+ 'unit_cost' => 1800,
+ 'quantity' => 1,
+ 'tax_amount' => 270,
+ 'discount_amount' => 0,
+ ],
+ ],
+ 'shipping_address_zip' => '98012',
+ 'shipping_from_zip' => '94110',
+ ];
+
+ $this->legacy_proxy->expects( $this->once() )
+ ->method( 'call_function' )
+ ->with( 'get_option', 'woocommerce_store_postcode' )
+ ->willReturn( '94110' );
+
+ $this->mock_account->method( 'get_account_country' )->willReturn( 'US' );
+ $this->mock_level_3_order( '98012', false, false, 1, 1, 123456789123456 );
+ $level_3_data = $this->sut->get_data_from_order( $this->order_id );
+
+ $this->assertEquals( $expected_data, $level_3_data );
+ }
+
+ public function test_full_level3_data_with_fee() {
+ $expected_data = [
+ 'merchant_reference' => '210',
+ 'customer_reference' => '210',
+ 'shipping_amount' => 3800,
+ 'line_items' => [
+ (object) [
+ 'product_code' => 30,
+ 'product_description' => 'Beanie with Logo',
+ 'unit_cost' => 1800,
+ 'quantity' => 1,
+ 'tax_amount' => 270,
+ 'discount_amount' => 0,
+ ],
+ (object) [
+ 'product_code' => 'fee',
+ 'product_description' => 'fee',
+ 'unit_cost' => 1000,
+ 'quantity' => 1,
+ 'tax_amount' => 150,
+ 'discount_amount' => 0,
+ ],
+ ],
+ 'shipping_address_zip' => '98012',
+ 'shipping_from_zip' => '94110',
+ ];
+
+ $this->legacy_proxy->expects( $this->once() )
+ ->method( 'call_function' )
+ ->with( 'get_option', 'woocommerce_store_postcode' )
+ ->willReturn( '94110' );
+
+ $this->mock_account->method( 'get_account_country' )->willReturn( 'US' );
+ $this->mock_level_3_order( '98012', true );
+ $level_3_data = $this->sut->get_data_from_order( $this->order_id );
+
+ $this->assertEquals( $expected_data, $level_3_data );
+ }
+
+ public function test_full_level3_data_with_negative_price_product() {
+ $expected_data = [
+ 'merchant_reference' => '210',
+ 'customer_reference' => '210',
+ 'shipping_amount' => 3800,
+ 'line_items' => [
+ (object) [
+ 'product_code' => 30,
+ 'product_description' => 'Beanie with Logo',
+ 'unit_cost' => 1800,
+ 'quantity' => 1,
+ 'tax_amount' => 270,
+ 'discount_amount' => 0,
+ ],
+ (object) [
+ 'product_code' => 42,
+ 'product_description' => 'Negative Product Price',
+ 'unit_cost' => 0,
+ 'quantity' => 1,
+ 'tax_amount' => 270,
+ 'discount_amount' => 1899,
+ ],
+ ],
+ 'shipping_address_zip' => '98012',
+ 'shipping_from_zip' => '94110',
+ ];
+
+ $this->legacy_proxy->expects( $this->once() )
+ ->method( 'call_function' )
+ ->with( 'get_option', 'woocommerce_store_postcode' )
+ ->willReturn( '94110' );
+
+ $this->mock_account->method( 'get_account_country' )->willReturn( 'US' );
+ $this->mock_level_3_order( '98012', false, true, 1, 1 );
+ $level_3_data = $this->sut->get_data_from_order( $this->order_id );
+
+ $this->assertEquals( $expected_data, $level_3_data );
+ }
+
+ public function test_us_store_level_3_data() {
+ // Use a non-us customer postcode to ensure it's not included in the level3 data.
+ $this->mock_account->method( 'get_account_country' )->willReturn( 'US' );
+ $this->mock_level_3_order( '9000' );
+ $level_3_data = $this->sut->get_data_from_order( $this->order_id );
+
+ $this->assertArrayNotHasKey( 'shipping_address_zip', $level_3_data );
+ }
+
+ public function test_us_customer_level_3_data() {
+ $expected_data = [
+ 'merchant_reference' => '210',
+ 'customer_reference' => '210',
+ 'shipping_amount' => 3800,
+ 'line_items' => [
+ (object) [
+ 'product_code' => 30,
+ 'product_description' => 'Beanie with Logo',
+ 'unit_cost' => 1800,
+ 'quantity' => 1,
+ 'tax_amount' => 270,
+ 'discount_amount' => 0,
+ ],
+ ],
+ 'shipping_address_zip' => '98012',
+ ];
+
+ // Use a non-US postcode.
+ $this->legacy_proxy->expects( $this->once() )
+ ->method( 'call_function' )
+ ->with( 'get_option', 'woocommerce_store_postcode' )
+ ->willReturn( '9000' );
+
+ $this->mock_account->method( 'get_account_country' )->willReturn( 'US' );
+ $this->mock_level_3_order( '98012' );
+ $level_3_data = $this->sut->get_data_from_order( $this->order_id );
+
+ $this->assertEquals( $expected_data, $level_3_data );
+ }
+
+ public function test_non_us_customer_level_3_data() {
+ $expected_data = [];
+
+ $this->mock_account->method( 'get_account_country' )->willReturn( 'CA' );
+ $this->mock_level_3_order( 'K0A' );
+ $level_3_data = $this->sut->get_data_from_order( $this->order_id );
+
+ $this->assertEquals( $expected_data, $level_3_data );
+ }
+
+ public function test_full_level3_data_with_float_quantity() {
+ $expected_data = [
+ 'merchant_reference' => '210',
+ 'customer_reference' => '210',
+ 'shipping_amount' => 3800,
+ 'line_items' => [
+ (object) [
+ 'product_code' => 30,
+ 'product_description' => 'Beanie with Logo',
+ 'unit_cost' => 450,
+ 'quantity' => 4,
+ 'tax_amount' => 270,
+ 'discount_amount' => 0,
+ ],
+ ],
+ 'shipping_address_zip' => '98012',
+ 'shipping_from_zip' => '94110',
+ ];
+
+ $this->legacy_proxy->expects( $this->once() )
+ ->method( 'call_function' )
+ ->with( 'get_option', 'woocommerce_store_postcode' )
+ ->willReturn( '94110' );
+
+ $this->mock_account->method( 'get_account_country' )->willReturn( 'US' );
+ $this->mock_level_3_order( '98012', false, false, 3.7 );
+ $level_3_data = $this->sut->get_data_from_order( $this->order_id );
+
+ $this->assertEquals( $expected_data, $level_3_data );
+ }
+
+ public function test_full_level3_data_with_float_quantity_zero() {
+ $expected_data = [
+ 'merchant_reference' => '210',
+ 'customer_reference' => '210',
+ 'shipping_amount' => 3800,
+ 'line_items' => [
+ (object) [
+ 'product_code' => 30,
+ 'product_description' => 'Beanie with Logo',
+ 'unit_cost' => 1800,
+ 'quantity' => 1,
+ 'tax_amount' => 270,
+ 'discount_amount' => 0,
+ ],
+ ],
+ 'shipping_address_zip' => '98012',
+ 'shipping_from_zip' => '94110',
+ ];
+
+ $this->legacy_proxy->expects( $this->once() )
+ ->method( 'call_function' )
+ ->with( 'get_option', 'woocommerce_store_postcode' )
+ ->willReturn( '94110' );
+
+ $this->mock_account->method( 'get_account_country' )->willReturn( 'US' );
+ $this->mock_level_3_order( '98012', false, false, 0.4 );
+ $level_3_data = $this->sut->get_data_from_order( $this->order_id );
+
+ $this->assertEquals( $expected_data, $level_3_data );
+ }
+
+ public function test_level3_data_bundle() {
+ $items = (array) [
+ (object) [
+ 'product_code' => 'abcd',
+ 'product_description' => 'product description',
+ 'unit_cost' => 1000,
+ 'quantity' => 4,
+ 'tax_amount' => 200,
+ 'discount_amount' => 500,
+ ],
+ (object) [
+ 'product_code' => 'abcd',
+ 'product_description' => 'product description',
+ 'unit_cost' => 5000,
+ 'quantity' => 3,
+ 'tax_amount' => 1000,
+ 'discount_amount' => 200,
+ ],
+ ];
+
+ // Use reflection to test the otherwise method.
+ $reflection = new ReflectionClass( Level3Service::class );
+ $method = $reflection->getMethod( 'bundle_level3_data_from_items' );
+ $method->setAccessible( true );
+ $bundle_data = $method->invoke( $this->sut, $items );
+
+ $this->assertSame( $bundle_data->product_description, '2 more items' );
+
+ // total_unit_cost = sum( unit_cost * quantity ).
+ $this->assertSame( $bundle_data->unit_cost, 19000 );
+
+ // quantity of the bundle = 1.
+ $this->assertSame( $bundle_data->quantity, 1 );
+
+ // total_tax_amount = sum( tax_amount ).
+ $this->assertSame( $bundle_data->tax_amount, 1200 );
+
+ // total_discount_amount = sum( discount_amount ).
+ $this->assertSame( $bundle_data->discount_amount, 700 );
+ }
+
+ public function test_level3_data_bundle_for_orders_with_more_than_200_items() {
+ $this->mock_account->method( 'get_account_country' )->willReturn( 'US' );
+ $this->mock_level_3_order( '98012', true, false, 1, 500 );
+ $level_3_data = $this->sut->get_data_from_order( $this->order_id );
+
+ $this->assertSame( count( $level_3_data['line_items'] ), 200 );
+
+ $bundled_data = end( $level_3_data['line_items'] );
+
+ $this->assertSame( $bundled_data->product_description, '301 more items' );
+ }
+}
diff --git a/tests/unit/src/Internal/Service/OrderServiceTest.php b/tests/unit/src/Internal/Service/OrderServiceTest.php
index 849baad0763..ba45a8afc50 100644
--- a/tests/unit/src/Internal/Service/OrderServiceTest.php
+++ b/tests/unit/src/Internal/Service/OrderServiceTest.php
@@ -8,7 +8,13 @@
namespace WCPay\Tests\Internal\Service;
use PHPUnit\Framework\MockObject\MockObject;
+use WC_Order;
+use WC_Payments_Features;
use WC_Payments_Order_Service;
+use WCPay\Constants\Payment_Type;
+use WCPay\Exceptions\Order_Not_Found_Exception;
+use WCPay\Internal\Proxy\HooksProxy;
+use WCPay\Internal\Proxy\LegacyProxy;
use WCPAY_UnitTestCase;
use WCPay\Internal\Service\OrderService;
@@ -19,7 +25,7 @@ class OrderServiceTest extends WCPAY_UnitTestCase {
/**
* Service under test.
*
- * @var OrderService|MockObject
+ * @var OrderService
*/
private $sut;
@@ -28,25 +34,193 @@ class OrderServiceTest extends WCPAY_UnitTestCase {
*/
private $mock_legacy_service;
+ /**
+ * @var LegacyProxy|MockObject
+ */
+ private $mock_legacy_proxy;
+
+ /**
+ * @var HooksProxy|MockObject
+ */
+ private $mock_hooks_proxy;
+
+ /**
+ * Order ID used for mocks.
+ *
+ * @var int
+ */
+ private $order_id = 123;
+
/**
* Set up the test.
*/
protected function setUp(): void {
parent::setUp();
+ $this->mock_legacy_proxy = $this->createMock( LegacyProxy::class );
$this->mock_legacy_service = $this->createMock( WC_Payments_Order_Service::class );
+ $this->mock_hooks_proxy = $this->createMock( HooksProxy::class );
+
+ // Main service under test: OrderService.
+ $this->sut = new OrderService(
+ $this->mock_legacy_service,
+ $this->mock_legacy_proxy,
+ $this->mock_hooks_proxy
+ );
+ }
+
+ public function test_get_order_returns_order() {
+ $mock_order = $this->createMock( WC_Order::class );
+
+ $this->mock_legacy_proxy->expects( $this->once() )
+ ->method( 'call_function' )
+ ->with( 'wc_get_order', $this->order_id )
+ ->willReturn( $mock_order );
+
+ $result = $this->sut->_deprecated_get_order( $this->order_id );
+ $this->assertSame( $mock_order, $result );
+ }
+
+ public function test_get_order_throws_exception() {
+ $this->mock_legacy_proxy->expects( $this->once() )
+ ->method( 'call_function' )
+ ->with( 'wc_get_order', $this->order_id )
+ ->willReturn( false );
- $this->sut = new OrderService( $this->mock_legacy_service );
+ $this->expectException( Order_Not_Found_Exception::class );
+ $this->expectExceptionMessage( "The requested order (ID $this->order_id) was not found." );
+ $this->sut->_deprecated_get_order( $this->order_id );
}
public function test_set_payment_method_id() {
- $order_id = 123;
- $pm_id = 'pm_XYZ';
+ $pm_id = 'pm_XYZ';
$this->mock_legacy_service->expects( $this->once() )
->method( 'set_payment_method_id_for_order' )
- ->with( $order_id, $pm_id );
+ ->with( $this->order_id, $pm_id );
+
+ $this->sut->set_payment_method_id( $this->order_id, $pm_id );
+ }
+
+ public function test_get_payment_metadata_without_subscriptions() {
+ // Prepare data and expectations.
+ $first_name = 'John';
+ $last_name = 'Doe';
+ $email = 'example@example.com';
+ $order_number = 'ABC123';
+ $order_key = 'xyz123';
+ $created_via = 'checkout';
+
+ $expected = [
+ 'customer_name' => $first_name . ' ' . $last_name,
+ 'customer_email' => $email,
+ 'site_url' => esc_url( get_site_url() ),
+ 'order_id' => $this->order_id,
+ 'order_number' => $order_number,
+ 'order_key' => $order_key,
+ 'payment_type' => 'single',
+ 'checkout_type' => $created_via,
+ 'client_version' => WCPAY_VERSION_NUMBER,
+ 'subscription_payment' => 'no',
+ ];
+
+ // Setup the mock order.
+ $mock_order = $this->mock_get_order();
+
+ $order_methods = [
+ 'get_id' => $this->order_id,
+ 'get_billing_first_name' => $first_name,
+ 'get_billing_last_name' => $last_name,
+ 'get_billing_email' => $email,
+ 'get_order_number' => $order_number,
+ 'get_order_key' => $order_key,
+ 'get_created_via' => $created_via,
+ ];
+ foreach ( $order_methods as $name => $value ) {
+ $mock_order->expects( $this->once() )
+ ->method( $name )
+ ->willReturn( $value );
+ }
+
+ // Expect filters.
+ $this->mock_hooks_proxy->expects( $this->once() )
+ ->method( 'apply_filters' )
+ ->with( 'wcpay_metadata_from_order', $expected, $mock_order, Payment_Type::SINGLE() )
+ ->willReturn( $expected );
+
+ // Act.
+ $result = $this->sut->get_payment_metadata( $this->order_id, Payment_Type::SINGLE() );
+
+ // Assert.
+ $this->assertEquals( $expected, $result );
+ }
+
+ /**
+ * @dataProvider provider_subscription_details
+ */
+ public function test_get_payment_metadata_with_subscription( bool $is_renewal, bool $wcpay_subscription ) {
+ $mock_order = $this->createMock( WC_Order::class );
+
+ $this->mock_legacy_proxy->expects( $this->exactly( 4 ) )
+ ->method( 'call_function' )
+ ->withConsecutive(
+ [ 'wc_get_order', $this->order_id ],
+ [ 'function_exists', 'wcs_order_contains_subscription' ],
+ [ 'wcs_order_contains_subscription', $mock_order, 'any' ],
+ [ 'wcs_order_contains_renewal', $mock_order ]
+ )
+ ->willReturnOnConsecutiveCalls(
+ $mock_order,
+ true,
+ true,
+ $is_renewal
+ );
+
+ $this->mock_legacy_proxy->expects( $this->once() )
+ ->method( 'call_static' )
+ ->with( WC_Payments_Features::class, 'should_use_stripe_billing' )
+ ->willReturn( $wcpay_subscription );
+
+ // Expect filters.
+ $this->mock_hooks_proxy->expects( $this->once() )
+ ->method( 'apply_filters' )
+ ->with( 'wcpay_metadata_from_order', $this->callback( 'is_array' ), $mock_order, Payment_Type::RECURRING() )
+ ->willReturnArgument( 1 );
+
+ // Act.
+ $result = $this->sut->get_payment_metadata( $this->order_id, Payment_Type::RECURRING() );
+
+ // Assert.
+ $this->assertIsArray( $result );
+ $this->assertEquals( $is_renewal ? 'renewal' : 'initial', $result['subscription_payment'] );
+ $this->assertEquals( $wcpay_subscription ? 'wcpay_subscription' : 'regular_subscription', $result['payment_context'] );
+ }
+
+ public function provider_subscription_details() {
+ return [
+ // is_renewal and wcpay_subscription.
+ [ false, false ],
+ [ false, true ],
+ [ true, false ],
+ [ true, true ],
+ ];
+ }
+
+ /**
+ * Mocks order retrieval.
+ *
+ * @param int $order_id ID of the order to mock.
+ * @return WC_Order|MockObject The mock order, ready for setup.
+ */
+ private function mock_get_order( int $order_id = null ) {
+ $order_id = $order_id ?? $this->order_id;
+ $mock_order = $this->createMock( WC_Order::class );
+
+ $this->mock_legacy_proxy->expects( $this->once() )
+ ->method( 'call_function' )
+ ->with( 'wc_get_order', $order_id )
+ ->willReturn( $mock_order );
- $this->sut->set_payment_method_id( $order_id, $pm_id );
+ return $mock_order;
}
}
diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay-payment-types.php b/tests/unit/test-class-wc-payment-gateway-wcpay-payment-types.php
index 94d1ce4e3d1..3165d8e447f 100644
--- a/tests/unit/test-class-wc-payment-gateway-wcpay-payment-types.php
+++ b/tests/unit/test-class-wc-payment-gateway-wcpay-payment-types.php
@@ -10,6 +10,7 @@
use WCPay\Duplicate_Payment_Prevention_Service;
use WCPay\Session_Rate_Limiter;
use WCPay\Fraud_Prevention\Fraud_Prevention_Service;
+use WCPay\Internal\Service\OrderService;
/**
* WC_Payment_Gateway_WCPay unit tests.
@@ -155,6 +156,7 @@ public function set_up() {
'mark_payment_complete_for_order',
'get_level3_data_from_order', // To avoid needing to mock the order items.
'get_payment_method_ids_enabled_at_checkout',
+ 'get_metadata_from_order',
]
)
->getMock();
@@ -172,6 +174,11 @@ public function set_up() {
'wcpay-payment-method' => 'pm_mock',
'payment_method' => WC_Payment_Gateway_WCPay::GATEWAY_ID,
];
+
+ // Intent metadata is generated elsewhere, use empty arrays here.
+ $this->mock_wcpay_gateway->expects( $this->any() )
+ ->method( 'get_metadata_from_order' )
+ ->willReturn( [] );
}
/**
@@ -225,15 +232,7 @@ public function test_single_payment() {
$request->expects( $this->once() )
->method( 'set_metadata' )
- ->with(
- $this->callback(
- function( $metadata ) use ( $order ) {
- $this->assertEquals( $metadata['payment_type'], 'single' );
- $this->assertEquals( $metadata['order_key'], $order->get_order_key() );
- return is_array( $metadata );
- }
- )
- );
+ ->with( [] );
$request->expects( $this->once() )
->method( 'format_response' )
@@ -261,15 +260,7 @@ public function test_initial_subscription_payment() {
$request->expects( $this->once() )
->method( 'set_metadata' )
- ->with(
- $this->callback(
- function( $metadata ) use ( $order ) {
- $this->assertEquals( $metadata['payment_type'], 'recurring' );
- $this->assertEquals( $metadata['order_key'], $order->get_order_key() );
- return is_array( $metadata );
- }
- )
- );
+ ->with( [] );
$request->expects( $this->once() )
->method( 'format_response' )
@@ -294,15 +285,7 @@ public function test_renewal_subscription_payment() {
$request->expects( $this->once() )
->method( 'set_metadata' )
- ->with(
- $this->callback(
- function( $metadata ) use ( $order ) {
- $this->assertEquals( $metadata['payment_type'], 'recurring' );
- $this->assertEquals( $metadata['order_key'], $order->get_order_key() );
- return is_array( $metadata );
- }
- )
- );
+ ->with( [] );
$request->expects( $this->once() )
->method( 'format_response' )
diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay-process-payment.php b/tests/unit/test-class-wc-payment-gateway-wcpay-process-payment.php
index 6d8f6a7318c..af672185ee9 100644
--- a/tests/unit/test-class-wc-payment-gateway-wcpay-process-payment.php
+++ b/tests/unit/test-class-wc-payment-gateway-wcpay-process-payment.php
@@ -174,6 +174,7 @@ public function set_up() {
'get_level3_data_from_order', // To avoid needing to mock the order items.
'should_use_stripe_platform_on_checkout_page',
'get_payment_method_ids_enabled_at_checkout',
+ 'get_metadata_from_order',
]
)
->getMock();
@@ -186,10 +187,16 @@ public function set_up() {
$this->returnValue( $this->return_url )
);
+ $this->mock_wcpay_gateway
+ ->expects( $this->any() )
+ ->method( 'get_payment_method_ids_enabled_at_checkout' )
+ ->willReturn( [ Payment_Method::CARD ] );
+
+ // Plenty of methods require metadata, but it will be tested elsewhere.
$this->mock_wcpay_gateway
->expects( $this->any() )
- ->method( 'get_payment_method_ids_enabled_at_checkout' )
- ->willReturn( [ Payment_Method::CARD ] );
+ ->method( 'get_metadata_from_order' )
+ ->willReturn( [] );
$this->wcpay_gateway = WC_Payments::get_gateway();
WC_Payments::set_gateway( $this->mock_wcpay_gateway );
@@ -1462,20 +1469,8 @@ public function test_process_payment_for_subscription_in_woopay_adds_subscriptio
$request->expects( $this->once() )
->method( 'set_metadata' )
- ->with(
- $this->callback(
- function( $metadata ) {
- $required_keys = [ 'customer_name', 'customer_email', 'site_url', 'order_id', 'order_number', 'order_key', 'payment_type' ];
- foreach ( $required_keys as $key ) {
- if ( ! array_key_exists( $key, $metadata ) ) {
- return false;
- }
- }
- return true;
- }
- )
- )
- ->willReturn( $request );
+ ->with( [] )
+ ->willReturn( $request );
$request->expects( $this->once() )
->method( 'format_response' )
diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions-process-payment.php b/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions-process-payment.php
index 74a4ad79b29..6a26e8c4a6c 100644
--- a/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions-process-payment.php
+++ b/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions-process-payment.php
@@ -160,6 +160,7 @@ public function set_up() {
'get_return_url',
'mark_payment_complete_for_order',
'get_level3_data_from_order', // To avoid needing to mock the order items.
+ 'get_metadata_from_order',
]
)
->getMock();
@@ -181,6 +182,11 @@ public function set_up() {
'wcpay-payment-method' => self::PAYMENT_METHOD_ID,
'payment_method' => WC_Payment_Gateway_WCPay::GATEWAY_ID,
];
+
+ // Intent metadata is generated elsewhere, use empty arrays here.
+ $this->mock_wcpay_gateway->expects( $this->any() )
+ ->method( 'get_metadata_from_order' )
+ ->willReturn( [] );
}
public function test_new_card_subscription() {
@@ -239,19 +245,8 @@ public function test_new_card_subscription() {
$request->expects( $this->once() )
->method( 'set_metadata' )
- ->with(
- $this->callback(
- function( $metadata ) {
- $required_keys = [ 'customer_name', 'customer_email', 'site_url', 'order_id', 'order_number', 'order_key', 'payment_type' ];
- foreach ( $required_keys as $key ) {
- if ( ! array_key_exists( $key, $metadata ) ) {
- return false;
- }
- }
- return true;
- }
- )
- );
+ ->with( [] );
+
$request->expects( $this->once() )
->method( 'format_response' )
->willReturn( $this->payment_intent );
@@ -406,19 +401,7 @@ public function test_saved_card_subscription() {
$request->expects( $this->once() )
->method( 'set_metadata' )
- ->with(
- $this->callback(
- function( $metadata ) {
- $required_keys = [ 'customer_name', 'customer_email', 'site_url', 'order_id', 'order_number', 'order_key', 'payment_type' ];
- foreach ( $required_keys as $key ) {
- if ( ! array_key_exists( $key, $metadata ) ) {
- return false;
- }
- }
- return true;
- }
- )
- );
+ ->with( [] );
$request->expects( $this->once() )
->method( 'format_response' )
diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions.php b/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions.php
index 286be65ff4f..8ca0ea3b83d 100644
--- a/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions.php
+++ b/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions.php
@@ -8,6 +8,8 @@
use WCPay\Core\Server\Request\Create_And_Confirm_Intention;
use WCPay\Duplicate_Payment_Prevention_Service;
use WCPay\Exceptions\API_Exception;
+use WCPay\Internal\Service\Level3Service;
+use WCPay\Internal\Service\OrderService;
use WCPay\Session_Rate_Limiter;
/**
@@ -136,12 +138,27 @@ public function set_up() {
$this->mock_localization_service
);
$this->wcpay_gateway->init_hooks();
+
+ // Mock the level3 service to always return an empty array.
+ $mock_level3_service = $this->createMock( Level3Service::class );
+ $mock_level3_service->expects( $this->any() )
+ ->method( 'get_data_from_order' )
+ ->willReturn( [] );
+ wcpay_get_test_container()->replace( Level3Service::class, $mock_level3_service );
+
+ // Mock the order service to always return an empty array for meta.
+ $mock_order_service = $this->createMock( OrderService::class );
+ $mock_order_service->expects( $this->any() )
+ ->method( 'get_payment_metadata' )
+ ->willReturn( [] );
+ wcpay_get_test_container()->replace( OrderService::class, $mock_order_service );
}
public static function tear_down_after_class() {
WC_Subscriptions::set_wcs_get_subscriptions_for_order( null );
WC_Subscriptions::set_wcs_is_subscription( null );
WC_Subscriptions::set_wcs_get_subscriptions_for_renewal_order( null );
+ wcpay_get_test_container()->reset_all_replacements();
parent::tear_down_after_class();
}
@@ -293,19 +310,7 @@ public function test_scheduled_subscription_payment() {
$request->expects( $this->once() )
->method( 'set_metadata' )
- ->with(
- $this->callback(
- function( $metadata ) {
- $required_keys = [ 'customer_name', 'customer_email', 'site_url', 'order_id', 'order_number', 'order_key', 'payment_type' ];
- foreach ( $required_keys as $key ) {
- if ( ! array_key_exists( $key, $metadata ) ) {
- return false;
- }
- }
- return true;
- }
- )
- );
+ ->with( [ 'gateway_type' => 'legacy_card' ] );
$request->expects( $this->once() )
->method( 'format_response' )
@@ -466,20 +471,7 @@ public function test_scheduled_subscription_payment_adds_mandate() {
$request->expects( $this->once() )
->method( 'set_metadata' )
- ->with(
- $this->callback(
- function( $metadata ) {
- $required_keys = [ 'customer_name', 'customer_email', 'site_url', 'order_id', 'order_number', 'order_key', 'payment_type' ];
- foreach ( $required_keys as $key ) {
- if ( ! array_key_exists( $key, $metadata ) ) {
- return false;
- }
- }
- return true;
- }
- )
- );
-
+ ->with( [ 'gateway_type' => 'legacy_card' ] );
$request->expects( $this->once() )
->method( 'format_response' )
->willReturn( WC_Helper_Intention::create_intention() );
diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay.php b/tests/unit/test-class-wc-payment-gateway-wcpay.php
index 8809c06a8af..632feacaa78 100644
--- a/tests/unit/test-class-wc-payment-gateway-wcpay.php
+++ b/tests/unit/test-class-wc-payment-gateway-wcpay.php
@@ -24,6 +24,8 @@
use WCPay\Internal\Payment\Factor;
use WCPay\Internal\Payment\Router;
use WCPay\Internal\Payment\State\CompletedState;
+use WCPay\Internal\Service\Level3Service;
+use WCPay\Internal\Service\OrderService;
use WCPay\Internal\Service\PaymentProcessingService;
use WCPay\Payment_Information;
use WCPay\WooPay\WooPay_Utilities;
@@ -51,42 +53,42 @@ class WC_Payment_Gateway_WCPay_Test extends WCPAY_UnitTestCase {
/**
* Mock WC_Payments_API_Client.
*
- * @var WC_Payments_API_Client|PHPUnit_Framework_MockObject_MockObject
+ * @var WC_Payments_API_Client|MockObject
*/
private $mock_api_client;
/**
* Mock WC_Payments_Customer_Service.
*
- * @var WC_Payments_Customer_Service|PHPUnit_Framework_MockObject_MockObject
+ * @var WC_Payments_Customer_Service|MockObject
*/
private $mock_customer_service;
/**
* Mock WC_Payments_Token_Service.
*
- * @var WC_Payments_Token_Service|PHPUnit_Framework_MockObject_MockObject
+ * @var WC_Payments_Token_Service|MockObject
*/
private $mock_token_service;
/**
* Mock WC_Payments_Action_Scheduler_Service.
*
- * @var WC_Payments_Action_Scheduler_Service|PHPUnit_Framework_MockObject_MockObject
+ * @var WC_Payments_Action_Scheduler_Service|MockObject
*/
private $mock_action_scheduler_service;
/**
* WC_Payments_Account instance.
*
- * @var WC_Payments_Account|PHPUnit_Framework_MockObject_MockObject
+ * @var WC_Payments_Account|MockObject
*/
private $mock_wcpay_account;
/**
* Session_Rate_Limiter instance.
*
- * @var Session_Rate_Limiter|PHPUnit_Framework_MockObject_MockObject
+ * @var Session_Rate_Limiter|MockObject
*/
private $mock_rate_limiter;
@@ -199,6 +201,20 @@ public function set_up() {
$this->mock_wcpay_account,
$this->mock_customer_service
);
+
+ // Mock the level3 service to always return an empty array.
+ $mock_level3_service = $this->createMock( Level3Service::class );
+ $mock_level3_service->expects( $this->any() )
+ ->method( 'get_data_from_order' )
+ ->willReturn( [] );
+ wcpay_get_test_container()->replace( Level3Service::class, $mock_level3_service );
+
+ // Mock the order service to always return an empty array for meta.
+ $mock_order_service = $this->createMock( OrderService::class );
+ $mock_order_service->expects( $this->any() )
+ ->method( 'get_payment_metadata' )
+ ->willReturn( [] );
+ wcpay_get_test_container()->replace( OrderService::class, $mock_order_service );
}
/**
@@ -229,6 +245,8 @@ public function tear_down() {
}
// phpcs:enable WordPress.Security.NonceVerification.Missing
}
+
+ wcpay_get_test_container()->reset_all_replacements();
}
public function test_attach_exchange_info_to_order_with_no_conversion() {
@@ -421,424 +439,6 @@ function ( $output ) use ( $token_value ) {
$this->wcpay_gateway->payment_fields();
}
- protected function create_mock_item( $name, $quantity, $subtotal, $total_tax, $product_id ) {
- // Setup the item.
- $mock_item = $this
- ->getMockBuilder( WC_Order_Item_Product::class )
- ->disableOriginalConstructor()
- ->setMethods(
- [
- 'get_name',
- 'get_quantity',
- 'get_subtotal',
- 'get_total_tax',
- 'get_total',
- 'get_variation_id',
- 'get_product_id',
- ]
- )
- ->getMock();
-
- $mock_item
- ->method( 'get_name' )
- ->will( $this->returnValue( $name ) );
-
- $mock_item
- ->method( 'get_quantity' )
- ->will( $this->returnValue( $quantity ) );
-
- $mock_item
- ->method( 'get_total' )
- ->will( $this->returnValue( $subtotal ) );
-
- $mock_item
- ->method( 'get_subtotal' )
- ->will( $this->returnValue( $subtotal ) );
-
- $mock_item
- ->method( 'get_total_tax' )
- ->will( $this->returnValue( $total_tax ) );
-
- $mock_item
- ->method( 'get_variation_id' )
- ->will( $this->returnValue( false ) );
-
- $mock_item
- ->method( 'get_product_id' )
- ->will( $this->returnValue( $product_id ) );
-
- return $mock_item;
- }
-
- protected function mock_level_3_order(
- $shipping_postcode,
- $with_fee = false,
- $with_negative_price_product = false,
- $quantity = 1,
- $basket_size = 1,
- $product_id = 30
- ) {
- $mock_items[] = $this->create_mock_item( 'Beanie with Logo', $quantity, 18, 2.7, $product_id );
-
- if ( $with_fee ) {
- // Setup the fee.
- $mock_fee = $this
- ->getMockBuilder( WC_Order_Item_Fee::class )
- ->disableOriginalConstructor()
- ->setMethods( [ 'get_name', 'get_quantity', 'get_total_tax', 'get_total' ] )
- ->getMock();
-
- $mock_fee
- ->method( 'get_name' )
- ->will( $this->returnValue( 'fee' ) );
-
- $mock_fee
- ->method( 'get_quantity' )
- ->will( $this->returnValue( 1 ) );
-
- $mock_fee
- ->method( 'get_total' )
- ->will( $this->returnValue( 10 ) );
-
- $mock_fee
- ->method( 'get_total_tax' )
- ->will( $this->returnValue( 1.5 ) );
-
- $mock_items[] = $mock_fee;
- }
-
- if ( $with_negative_price_product ) {
- $mock_items[] = $this->create_mock_item( 'Negative Product Price', $quantity, -18.99, 2.7, 42 );
- }
-
- if ( $basket_size > 1 ) {
- // Keep the formely created item/fee and add duplicated items to the basket.
- $mock_items = array_merge( $mock_items, array_fill( 0, $basket_size - 1, $mock_items[0] ) );
- }
-
- // Setup the order.
- $mock_order = $this
- ->getMockBuilder( WC_Order::class )
- ->disableOriginalConstructor()
- ->setMethods(
- [
- 'get_id',
- 'get_items',
- 'get_currency',
- 'get_shipping_total',
- 'get_shipping_tax',
- 'get_shipping_postcode',
- ]
- )
- ->getMock();
-
- $mock_order
- ->method( 'get_id' )
- ->will( $this->returnValue( 210 ) );
-
- $mock_order
- ->method( 'get_items' )
- ->will( $this->returnValue( $mock_items ) );
-
- $mock_order
- ->method( 'get_currency' )
- ->will( $this->returnValue( 'USD' ) );
-
- $mock_order
- ->method( 'get_shipping_total' )
- ->will( $this->returnValue( 30 ) );
-
- $mock_order
- ->method( 'get_shipping_tax' )
- ->will( $this->returnValue( 8 ) );
-
- $mock_order
- ->method( 'get_shipping_postcode' )
- ->will( $this->returnValue( $shipping_postcode ) );
-
- return $mock_order;
- }
-
- public function test_full_level3_data() {
- $expected_data = [
- 'merchant_reference' => '210',
- 'customer_reference' => '210',
- 'shipping_amount' => 3800,
- 'line_items' => [
- (object) [
- 'product_code' => 30,
- 'product_description' => 'Beanie with Logo',
- 'unit_cost' => 1800,
- 'quantity' => 1,
- 'tax_amount' => 270,
- 'discount_amount' => 0,
- ],
- ],
- 'shipping_address_zip' => '98012',
- 'shipping_from_zip' => '94110',
- ];
-
- update_option( 'woocommerce_store_postcode', '94110' );
-
- $this->mock_wcpay_account->method( 'get_account_country' )->willReturn( 'US' );
- $mock_order = $this->mock_level_3_order( '98012' );
- $level_3_data = $this->wcpay_gateway->get_level3_data_from_order( $mock_order );
-
- $this->assertEquals( $expected_data, $level_3_data );
- }
-
- public function test_full_level3_data_with_product_id_longer_than_12_characters() {
- $expected_data = [
- 'merchant_reference' => '210',
- 'customer_reference' => '210',
- 'shipping_amount' => 3800,
- 'line_items' => [
- (object) [
- 'product_code' => 123456789123,
- 'product_description' => 'Beanie with Logo',
- 'unit_cost' => 1800,
- 'quantity' => 1,
- 'tax_amount' => 270,
- 'discount_amount' => 0,
- ],
- ],
- 'shipping_address_zip' => '98012',
- 'shipping_from_zip' => '94110',
- ];
-
- update_option( 'woocommerce_store_postcode', '94110' );
-
- $this->mock_wcpay_account->method( 'get_account_country' )->willReturn( 'US' );
- $mock_order = $this->mock_level_3_order( '98012', false, false, 1, 1, 123456789123456 );
- $level_3_data = $this->wcpay_gateway->get_level3_data_from_order( $mock_order );
-
- $this->assertEquals( $expected_data, $level_3_data );
- }
-
- public function test_full_level3_data_with_fee() {
- $expected_data = [
- 'merchant_reference' => '210',
- 'customer_reference' => '210',
- 'shipping_amount' => 3800,
- 'line_items' => [
- (object) [
- 'product_code' => 30,
- 'product_description' => 'Beanie with Logo',
- 'unit_cost' => 1800,
- 'quantity' => 1,
- 'tax_amount' => 270,
- 'discount_amount' => 0,
- ],
- (object) [
- 'product_code' => 'fee',
- 'product_description' => 'fee',
- 'unit_cost' => 1000,
- 'quantity' => 1,
- 'tax_amount' => 150,
- 'discount_amount' => 0,
- ],
- ],
- 'shipping_address_zip' => '98012',
- 'shipping_from_zip' => '94110',
- ];
-
- update_option( 'woocommerce_store_postcode', '94110' );
-
- $this->mock_wcpay_account->method( 'get_account_country' )->willReturn( 'US' );
- $mock_order = $this->mock_level_3_order( '98012', true );
- $level_3_data = $this->wcpay_gateway->get_level3_data_from_order( $mock_order );
-
- $this->assertEquals( $expected_data, $level_3_data );
- }
-
- public function test_full_level3_data_with_negative_price_product() {
- $expected_data = [
- 'merchant_reference' => '210',
- 'customer_reference' => '210',
- 'shipping_amount' => 3800,
- 'line_items' => [
- (object) [
- 'product_code' => 30,
- 'product_description' => 'Beanie with Logo',
- 'unit_cost' => 1800,
- 'quantity' => 1,
- 'tax_amount' => 270,
- 'discount_amount' => 0,
- ],
- (object) [
- 'product_code' => 42,
- 'product_description' => 'Negative Product Price',
- 'unit_cost' => 0,
- 'quantity' => 1,
- 'tax_amount' => 270,
- 'discount_amount' => 1899,
- ],
- ],
- 'shipping_address_zip' => '98012',
- 'shipping_from_zip' => '94110',
- ];
-
- update_option( 'woocommerce_store_postcode', '94110' );
-
- $this->mock_wcpay_account->method( 'get_account_country' )->willReturn( 'US' );
- $mock_order = $this->mock_level_3_order( '98012', false, true, 1, 1 );
- $level_3_data = $this->wcpay_gateway->get_level3_data_from_order( $mock_order );
-
- $this->assertEquals( $expected_data, $level_3_data );
- }
-
- public function test_us_store_level_3_data() {
- // Use a non-us customer postcode to ensure it's not included in the level3 data.
- $this->mock_wcpay_account->method( 'get_account_country' )->willReturn( 'US' );
- $mock_order = $this->mock_level_3_order( '9000' );
- $level_3_data = $this->wcpay_gateway->get_level3_data_from_order( $mock_order );
-
- $this->assertArrayNotHasKey( 'shipping_address_zip', $level_3_data );
- }
-
- public function test_us_customer_level_3_data() {
- $expected_data = [
- 'merchant_reference' => '210',
- 'customer_reference' => '210',
- 'shipping_amount' => 3800,
- 'line_items' => [
- (object) [
- 'product_code' => 30,
- 'product_description' => 'Beanie with Logo',
- 'unit_cost' => 1800,
- 'quantity' => 1,
- 'tax_amount' => 270,
- 'discount_amount' => 0,
- ],
- ],
- 'shipping_address_zip' => '98012',
- ];
-
- // Use a non-US postcode.
- update_option( 'woocommerce_store_postcode', '9000' );
-
- $this->mock_wcpay_account->method( 'get_account_country' )->willReturn( 'US' );
- $mock_order = $this->mock_level_3_order( '98012' );
- $level_3_data = $this->wcpay_gateway->get_level3_data_from_order( $mock_order );
-
- $this->assertEquals( $expected_data, $level_3_data );
- }
-
- public function test_non_us_customer_level_3_data() {
- $expected_data = [];
-
- $this->mock_wcpay_account->method( 'get_account_country' )->willReturn( 'CA' );
- $mock_order = $this->mock_level_3_order( 'K0A' );
- $level_3_data = $this->wcpay_gateway->get_level3_data_from_order( $mock_order );
-
- $this->assertEquals( $expected_data, $level_3_data );
- }
-
- public function test_full_level3_data_with_float_quantity() {
- $expected_data = [
- 'merchant_reference' => '210',
- 'customer_reference' => '210',
- 'shipping_amount' => 3800,
- 'line_items' => [
- (object) [
- 'product_code' => 30,
- 'product_description' => 'Beanie with Logo',
- 'unit_cost' => 450,
- 'quantity' => 4,
- 'tax_amount' => 270,
- 'discount_amount' => 0,
- ],
- ],
- 'shipping_address_zip' => '98012',
- 'shipping_from_zip' => '94110',
- ];
-
- update_option( 'woocommerce_store_postcode', '94110' );
-
- $this->mock_wcpay_account->method( 'get_account_country' )->willReturn( 'US' );
- $mock_order = $this->mock_level_3_order( '98012', false, false, 3.7 );
- $level_3_data = $this->wcpay_gateway->get_level3_data_from_order( $mock_order );
-
- $this->assertEquals( $expected_data, $level_3_data );
- }
-
- public function test_full_level3_data_with_float_quantity_zero() {
- $expected_data = [
- 'merchant_reference' => '210',
- 'customer_reference' => '210',
- 'shipping_amount' => 3800,
- 'line_items' => [
- (object) [
- 'product_code' => 30,
- 'product_description' => 'Beanie with Logo',
- 'unit_cost' => 1800,
- 'quantity' => 1,
- 'tax_amount' => 270,
- 'discount_amount' => 0,
- ],
- ],
- 'shipping_address_zip' => '98012',
- 'shipping_from_zip' => '94110',
- ];
-
- update_option( 'woocommerce_store_postcode', '94110' );
-
- $this->mock_wcpay_account->method( 'get_account_country' )->willReturn( 'US' );
- $mock_order = $this->mock_level_3_order( '98012', false, false, 0.4 );
- $level_3_data = $this->wcpay_gateway->get_level3_data_from_order( $mock_order );
-
- $this->assertEquals( $expected_data, $level_3_data );
- }
-
- public function test_level3_data_bundle() {
- $items = (array) [
- (object) [
- 'product_code' => 'abcd',
- 'product_description' => 'product description',
- 'unit_cost' => 1000,
- 'quantity' => 4,
- 'tax_amount' => 200,
- 'discount_amount' => 500,
- ],
- (object) [
- 'product_code' => 'abcd',
- 'product_description' => 'product description',
- 'unit_cost' => 5000,
- 'quantity' => 3,
- 'tax_amount' => 1000,
- 'discount_amount' => 200,
- ],
- ];
-
- $bundle_data = $this->wcpay_gateway->bundle_level3_data_from_items( $items );
-
- $this->assertSame( $bundle_data->product_description, '2 more items' );
-
- // total_unit_cost = sum( unit_cost * quantity ).
- $this->assertSame( $bundle_data->unit_cost, 19000 );
-
- // quantity of the bundle = 1.
- $this->assertSame( $bundle_data->quantity, 1 );
-
- // total_tax_amount = sum( tax_amount ).
- $this->assertSame( $bundle_data->tax_amount, 1200 );
-
- // total_discount_amount = sum( discount_amount ).
- $this->assertSame( $bundle_data->discount_amount, 700 );
- }
-
- public function test_level3_data_bundle_for_orders_with_more_than_200_items() {
- $this->mock_wcpay_account->method( 'get_account_country' )->willReturn( 'US' );
- $mock_order = $this->mock_level_3_order( '98012', true, false, 1, 500 );
- $level_3_data = $this->wcpay_gateway->get_level3_data_from_order( $mock_order );
-
- $this->assertSame( count( $level_3_data['line_items'] ), 200 );
-
- $bundled_data = end( $level_3_data['line_items'] );
-
- $this->assertSame( $bundled_data->product_description, '301 more items' );
- }
-
public function test_capture_charge_success() {
$intent_id = 'pi_mock';
$charge_id = 'ch_mock';
@@ -858,16 +458,11 @@ public function test_capture_charge_success() {
->with( $mock_intent->get_amount() );
$capture_intent_request->expects( $this->once() )
->method( 'set_metadata' )
- ->with( $this->anything() );
+ ->with( [ 'gateway_type' => 'legacy_card' ] );
$capture_intent_request->expects( $this->once() )
->method( 'format_response' )
->willReturn( WC_Helper_Intention::create_intention() );
- $this->mock_wcpay_account
- ->expects( $this->once() )
- ->method( 'get_account_country' )
- ->willReturn( 'US' );
-
$result = $this->wcpay_gateway->capture_charge( $order );
$notes = wc_get_order_notes(
@@ -918,16 +513,11 @@ public function test_capture_charge_success_non_usd() {
->with( $mock_intent->get_amount() );
$capture_intent_request->expects( $this->once() )
->method( 'set_metadata' )
- ->with( $this->anything() );
+ ->with( [ 'gateway_type' => 'legacy_card' ] );
$capture_intent_request->expects( $this->once() )
->method( 'format_response' )
->willReturn( WC_Helper_Intention::create_intention( [ 'currency' => 'eur' ] ) );
- $this->mock_wcpay_account
- ->expects( $this->once() )
- ->method( 'get_account_country' )
- ->willReturn( 'US' );
-
$result = $this->wcpay_gateway->capture_charge( $order );
$notes = wc_get_order_notes(
@@ -975,16 +565,11 @@ public function test_capture_charge_failure() {
->with( $mock_intent->get_amount() );
$capture_intent_request->expects( $this->once() )
->method( 'set_metadata' )
- ->with( $this->anything() );
+ ->with( [ 'gateway_type' => 'legacy_card' ] );
$capture_intent_request->expects( $this->once() )
->method( 'format_response' )
->willReturn( $mock_intent );
- $this->mock_wcpay_account
- ->expects( $this->once() )
- ->method( 'get_account_country' )
- ->willReturn( 'US' );
-
$result = $this->wcpay_gateway->capture_charge( $order );
$note = wc_get_order_notes(
@@ -1035,16 +620,11 @@ public function test_capture_charge_failure_non_usd() {
->with( $mock_intent->get_amount() );
$capture_intent_request->expects( $this->once() )
->method( 'set_metadata' )
- ->with( $this->anything() );
+ ->with( [ 'gateway_type' => 'legacy_card' ] );
$capture_intent_request->expects( $this->once() )
->method( 'format_response' )
->willReturn( $mock_intent );
- $this->mock_wcpay_account
- ->expects( $this->once() )
- ->method( 'get_account_country' )
- ->willReturn( 'US' );
-
$result = $this->wcpay_gateway->capture_charge( $order );
$note = wc_get_order_notes(
@@ -1097,16 +677,11 @@ public function test_capture_charge_api_failure() {
->with( $mock_intent->get_amount() );
$capture_intent_request->expects( $this->once() )
->method( 'set_metadata' )
- ->with( $this->anything() );
+ ->with( [ 'gateway_type' => 'legacy_card' ] );
$capture_intent_request->expects( $this->once() )
->method( 'format_response' )
->will( $this->throwException( new API_Exception( 'test exception', 'server_error', 500 ) ) );
- $this->mock_wcpay_account
- ->expects( $this->once() )
- ->method( 'get_account_country' )
- ->willReturn( 'US' );
-
$result = $this->wcpay_gateway->capture_charge( $order );
$note = wc_get_order_notes(
@@ -1164,16 +739,11 @@ public function test_capture_charge_api_failure_non_usd() {
->with( $mock_intent->get_amount() );
$capture_intent_request->expects( $this->once() )
->method( 'set_metadata' )
- ->with( $this->anything() );
+ ->with( [ 'gateway_type' => 'legacy_card' ] );
$capture_intent_request->expects( $this->once() )
->method( 'format_response' )
->will( $this->throwException( new API_Exception( 'test exception', 'server_error', 500 ) ) );
- $this->mock_wcpay_account
- ->expects( $this->once() )
- ->method( 'get_account_country' )
- ->willReturn( 'US' );
-
$result = $this->wcpay_gateway->capture_charge( $order );
$note = wc_get_order_notes(
@@ -1227,16 +797,11 @@ public function test_capture_charge_expired() {
->with( $mock_intent->get_amount() );
$capture_intent_request->expects( $this->once() )
->method( 'set_metadata' )
- ->with( $this->anything() );
+ ->with( [ 'gateway_type' => 'legacy_card' ] );
$capture_intent_request->expects( $this->once() )
->method( 'format_response' )
->will( $this->throwException( new API_Exception( 'test exception', 'server_error', 500 ) ) );
- $this->mock_wcpay_account
- ->expects( $this->once() )
- ->method( 'get_account_country' )
- ->willReturn( 'US' );
-
$result = $this->wcpay_gateway->capture_charge( $order );
$note = wc_get_order_notes(
@@ -1284,38 +849,18 @@ public function test_capture_charge_metadata() {
]
);
- $merged_metadata = [
- 'customer_name' => 'Test',
- 'reader_ID' => 'wisepad',
- 'customer_email' => $order->get_billing_email(),
- 'site_url' => esc_url( get_site_url() ),
- 'order_id' => $order->get_id(),
- 'order_number' => $order->get_order_number(),
- 'order_key' => $order->get_order_key(),
- 'payment_type' => Payment_Type::SINGLE(),
- 'gateway_type' => 'legacy_card',
- 'checkout_type' => '',
- 'client_version' => WCPAY_VERSION_NUMBER,
- 'subscription_payment' => 'no',
- ];
-
$capture_intent_request = $this->mock_wcpay_request( Capture_Intention::class, 1, $intent_id );
$capture_intent_request->expects( $this->once() )
->method( 'set_amount_to_capture' )
->with( $mock_intent->get_amount() );
$capture_intent_request->expects( $this->once() )
->method( 'set_metadata' )
- ->with( $merged_metadata );
+ ->with( [ 'gateway_type' => 'legacy_card' ] );
$capture_intent_request->expects( $this->once() )
->method( 'format_response' )
->willReturn( WC_Helper_Intention::create_intention() );
- $this->mock_wcpay_account
- ->expects( $this->once() )
- ->method( 'get_account_country' )
- ->willReturn( 'US' );
-
- $result = $this->wcpay_gateway->capture_charge( $order, true, $merged_metadata );
+ $result = $this->wcpay_gateway->capture_charge( $order, true, [] );
$note = wc_get_order_notes(
[
@@ -1359,7 +904,7 @@ public function test_capture_charge_without_level3() {
->with( $mock_intent->get_amount() );
$capture_intent_request->expects( $this->once() )
->method( 'set_metadata' )
- ->with( $this->anything() );
+ ->with( [ 'gateway_type' => 'legacy_card' ] );
$capture_intent_request->expects( $this->once() )
->method( 'format_response' )
->willReturn( WC_Helper_Intention::create_intention() );
@@ -1965,19 +1510,7 @@ public function test_process_payment_caches_mimimum_amount_and_displays_error_up
$request->expects( $this->once() )
->method( 'set_metadata' )
- ->with(
- $this->callback(
- function( $metadata ) {
- $required_keys = [ 'customer_name', 'customer_email', 'site_url', 'order_id', 'order_number', 'order_key', 'payment_type' ];
- foreach ( $required_keys as $key ) {
- if ( ! array_key_exists( $key, $metadata ) ) {
- return false;
- }
- }
- return true;
- }
- )
- );
+ ->with( [ 'gateway_type' => 'legacy_card' ] );
$request->expects( $this->once() )
->method( 'format_response' )
From f8b1b2bbc762e5a4e9533f618e0499e83930df5b Mon Sep 17 00:00:00 2001
From: Zvonimir Maglica
Date: Thu, 5 Oct 2023 14:13:04 +0200
Subject: [PATCH 69/98] Added documentation for payment methods API (#7369)
Co-authored-by: jessy <32092402+jessy-p@users.noreply.github.com>
---
...-api-documentation-for-payment-methods-api | 4 +
.../source/includes/wp-api-v3/customer.md | 93 +++++++++++++++++++
2 files changed, 97 insertions(+)
create mode 100644 changelog/add-7368-add-api-documentation-for-payment-methods-api
create mode 100644 docs/rest-api/source/includes/wp-api-v3/customer.md
diff --git a/changelog/add-7368-add-api-documentation-for-payment-methods-api b/changelog/add-7368-add-api-documentation-for-payment-methods-api
new file mode 100644
index 00000000000..b2e92105371
--- /dev/null
+++ b/changelog/add-7368-add-api-documentation-for-payment-methods-api
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Added documentation for payment methods API endpoint
diff --git a/docs/rest-api/source/includes/wp-api-v3/customer.md b/docs/rest-api/source/includes/wp-api-v3/customer.md
new file mode 100644
index 00000000000..692224f8b87
--- /dev/null
+++ b/docs/rest-api/source/includes/wp-api-v3/customer.md
@@ -0,0 +1,93 @@
+# Customer
+
+The Customers API endpoints provide access to customer data. This includes payment methods and other key information useful for your application.
+
+
+## Get customer's payment methods
+
+_@since v6.6.0_
+
+Return all customer's payment methods.
+
+### Error codes
+
+- `rest_forbidden` - indicates that the user or application does not have the necessary permissions to perform the requested action.
+
+### HTTP request
+
+
+
+ GET
+
/wp-json/wc/v3/payments/customers/<customer_id>/payment_methods
+
+
+
+```shell
+curl -X GET https://example.com/wp-json/wc/v3/payments/customers/cus_123456/payment_methods \
+ -u consumer_key:consumer_secret \
+ -H "Content-Type: application/json"
+```
+
+> JSON response example:
+
+```json
+[
+ {
+ "id": "pm_1AxXc2a5dGhIZQYPlaLbKj1Z",
+ "type": "card",
+ "billing_details": {
+ "address": {
+ "city": "Los Angeles",
+ "country": "US",
+ "line1": "123 Anywhere St",
+ "line2": null,
+ "postal_code": "90002",
+ "state": "CA"
+ },
+ "email": "john.doe@example.com",
+ "name": "John Doe",
+ "phone": null
+ },
+ "card": {
+ "brand": "visa",
+ "last4": "1122",
+ "exp_month": 10,
+ "exp_year": 2028
+ }
+ },
+ {
+ "id": "pm_2BcYd3e6hKjZLQWQmMnOjK45",
+ "type": "card",
+ "billing_details": {
+ "address": {
+ "city": "New York",
+ "country": "US",
+ "line1": "456 Broadway Ave",
+ "line2": null,
+ "postal_code": "10012",
+ "state": "NY"
+ },
+ "email": "jane.smith@example.com",
+ "name": "Jane Smith",
+ "phone": null
+ },
+ "card": {
+ "brand": "mastercard",
+ "last4": "3344",
+ "exp_month": 12,
+ "exp_year": 2027
+ }
+ }
+]
+
+```
+
+```json
+{
+ "code":"rest_forbidden",
+ "message":"Sorry, you are not allowed to do that.",
+ "data":{
+ "status":401
+ }
+}
+```
From 424732826ad99eb71fe69f6b63d484e434f1294f Mon Sep 17 00:00:00 2001
From: Zvonimir Maglica
Date: Thu, 5 Oct 2023 14:20:57 +0200
Subject: [PATCH 70/98] Add documentation for create payment intent api
endpoint (#7384)
---
...ion-for-create-payment-intent-api-endpoint | 4 +
.../source/includes/wp-api-v3/intent.md | 87 +++++++++++++++++++
2 files changed, 91 insertions(+)
create mode 100644 changelog/add-7381-add-documentation-for-create-payment-intent-api-endpoint
create mode 100644 docs/rest-api/source/includes/wp-api-v3/intent.md
diff --git a/changelog/add-7381-add-documentation-for-create-payment-intent-api-endpoint b/changelog/add-7381-add-documentation-for-create-payment-intent-api-endpoint
new file mode 100644
index 00000000000..2d4310612e6
--- /dev/null
+++ b/changelog/add-7381-add-documentation-for-create-payment-intent-api-endpoint
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Added documentation for create payment intent API endpoint.
diff --git a/docs/rest-api/source/includes/wp-api-v3/intent.md b/docs/rest-api/source/includes/wp-api-v3/intent.md
new file mode 100644
index 00000000000..d5d38d50986
--- /dev/null
+++ b/docs/rest-api/source/includes/wp-api-v3/intent.md
@@ -0,0 +1,87 @@
+# Payment intents
+
+The Payment Intents API provides comprehensive functionality for managing payment intents. You can create and manage them seamlessly through its endpoints.
+
+
+## Create payment intenr
+
+_@since v6.6.0_
+
+Create new payment intent.
+
+### Error codes
+
+- `rest_forbidden` - indicates that the user or application does not have the necessary permissions to perform the requested action.
+- `wcpay_server_error` - Indicates that API had error processing the request. Usually occurs when request params are invalid like order is not found, and similar.
+
+### HTTP request
+
+
+
+ POST
+
/wp-json/wc/v3/payments/payment_intents
+
+
+
+```shell
+curl -X POST https://example.com/wp-json/wc/v3/payments/payment_intents \
+ -u consumer_key:consumer_secret \
+ -H "Content-Type: application/json" \
+ -d '{"payment_method":"","customer":"","order_id":""}'
+```
+
+> JSON response example:
+
+```json
+{
+ "id": "pi_4NxlPtR3eYmZSVZP0PPpwee8",
+ "amount": 1023,
+ "currency": "USD",
+ "created": 1696496578,
+ "customer": "cus_OUQoHGzJLw87Tk",
+ "payment_method": "pm_2NlT19R3eYmZSVZPRJEvxvwF",
+ "status": "succeeded",
+ "charge": {
+ "id": "ch_4NxlPtR3eYmZSVZP0ZLpKjfI",
+ "amount": 1023,
+ "application_fee_amount": 62,
+ "status": "succeeded",
+ "billing_details": {
+ "address": {
+ "city": "San Francisco",
+ "country": "US",
+ "line1": "123 Random St",
+ "line2": "-",
+ "postal_code": "94101",
+ "state": "CA"
+ },
+ "email": "random.email@example.com",
+ "name": "John Doe",
+ "phone": "5555555555"
+ },
+ "payment_method_details": {
+ "card": {
+ "amount_authorized": 1023,
+ "brand": "mastercard",
+ "capture_before": "",
+ "country": "US",
+ "exp_month": 5,
+ "exp_year": 2030,
+ "last4": "4321",
+ "three_d_secure": ""
+ }
+ }
+ }
+}
+
+```
+
+```json
+{
+ "code":"rest_forbidden",
+ "message":"Sorry, you are not allowed to do that.",
+ "data":{
+ "status":401
+ }
+}
+```
From cda6a744b5df20ddcf4729460b49d38666aa3051 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ismael=20Mart=C3=ADn=20Alabarce?=
Date: Thu, 5 Oct 2023 15:05:32 +0200
Subject: [PATCH 71/98] Prevent onboarding flow access without server
connection (#7386)
Co-authored-by: Oleksandr Aratovskyi <79862886+oaratovskyi@users.noreply.github.com>
---
...-7360-non-connected-onboarding-flow-access | 4 +
includes/admin/class-wc-payments-admin.php | 20 +++++
.../admin/test-class-wc-payments-admin.php | 87 ++++++++++++++++++-
3 files changed, 109 insertions(+), 2 deletions(-)
create mode 100644 changelog/fix-7360-non-connected-onboarding-flow-access
diff --git a/changelog/fix-7360-non-connected-onboarding-flow-access b/changelog/fix-7360-non-connected-onboarding-flow-access
new file mode 100644
index 00000000000..3af6b6a4ca2
--- /dev/null
+++ b/changelog/fix-7360-non-connected-onboarding-flow-access
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Redirect back to the connect page when attempting to access the new onboarding flow without a server connection.
diff --git a/includes/admin/class-wc-payments-admin.php b/includes/admin/class-wc-payments-admin.php
index 9b570625452..e77918581d6 100644
--- a/includes/admin/class-wc-payments-admin.php
+++ b/includes/admin/class-wc-payments-admin.php
@@ -195,6 +195,7 @@ public function init_hooks() {
add_action( 'admin_menu', [ $this, 'add_payments_menu' ], 0 );
add_action( 'admin_init', [ $this, 'maybe_redirect_to_onboarding' ], 11 ); // Run this after the WC setup wizard and onboarding redirection logic.
add_action( 'admin_enqueue_scripts', [ $this, 'maybe_redirect_overview_to_connect' ], 1 ); // Run this late (after `admin_init`) but before any scripts are actually enqueued.
+ add_action( 'admin_enqueue_scripts', [ $this, 'maybe_redirect_onboarding_flow_to_connect' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'register_payments_scripts' ], 9 );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_payments_scripts' ], 9 );
add_action( 'woocommerce_admin_field_payment_gateways', [ $this, 'payment_gateways_container' ] );
@@ -1098,6 +1099,25 @@ public function maybe_redirect_overview_to_connect() {
$this->account->redirect_to_onboarding_welcome_page();
}
+ /**
+ * Prevent access to onboarding flow if the server is not connected.
+ * Redirect back to the connect page with an error message.
+ */
+ public function maybe_redirect_onboarding_flow_to_connect(): void {
+ $url_params = wp_unslash( $_GET ); // phpcs:ignore WordPress.Security.NonceVerification
+ if ( isset( $url_params['page'] ) && 'wc-admin' === $url_params['page']
+ && isset( $url_params['path'] ) && '/payments/onboarding' === $url_params['path'] && ! $this->payments_api_client->is_server_connected() ) {
+ $this->account->redirect_to_onboarding_welcome_page(
+ sprintf(
+ /* translators: %s: WooPayments */
+ __( 'Please connect to WordPress.com to start using %s.', 'woocommerce-payments' ),
+ 'WooPayments'
+ )
+ );
+ return;
+ }
+ }
+
/**
* Add woopay as a payment method to the edit order on admin.
*
diff --git a/tests/unit/admin/test-class-wc-payments-admin.php b/tests/unit/admin/test-class-wc-payments-admin.php
index 1447d17234e..10079f3838b 100644
--- a/tests/unit/admin/test-class-wc-payments-admin.php
+++ b/tests/unit/admin/test-class-wc-payments-admin.php
@@ -23,6 +23,13 @@ class WC_Payments_Admin_Test extends WCPAY_UnitTestCase {
*/
private $mock_gateway;
+ /**
+ * Mock WC_Payments_API_Client.
+ *
+ * @var WC_Payments_API_Client|MockObject
+ */
+ private $mock_api_client;
+
/**
* Mock Onboarding Service.
*
@@ -62,7 +69,7 @@ public function set_up() {
$menu = null; // phpcs:ignore: WordPress.WP.GlobalVariablesOverride.Prohibited
$submenu = null; // phpcs:ignore: WordPress.WP.GlobalVariablesOverride.Prohibited
- $mock_api_client = $this->getMockBuilder( WC_Payments_API_Client::class )
+ $this->mock_api_client = $this->getMockBuilder( WC_Payments_API_Client::class )
->disableOriginalConstructor()
->getMock();
@@ -99,7 +106,7 @@ public function set_up() {
);
$this->payments_admin = new WC_Payments_Admin(
- $mock_api_client,
+ $this->mock_api_client,
$this->mock_gateway,
$this->mock_account,
$this->mock_onboarding_service,
@@ -373,6 +380,82 @@ public function data_maybe_redirect_overview_to_connect() {
];
}
+ /**
+ * @dataProvider data_maybe_redirect_onboarding_flow_to_connect
+ */
+ public function test_maybe_redirect_onboarding_flow_to_connect( $expected_times_redirect_called, $is_server_connected, $get_params ) {
+ $_GET = $get_params;
+
+ $this->mock_api_client
+ ->method( 'is_server_connected' )
+ ->willReturn( $is_server_connected );
+
+ $this->mock_account
+ ->expects( $this->exactly( $expected_times_redirect_called ) )
+ ->method( 'redirect_to_onboarding_welcome_page' );
+
+ $this->payments_admin->maybe_redirect_onboarding_flow_to_connect();
+ }
+
+ /**
+ * Data provider for test_maybe_redirect_onboarding_flow_to_connect
+ */
+ public function data_maybe_redirect_onboarding_flow_to_connect() {
+ return [
+ 'no_get_params' => [
+ 0,
+ false,
+ [],
+ ],
+ 'empty_page_param' => [
+ 0,
+ false,
+ [
+ 'path' => '/payments/onboarding',
+ ],
+ ],
+ 'incorrect_page_param' => [
+ 0,
+ false,
+ [
+ 'page' => 'wc-settings',
+ 'path' => '/payments/onboarding',
+ ],
+ ],
+ 'empty_path_param' => [
+ 0,
+ false,
+ [
+ 'page' => 'wc-admin',
+ ],
+ ],
+ 'incorrect_path_param' => [
+ 0,
+ false,
+ [
+ 'page' => 'wc-admin',
+ 'path' => '/payments/does-not-exist',
+ ],
+ ],
+ 'server_connected' => [
+ 0,
+ true,
+ [
+ 'page' => 'wc-admin',
+ 'path' => '/payments/onboarding',
+ ],
+ ],
+ 'happy_path' => [
+ 1,
+ false,
+ [
+ 'page' => 'wc-admin',
+ 'path' => '/payments/onboarding',
+ ],
+ ],
+ ];
+ }
+
/**
* Tests WC_Payments_Admin::add_disputes_notification_badge()
*/
From b57f91bfab770c8645bccd14a7c079af31ad5775 Mon Sep 17 00:00:00 2001
From: Oleksandr Aratovskyi <79862886+oaratovskyi@users.noreply.github.com>
Date: Thu, 5 Oct 2023 16:09:53 +0300
Subject: [PATCH 72/98] Add e2e tests for progressive onboarding (#7309)
Co-authored-by: Vlad Olaru
---
...-6214-e2e-tests-for-progressive-onboarding | 4 +
.../custom-select-control/index.tsx | 3 +
.../grouped-select-control/index.tsx | 3 +
client/onboarding/form.tsx | 24 ++--
.../merchant-progressive-onboarding.spec.js | 128 ++++++++++++++++++
tests/e2e/utils/flows.js | 82 +++++++++++
6 files changed, 232 insertions(+), 12 deletions(-)
create mode 100644 changelog/dev-6214-e2e-tests-for-progressive-onboarding
create mode 100644 tests/e2e/specs/wcpay/merchant/merchant-progressive-onboarding.spec.js
diff --git a/changelog/dev-6214-e2e-tests-for-progressive-onboarding b/changelog/dev-6214-e2e-tests-for-progressive-onboarding
new file mode 100644
index 00000000000..73526b51ed3
--- /dev/null
+++ b/changelog/dev-6214-e2e-tests-for-progressive-onboarding
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+e2e tests for progressive onboarding
diff --git a/client/components/custom-select-control/index.tsx b/client/components/custom-select-control/index.tsx
index 88fca32ae53..3fe7de467cb 100644
--- a/client/components/custom-select-control/index.tsx
+++ b/client/components/custom-select-control/index.tsx
@@ -30,6 +30,7 @@ export interface Item {
}
export interface ControlProps< ItemType > {
+ name?: string;
className?: string;
label: string;
describedBy?: string;
@@ -81,6 +82,7 @@ const stateReducer = (
};
function CustomSelectControl< ItemType extends Item >( {
+ name,
className,
label,
describedBy,
@@ -168,6 +170,7 @@ function CustomSelectControl< ItemType extends Item >( {
'components-custom-select-control__button',
{ placeholder: ! itemString }
),
+ name,
} ) }
>
diff --git a/client/components/grouped-select-control/index.tsx b/client/components/grouped-select-control/index.tsx
index 013605ce609..a8b75dfea0c 100644
--- a/client/components/grouped-select-control/index.tsx
+++ b/client/components/grouped-select-control/index.tsx
@@ -27,11 +27,13 @@ export interface GroupedSelectControlProps< ItemType > {
value?: ItemType | null;
placeholder?: string;
searchable?: boolean;
+ name?: string;
className?: string;
onChange?: ( changes: Partial< UseSelectState< ItemType > > ) => void;
}
const GroupedSelectControl = < ItemType extends ListItem >( {
+ name,
className,
label,
options: listItems,
@@ -176,6 +178,7 @@ const GroupedSelectControl = < ItemType extends ListItem >( {
'components-text-control__input wcpay-component-grouped-select-control__button',
{ placeholder }
),
+ name,
} ) }
>
diff --git a/client/onboarding/form.tsx b/client/onboarding/form.tsx
index 1b3f2852ec9..d5cb1eeb4ed 100644
--- a/client/onboarding/form.tsx
+++ b/client/onboarding/form.tsx
@@ -58,10 +58,10 @@ interface OnboardingTextFieldProps extends Partial< TextFieldProps > {
name: keyof OnboardingFields;
}
-export const OnboardingTextField: React.FC< OnboardingTextFieldProps > = ( {
- name,
- ...rest
-} ) => {
+export const OnboardingTextField: React.FC< OnboardingTextFieldProps > = (
+ props
+) => {
+ const { name } = props;
const { data, setData, touched } = useOnboardingContext();
const { validate, error } = useValidation( name );
const inputRef = React.useRef< HTMLInputElement >( null );
@@ -85,7 +85,7 @@ export const OnboardingTextField: React.FC< OnboardingTextFieldProps > = ( {
if ( event.key === 'Enter' ) validate();
} }
error={ error() }
- { ...rest }
+ { ...props }
/>
);
};
@@ -95,10 +95,10 @@ interface OnboardingPhoneNumberFieldProps
name: keyof OnboardingFields;
}
-export const OnboardingPhoneNumberField: React.FC< OnboardingPhoneNumberFieldProps > = ( {
- name,
- ...rest
-} ) => {
+export const OnboardingPhoneNumberField: React.FC< OnboardingPhoneNumberFieldProps > = (
+ props
+) => {
+ const { name } = props;
const { data, setData, temp, setTemp, touched } = useOnboardingContext();
const { validate, error } = useValidation( name );
@@ -117,7 +117,7 @@ export const OnboardingPhoneNumberField: React.FC< OnboardingPhoneNumberFieldPro
onKeyDown={ ( event: React.KeyboardEvent< HTMLInputElement > ) => {
if ( event.key === 'Enter' ) validate();
} }
- { ...rest }
+ { ...props }
/>
);
};
@@ -129,10 +129,10 @@ interface OnboardingSelectFieldProps< ItemType >
}
export const OnboardingSelectField = < ItemType extends SelectItem >( {
- name,
onChange,
...rest
}: OnboardingSelectFieldProps< ItemType > ): JSX.Element => {
+ const { name } = rest;
const { data, setData } = useOnboardingContext();
const { validate, error } = useValidation( name );
@@ -169,10 +169,10 @@ interface OnboardingGroupedSelectFieldProps< ItemType >
export const OnboardingGroupedSelectField = <
ListItemType extends GroupedSelectItem
>( {
- name,
onChange,
...rest
}: OnboardingGroupedSelectFieldProps< ListItemType > ): JSX.Element => {
+ const { name } = rest;
const { data, setData } = useOnboardingContext();
const { validate, error } = useValidation( name );
diff --git a/tests/e2e/specs/wcpay/merchant/merchant-progressive-onboarding.spec.js b/tests/e2e/specs/wcpay/merchant/merchant-progressive-onboarding.spec.js
new file mode 100644
index 00000000000..e7cf88553e1
--- /dev/null
+++ b/tests/e2e/specs/wcpay/merchant/merchant-progressive-onboarding.spec.js
@@ -0,0 +1,128 @@
+/**
+ * External dependencies
+ */
+const { merchant, evalAndClick } = require( '@woocommerce/e2e-utils' );
+
+/**
+ * Internal dependencies
+ */
+import { merchantWCP, uiLoaded } from '../../../utils';
+
+describe( 'Admin merchant progressive onboarding', () => {
+ beforeAll( async () => {
+ await merchant.login();
+ await merchantWCP.enableProgressiveOnboarding();
+ await merchantWCP.enableActAsDisconnectedFromWCPay();
+ } );
+
+ afterAll( async () => {
+ await merchantWCP.disableProgressiveOnboarding();
+ await merchantWCP.disableActAsDisconnectedFromWCPay();
+ await merchant.logout();
+ } );
+
+ it( 'should pass merchant flow without any errors', async () => {
+ // Open connect account page and click Finish Setup
+ await merchantWCP.openConnectPage();
+ await Promise.all( [
+ evalAndClick(
+ 'div.connect-account-page button.components-button.is-primary'
+ ),
+ page.waitForNavigation( { waitUntil: 'networkidle0' } ),
+ uiLoaded(),
+ ] );
+
+ // Merchant vs builder flow step
+ await expect( page ).toMatchElement( 'h1.stepper__heading', {
+ text: 'Let’s get your store ready to accept payments',
+ } );
+ await expect( page ).toClick(
+ 'div.stepper__content button.components-button.is-primary',
+ {
+ text: 'Continue',
+ }
+ );
+
+ // User details step
+ await expect( page ).toMatchElement( 'h1.stepper__heading', {
+ text: 'First, you’ll need to create an account',
+ } );
+ await expect( page ).toFill( '[name="individual.first_name"]', 'Test' );
+ await expect( page ).toFill( '[name="individual.last_name"]', 'Test' );
+ await expect( page ).toFill( '[name="email"]', 'test@gmail.com' );
+ await page.waitForSelector(
+ 'div.wcpay-component-phone-number-control input[type="text"]'
+ );
+ await expect( page ).toFill(
+ 'div.wcpay-component-phone-number-control input[type="text"]',
+ '0000000000'
+ );
+ await expect( page ).toClick(
+ 'div.stepper__content button.components-button.is-primary',
+ {
+ text: 'Continue',
+ }
+ );
+
+ // Tell us about your business step
+ await expect( page ).toMatchElement( 'h1.stepper__heading', {
+ text: 'Tell us about your business',
+ } );
+ // pick Individual business entity
+ await expect( page ).toClick( '[name="business_type"]' );
+ await page.waitForSelector(
+ '[name="business_type"] ~ ul li.components-custom-select-control__item'
+ );
+ await expect( page ).toClick(
+ '[name="business_type"] ~ ul li.components-custom-select-control__item'
+ );
+ // pick Software type of goods
+ await expect( page ).toClick( '[name="mcc"]' );
+ await page.waitForSelector(
+ '[name="mcc"] ~ ul li.wcpay-component-grouped-select-control__item:not(.is-group)'
+ );
+ await expect( page ).toClick(
+ '[name="mcc"] ~ ul li.wcpay-component-grouped-select-control__item:not(.is-group)'
+ );
+ await expect( page ).toClick(
+ 'div.stepper__content button.components-button.is-primary',
+ {
+ text: 'Continue',
+ }
+ );
+
+ // Store details step: pick annual revenue and go live timeframe
+ await expect( page ).toMatchElement( 'h1.stepper__heading', {
+ text: 'Please share a few more details',
+ } );
+ await expect( page ).toClick( '[name="annual_revenue"]' );
+ await page.waitForSelector(
+ '[name="annual_revenue"] ~ ul li.components-custom-select-control__item'
+ );
+ await expect( page ).toClick(
+ '[name="annual_revenue"] ~ ul li.components-custom-select-control__item'
+ );
+ await expect( page ).toClick( '[name="go_live_timeframe"]' );
+ await page.waitForSelector(
+ '[name="go_live_timeframe"] ~ ul li.components-custom-select-control__item'
+ );
+ await expect( page ).toClick(
+ '[name="go_live_timeframe"] ~ ul li.components-custom-select-control__item'
+ );
+ await expect( page ).toClick(
+ 'div.stepper__content button.components-button.is-primary',
+ {
+ text: 'Continue',
+ }
+ );
+
+ // Loading screen
+ await expect( page ).toMatchElement( 'h1.stepper__heading', {
+ text: 'Let’s get you set up for payments',
+ } );
+
+ // Merchant is redirected away to payments/connect again (because of force fisconnected option)
+ // todo at some point test real Stripe KYC
+ await page.waitForNavigation( { waitUntil: 'networkidle0' } );
+ } );
+} );
diff --git a/tests/e2e/utils/flows.js b/tests/e2e/utils/flows.js
index 0a342e136ab..6459acaf6ca 100644
--- a/tests/e2e/utils/flows.js
+++ b/tests/e2e/utils/flows.js
@@ -30,6 +30,8 @@ const WC_ADMIN_BASE_URL = baseUrl + 'wp-admin/';
const MY_ACCOUNT_SUBSCRIPTIONS = baseUrl + 'my-account/subscriptions';
const MY_ACCOUNT_EDIT = baseUrl + 'my-account/edit-account';
const MY_ACCOUNT_ORDERS = SHOP_MY_ACCOUNT_PAGE + 'orders/';
+const WCPAY_CONNECT =
+ baseUrl + 'wp-admin/admin.php?page=wc-admin&path=/payments/connect';
const WCPAY_DISPUTES =
baseUrl + 'wp-admin/admin.php?page=wc-admin&path=/payments/disputes';
const WCPAY_DEPOSITS =
@@ -462,6 +464,79 @@ export const merchantWCP = {
} );
},
+ enableProgressiveOnboarding: async () => {
+ await page.goto( WCPAY_DEV_TOOLS, {
+ waitUntil: 'networkidle0',
+ } );
+
+ if (
+ ! ( await page.$(
+ '#_wcpay_feature_progressive_onboarding:checked'
+ ) )
+ ) {
+ await expect( page ).toClick(
+ 'label[for="_wcpay_feature_progressive_onboarding"]'
+ );
+ }
+
+ await expect( page ).toClick( 'input#submit' );
+ await page.waitForNavigation( {
+ waitUntil: 'networkidle0',
+ } );
+ },
+
+ disableProgressiveOnboarding: async () => {
+ await page.goto( WCPAY_DEV_TOOLS, {
+ waitUntil: 'networkidle0',
+ } );
+
+ if (
+ await page.$( '#_wcpay_feature_progressive_onboarding:checked' )
+ ) {
+ await expect( page ).toClick(
+ 'label[for="_wcpay_feature_progressive_onboarding"]'
+ );
+ }
+
+ await expect( page ).toClick( 'input#submit' );
+ await page.waitForNavigation( {
+ waitUntil: 'networkidle0',
+ } );
+ },
+
+ enableActAsDisconnectedFromWCPay: async () => {
+ await page.goto( WCPAY_DEV_TOOLS, {
+ waitUntil: 'networkidle0',
+ } );
+
+ if ( ! ( await page.$( '#wcpaydev_force_disconnected:checked' ) ) ) {
+ await expect( page ).toClick(
+ 'label[for="wcpaydev_force_disconnected"]'
+ );
+ }
+
+ await expect( page ).toClick( 'input#submit' );
+ await page.waitForNavigation( {
+ waitUntil: 'networkidle0',
+ } );
+ },
+
+ disableActAsDisconnectedFromWCPay: async () => {
+ await page.goto( WCPAY_DEV_TOOLS, {
+ waitUntil: 'networkidle0',
+ } );
+
+ if ( await page.$( '#wcpaydev_force_disconnected:checked' ) ) {
+ await expect( page ).toClick(
+ 'label[for="wcpaydev_force_disconnected"]'
+ );
+ }
+ await expect( page ).toClick( 'input#submit' );
+ await page.waitForNavigation( {
+ waitUntil: 'networkidle0',
+ } );
+ },
+
enablePaymentMethod: async ( paymentMethods ) => {
await page.goto( WCPAY_PAYMENT_SETTINGS, {
waitUntil: 'networkidle0',
@@ -607,6 +682,13 @@ export const merchantWCP = {
await uiLoaded();
},
+ openConnectPage: async () => {
+ await page.goto( WCPAY_CONNECT, {
+ waitUntil: 'networkidle0',
+ } );
+ await uiLoaded();
+ },
+
openOrderAnalytics: async () => {
await merchant.openAnalyticsPage( 'orders' );
await uiLoaded();
From a2d745455acbd450f7c5e32719c6de9eb6fa922f Mon Sep 17 00:00:00 2001
From: Eduardo Pieretti Umpierre
Date: Thu, 5 Oct 2023 10:33:56 -0300
Subject: [PATCH 73/98] Fix MultiCurrency onboarding settings page on Woo
Express (#7361)
---
changelog/fix-6466-mc-errors | 4 ++++
includes/multi-currency/SettingsOnboardCta.php | 8 ++++++++
2 files changed, 12 insertions(+)
create mode 100644 changelog/fix-6466-mc-errors
diff --git a/changelog/fix-6466-mc-errors b/changelog/fix-6466-mc-errors
new file mode 100644
index 00000000000..a0f78a106ca
--- /dev/null
+++ b/changelog/fix-6466-mc-errors
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix onboarding section on MultiCurrency settings page.
diff --git a/includes/multi-currency/SettingsOnboardCta.php b/includes/multi-currency/SettingsOnboardCta.php
index 835bcdc7f01..574cd8fdd7e 100644
--- a/includes/multi-currency/SettingsOnboardCta.php
+++ b/includes/multi-currency/SettingsOnboardCta.php
@@ -83,6 +83,10 @@ public function currencies_settings_onboarding_cta() {
* @return array
*/
public function get_settings( $current_section = '' ) {
+ // Hide the save button because there are no settings to save.
+ global $hide_save_button;
+ $hide_save_button = true;
+
return [
[
'title' => __( 'Enabled currencies', 'woocommerce-payments' ),
@@ -97,6 +101,10 @@ public function get_settings( $current_section = '' ) {
[
'type' => 'wcpay_currencies_settings_onboarding_cta',
],
+ [
+ 'type' => 'sectionend',
+ 'id' => $this->id . '_enabled_currencies',
+ ],
];
}
}
From 87b0675a058b7e2e75ef5548f161b333d80e247c Mon Sep 17 00:00:00 2001
From: Zvonimir Maglica
Date: Thu, 5 Oct 2023 15:35:27 +0200
Subject: [PATCH 74/98] Added missing API docs sidebar links (#7389)
---
.../fix-docs-api-add-missing-sidebar-links-for-intent-and-pm | 4 ++++
docs/rest-api/source/includes/wp-api-v3/intent.md | 2 +-
docs/rest-api/source/index.html.md | 2 ++
3 files changed, 7 insertions(+), 1 deletion(-)
create mode 100644 changelog/fix-docs-api-add-missing-sidebar-links-for-intent-and-pm
diff --git a/changelog/fix-docs-api-add-missing-sidebar-links-for-intent-and-pm b/changelog/fix-docs-api-add-missing-sidebar-links-for-intent-and-pm
new file mode 100644
index 00000000000..5147a9663af
--- /dev/null
+++ b/changelog/fix-docs-api-add-missing-sidebar-links-for-intent-and-pm
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+Added missing API docs links for payment intents and payment methods API endpoints
diff --git a/docs/rest-api/source/includes/wp-api-v3/intent.md b/docs/rest-api/source/includes/wp-api-v3/intent.md
index d5d38d50986..4f2daf7984f 100644
--- a/docs/rest-api/source/includes/wp-api-v3/intent.md
+++ b/docs/rest-api/source/includes/wp-api-v3/intent.md
@@ -3,7 +3,7 @@
The Payment Intents API provides comprehensive functionality for managing payment intents. You can create and manage them seamlessly through its endpoints.
-## Create payment intenr
+## Create payment intent
_@since v6.6.0_
diff --git a/docs/rest-api/source/index.html.md b/docs/rest-api/source/index.html.md
index a34dc466d1a..6498e134d05 100644
--- a/docs/rest-api/source/index.html.md
+++ b/docs/rest-api/source/index.html.md
@@ -14,6 +14,8 @@ includes:
- wp-api-v3/authentication
- wp-api-v3/order
- wp-api-v3/authorization
+ - wp-api-v3/customer
+ - wp-api-v3/intent
search: false
---
From 81b6bf9f647a7ad76318a7201cab6968ea260908 Mon Sep 17 00:00:00 2001
From: Eduardo Pieretti Umpierre
Date: Thu, 5 Oct 2023 11:28:33 -0300
Subject: [PATCH 75/98] Load MultiCurrency class on Multi-currency setup page
(#7205)
---
changelog/fix-7151-multi-currency-onboarding | 4 ++
client/data/multi-currency/actions.js | 5 +-
.../tasks/add-currencies-task/index.js | 52 ++++++++++------
.../test/__snapshots__/index.test.js.snap | 3 +-
.../add-currencies-task/test/index.test.js | 6 +-
.../tasks/store-settings-task/index.js | 29 ++++++---
.../store-settings-task/test/index.test.js | 60 ++++++++++++++++++-
includes/class-wc-payments.php | 6 +-
.../wc-payments-multi-currency.php | 19 ++++++
9 files changed, 143 insertions(+), 41 deletions(-)
create mode 100644 changelog/fix-7151-multi-currency-onboarding
diff --git a/changelog/fix-7151-multi-currency-onboarding b/changelog/fix-7151-multi-currency-onboarding
new file mode 100644
index 00000000000..880b2a905a1
--- /dev/null
+++ b/changelog/fix-7151-multi-currency-onboarding
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Load multi-currency class on setup page.
diff --git a/client/data/multi-currency/actions.js b/client/data/multi-currency/actions.js
index 857107ef732..6da75dcef32 100644
--- a/client/data/multi-currency/actions.js
+++ b/client/data/multi-currency/actions.js
@@ -118,7 +118,8 @@ export function* submitCurrencySettings( currencyCode, settings ) {
export function* submitStoreSettingsUpdate(
isAutoSwitchEnabled,
- isStorefrontSwitcherEnabled
+ isStorefrontSwitcherEnabled,
+ suppressNotices = false
) {
try {
const result = yield apiFetch( {
@@ -136,6 +137,8 @@ export function* submitStoreSettingsUpdate(
yield updateStoreSettings( result );
+ if ( suppressNotices ) return;
+
yield dispatch( 'core/notices' ).createSuccessNotice(
__( 'Store settings saved.', 'woocommerce-payments' )
);
diff --git a/client/multi-currency-setup/tasks/add-currencies-task/index.js b/client/multi-currency-setup/tasks/add-currencies-task/index.js
index a57c75fb14d..335b6b78985 100644
--- a/client/multi-currency-setup/tasks/add-currencies-task/index.js
+++ b/client/multi-currency-setup/tasks/add-currencies-task/index.js
@@ -45,12 +45,18 @@ const ContinueButton = ( {
setCompleted,
setSaving,
} ) => {
+ const isDisabled =
+ enabledCurrencyCodes.length <= 1 && selectedCurrencyCodesLength < 1;
+
const handleContinueClick = () => {
- setSaving( true );
- submitEnabledCurrenciesUpdate(
- [ ...enabledCurrencyCodes, ...selectedCurrencyCodes ].sort()
- );
- setSaving( false );
+ if ( selectedCurrencyCodesLength > 0 ) {
+ setSaving( true );
+ submitEnabledCurrenciesUpdate(
+ [ ...enabledCurrencyCodes, ...selectedCurrencyCodes ].sort()
+ );
+ setSaving( false );
+ }
+
setCompleted(
{
initialCurrencies: enabledCurrencyCodes,
@@ -59,24 +65,34 @@ const ContinueButton = ( {
);
};
+ const renderText = () => {
+ if ( selectedCurrencyCodesLength === 0 ) {
+ if ( enabledCurrencyCodes.length > 1 ) {
+ return __( 'Continue', 'woocommerce-payments' );
+ }
+
+ return __( 'Add currencies', 'woocommerce-payments' );
+ }
+
+ return sprintf(
+ _n(
+ 'Add %s currency',
+ 'Add %s currencies',
+ selectedCurrencyCodesLength,
+ 'woocommerce-payments'
+ ),
+ selectedCurrencyCodesLength
+ );
+ };
+
return (
);
};
diff --git a/client/multi-currency-setup/tasks/add-currencies-task/test/__snapshots__/index.test.js.snap b/client/multi-currency-setup/tasks/add-currencies-task/test/__snapshots__/index.test.js.snap
index 1f971098e88..277911ece2e 100644
--- a/client/multi-currency-setup/tasks/add-currencies-task/test/__snapshots__/index.test.js.snap
+++ b/client/multi-currency-setup/tasks/add-currencies-task/test/__snapshots__/index.test.js.snap
@@ -1437,10 +1437,9 @@ exports[`Multi-Currency enabled currencies list should hide recommended currenci
diff --git a/client/multi-currency-setup/tasks/add-currencies-task/test/index.test.js b/client/multi-currency-setup/tasks/add-currencies-task/test/index.test.js
index 094d38fae08..f58beb09ced 100644
--- a/client/multi-currency-setup/tasks/add-currencies-task/test/index.test.js
+++ b/client/multi-currency-setup/tasks/add-currencies-task/test/index.test.js
@@ -420,10 +420,8 @@ describe( 'Multi-Currency enabled currencies list', () => {
).not.toBeInTheDocument();
expect(
- screen.getByRole( 'button', {
- name: /Add ([a-z0-9]+ )?currenc(y|ies)/i,
- } )
- ).toBeDisabled();
+ screen.getByRole( 'button', { name: 'Continue' } )
+ ).not.toBeDisabled();
// Reset mock currencies to original state.
useEnabledCurrencies.mockReturnValue( {
diff --git a/client/multi-currency-setup/tasks/store-settings-task/index.js b/client/multi-currency-setup/tasks/store-settings-task/index.js
index 9d37241733e..7e20fc0f3e4 100644
--- a/client/multi-currency-setup/tasks/store-settings-task/index.js
+++ b/client/multi-currency-setup/tasks/store-settings-task/index.js
@@ -15,10 +15,15 @@ import WizardTaskItem from '../../wizard/task-item';
import PreviewModal from '../../../multi-currency/preview-modal';
import './index.scss';
-import { useStoreSettings } from 'wcpay/data';
+import { useStoreSettings, useSettings, useMultiCurrency } from 'wcpay/data';
const StoreSettingsTask = () => {
const { storeSettings, submitStoreSettingsUpdate } = useStoreSettings();
+ const { saveSettings, isSaving } = useSettings();
+ const [
+ isMultiCurrencyEnabled,
+ updateIsMultiCurrencyEnabled,
+ ] = useMultiCurrency();
const [ isPending, setPending ] = useState( false );
@@ -65,10 +70,18 @@ const StoreSettingsTask = () => {
const handleContinueClick = () => {
setPending( true );
+
+ if ( ! isMultiCurrencyEnabled ) {
+ updateIsMultiCurrencyEnabled( true );
+ saveSettings();
+ }
+
submitStoreSettingsUpdate(
isAutomaticSwitchEnabledValue,
- isStorefrontSwitcherEnabledValue
+ isStorefrontSwitcherEnabledValue,
+ ! isMultiCurrencyEnabled
);
+
setPending( false );
setCompleted( true, 'setup-complete' );
};
@@ -138,19 +151,19 @@ const StoreSettingsTask = () => {
diff --git a/client/multi-currency-setup/tasks/store-settings-task/test/index.test.js b/client/multi-currency-setup/tasks/store-settings-task/test/index.test.js
index 0fd0310fe52..e81b7b08f99 100644
--- a/client/multi-currency-setup/tasks/store-settings-task/test/index.test.js
+++ b/client/multi-currency-setup/tasks/store-settings-task/test/index.test.js
@@ -8,12 +8,19 @@ import { render, screen, fireEvent } from '@testing-library/react';
* Internal dependencies
*/
import WizardTaskContext from '../../../../additional-methods-setup/wizard/task/context';
-import { useCurrencies, useStoreSettings } from 'wcpay/data';
+import {
+ useCurrencies,
+ useStoreSettings,
+ useSettings,
+ useMultiCurrency,
+} from 'wcpay/data';
import StoreSettingsTask from '..';
jest.mock( 'wcpay/data', () => ( {
useStoreSettings: jest.fn(),
useCurrencies: jest.fn(),
+ useSettings: jest.fn(),
+ useMultiCurrency: jest.fn(),
} ) );
const changeableSettings = [
@@ -43,6 +50,11 @@ useStoreSettings.mockReturnValue( {
submitStoreSettingsUpdate: jest.fn(),
} );
+useSettings.mockReturnValue( {
+ saveSettings: jest.fn().mockResolvedValue( {} ),
+ isSaving: false,
+} );
+
const setCompletedMock = jest.fn();
const createContainer = () => {
@@ -61,6 +73,10 @@ describe( 'Multi-Currency store settings', () => {
jest.clearAllMocks();
} );
+ beforeEach( () => {
+ useMultiCurrency.mockReturnValue( [ true, jest.fn() ] );
+ } );
+
test( 'store settings task renders correctly', () => {
const container = createContainer();
expect( container ).toMatchSnapshot(
@@ -83,6 +99,40 @@ describe( 'Multi-Currency store settings', () => {
} );
} );
+ test( 'multi-currency is enabled if it was previously disabled', async () => {
+ useMultiCurrency.mockReturnValue( [ false, jest.fn() ] );
+
+ createContainer();
+ const { submitStoreSettingsUpdate } = useStoreSettings();
+ const { saveSettings } = useSettings();
+ const [ , updateIsMultiCurrencyEnabled ] = useMultiCurrency();
+
+ fireEvent.click(
+ screen.getByRole( 'button', {
+ name: /Continue/,
+ } )
+ );
+
+ expect( saveSettings ).toBeCalled();
+ expect( updateIsMultiCurrencyEnabled ).toBeCalledWith( true );
+ expect( submitStoreSettingsUpdate ).toBeCalledWith(
+ false,
+ false,
+ true
+ );
+
+ changeableSettings.forEach( ( setting ) => {
+ fireEvent.click( screen.getByTestId( setting ) );
+ expect( screen.getByTestId( setting ) ).toBeChecked();
+ } );
+ fireEvent.click(
+ screen.getByRole( 'button', {
+ name: /Continue/,
+ } )
+ );
+ expect( submitStoreSettingsUpdate ).toBeCalledWith( true, true, true );
+ } );
+
test( 'store settings are saved with continue button click', () => {
createContainer();
const { submitStoreSettingsUpdate } = useStoreSettings();
@@ -91,7 +141,11 @@ describe( 'Multi-Currency store settings', () => {
name: /Continue/,
} )
);
- expect( submitStoreSettingsUpdate ).toBeCalledWith( false, false );
+ expect( submitStoreSettingsUpdate ).toBeCalledWith(
+ false,
+ false,
+ false
+ );
changeableSettings.forEach( ( setting ) => {
fireEvent.click( screen.getByTestId( setting ) );
@@ -102,7 +156,7 @@ describe( 'Multi-Currency store settings', () => {
name: /Continue/,
} )
);
- expect( submitStoreSettingsUpdate ).toBeCalledWith( true, true );
+ expect( submitStoreSettingsUpdate ).toBeCalledWith( true, true, false );
} );
test( 'store settings preview should open a modal with an iframe', () => {
diff --git a/includes/class-wc-payments.php b/includes/class-wc-payments.php
index 8d949858b8c..ab8b476503c 100644
--- a/includes/class-wc-payments.php
+++ b/includes/class-wc-payments.php
@@ -461,11 +461,7 @@ public static function init() {
include_once __DIR__ . '/core/service/class-wc-payments-customer-service-api.php';
include_once __DIR__ . '/class-duplicate-payment-prevention-service.php';
include_once __DIR__ . '/class-wc-payments-incentives-service.php';
-
- // Load customer multi-currency if feature is enabled.
- if ( WC_Payments_Features::is_customer_multi_currency_enabled() ) {
- include_once __DIR__ . '/multi-currency/wc-payments-multi-currency.php';
- }
+ include_once __DIR__ . '/multi-currency/wc-payments-multi-currency.php';
self::$woopay_checkout_service = new Checkout_Service();
self::$woopay_checkout_service->init();
diff --git a/includes/multi-currency/wc-payments-multi-currency.php b/includes/multi-currency/wc-payments-multi-currency.php
index 10f5852d086..31017f9ff35 100644
--- a/includes/multi-currency/wc-payments-multi-currency.php
+++ b/includes/multi-currency/wc-payments-multi-currency.php
@@ -7,6 +7,25 @@
defined( 'ABSPATH' ) || exit;
+/**
+ * Load customer multi-currency if feature is enabled or if it is the setup page.
+ */
+function wcpay_multi_currency_onboarding_check() {
+ $is_setup_page = false;
+
+ // Skip checking the HTTP referer if it is a cron job.
+ if ( ! defined( 'DOING_CRON' ) ) {
+ $http_referer = sanitize_text_field( wp_unslash( $_SERVER['HTTP_REFERER'] ?? '' ) );
+ $is_setup_page = 0 < strpos( $http_referer, 'multi-currency-setup' );
+ }
+
+ return $is_setup_page;
+}
+
+if ( ! WC_Payments_Features::is_customer_multi_currency_enabled() && ! wcpay_multi_currency_onboarding_check() ) {
+ return;
+}
+
/**
* Returns the main instance of MultiCurrency.
*
From f5aa7cdb994a36dcce2717ff52481a10f876a37c Mon Sep 17 00:00:00 2001
From: Chris Shultz
Date: Thu, 5 Oct 2023 13:10:33 -0700
Subject: [PATCH 76/98] Add multi-currency enablement check to WooPay session
handler (#7390)
---
.../fix-7329-missing-multi-currency-check | 4 +
includes/woopay/class-woopay-session.php | 2 +-
.../unit/woopay/test-class-woopay-session.php | 81 +++++++++++++++++++
3 files changed, 86 insertions(+), 1 deletion(-)
create mode 100644 changelog/fix-7329-missing-multi-currency-check
diff --git a/changelog/fix-7329-missing-multi-currency-check b/changelog/fix-7329-missing-multi-currency-check
new file mode 100644
index 00000000000..af48c9f30e1
--- /dev/null
+++ b/changelog/fix-7329-missing-multi-currency-check
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Add multi-currency enablement check in WooPay session handling.
diff --git a/includes/woopay/class-woopay-session.php b/includes/woopay/class-woopay-session.php
index f00d21d8cfe..56b5a831636 100644
--- a/includes/woopay/class-woopay-session.php
+++ b/includes/woopay/class-woopay-session.php
@@ -350,7 +350,7 @@ private static function get_init_session_request( $order_id = null, $key = null,
$customer_id = WC_Payments::get_customer_service()->create_customer_for_user( $user, $customer_data );
}
- if ( 0 !== $user->ID ) {
+ if ( WC_Payments_Features::is_customer_multi_currency_enabled() && 0 !== $user->ID ) {
// Multicurrency selection is stored on user meta when logged in and WC session when logged out.
// This code just makes sure that currency selection is available on WC session for WooPay.
$currency = get_user_meta( $user->ID, MultiCurrency::CURRENCY_META_KEY, true );
diff --git a/tests/unit/woopay/test-class-woopay-session.php b/tests/unit/woopay/test-class-woopay-session.php
index 647637bb1bc..f17c094f192 100644
--- a/tests/unit/woopay/test-class-woopay-session.php
+++ b/tests/unit/woopay/test-class-woopay-session.php
@@ -5,10 +5,12 @@
* @package WooCommerce\Payments\Tests
*/
+use PHPUnit\Framework\MockObject\MockObject;
use WCPay\WooPay\WooPay_Session;
use WCPay\Platform_Checkout\WooPay_Store_Api_Token;
use WCPay\Platform_Checkout\SessionHandler;
use WCPay\WooPay\WooPay_Scheduler;
+use WCPay\MultiCurrency\MultiCurrency;
/**
* WooPay_Session unit tests.
@@ -19,6 +21,20 @@ class WooPay_Session_Test extends WCPAY_UnitTestCase {
*/
protected $mock_cache;
+ /**
+ * Mock WC_Payments_Customer_Service.
+ *
+ * @var WC_Payments_Customer_Service|MockObject
+ */
+ private $mock_customer_service;
+
+ /**
+ * WC_Payments_Customer_Service.
+ *
+ * @var WC_Payments_Customer_Service
+ */
+ private $original_customer_service;
+
public function set_up() {
parent::set_up();
@@ -33,6 +49,15 @@ public function set_up() {
$_SERVER['HTTP_USER_AGENT'] = 'WooPay';
$_SERVER['REQUEST_URI'] = '/wp-json/wc/store/v1/checkout';
+
+ $this->mock_customer_service = $this->createMock( WC_Payments_Customer_Service::class );
+ $this->original_customer_service = WC_Payments::get_customer_service();
+ WC_Payments::set_customer_service( $this->mock_customer_service );
+ }
+
+ public function tear_down() {
+ WC_Payments::set_customer_service( $this->original_customer_service );
+ parent::tear_down();
}
public function test_get_user_id_from_cart_token_with_guest_user() {
@@ -188,6 +213,62 @@ public function test_remove_order_customer_id_on_requests_with_verified_email_wi
$this->assertEquals( $updated_order->get_customer_id(), 0 );
}
+ public function test_session_currency_set_for_multi_currency_enabled() {
+ $user_id = 1;
+ $this->mock_customer_service
+ ->expects( $this->once() )
+ ->method( 'get_customer_id_by_user_id' )
+ ->with( $user_id )
+ ->willReturn( $user_id );
+
+ // For multi-currency enabled.
+ update_option( '_wcpay_feature_customer_multi_currency', '1' );
+
+ // Set mismatched user and session currency codes.
+ WC()->session->set( MultiCurrency::CURRENCY_SESSION_KEY, 'ABC' );
+ wp_set_current_user( $user_id );
+ update_user_meta( $user_id, MultiCurrency::CURRENCY_META_KEY, 'DEF' );
+
+ WooPay_Session::get_frontend_init_session_request();
+
+ // Currency in session should have been modified.
+ $this->assertSame(
+ 'DEF',
+ WC()->session->get( MultiCurrency::CURRENCY_SESSION_KEY )
+ );
+
+ // Destroy session data.
+ WC()->session->set( MultiCurrency::CURRENCY_SESSION_KEY, null );
+ }
+
+ public function test_session_currency_not_set_for_multi_currency_disabled() {
+ $user_id = 1;
+ $this->mock_customer_service
+ ->expects( $this->once() )
+ ->method( 'get_customer_id_by_user_id' )
+ ->with( $user_id )
+ ->willReturn( $user_id );
+
+ // For multi-currency disabled.
+ update_option( '_wcpay_feature_customer_multi_currency', '0' );
+
+ // Set mismatched user and session currency codes.
+ WC()->session->set( MultiCurrency::CURRENCY_SESSION_KEY, 'ABC' );
+ wp_set_current_user( $user_id );
+ update_user_meta( $user_id, MultiCurrency::CURRENCY_META_KEY, 'DEF' );
+
+ WooPay_Session::get_frontend_init_session_request();
+
+ // Currency in session should NOT have been modified.
+ $this->assertSame(
+ 'ABC',
+ WC()->session->get( MultiCurrency::CURRENCY_SESSION_KEY )
+ );
+
+ // Destroy session data.
+ WC()->session->set( MultiCurrency::CURRENCY_SESSION_KEY, null );
+ }
+
private function setup_session( $customer_id, $customer_email = null ) {
$session_handler = new SessionHandler();
From 3dc462782ed23c96653181f400c626c7bfaa2c81 Mon Sep 17 00:00:00 2001
From: Eric Jinks <3147296+Jinksi@users.noreply.github.com>
Date: Fri, 6 Oct 2023 07:53:26 +1000
Subject: [PATCH 77/98] =?UTF-8?q?Transaction=20Details=20=E2=86=92=20show?=
=?UTF-8?q?=20dispute=20amount=20in=20shopper=20currency=20(#7379)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Shendy <73803630+shendy-a8c@users.noreply.github.com>
---
...ix-7373-show-dispute-amount-in-shopper-currency | 5 +++++
...124-transaction-details-dispute-amount-currency | 5 -----
client/disputes/utils.ts | 2 +-
.../dispute-details/dispute-summary-row.tsx | 14 +-------------
client/payment-details/summary/test/index.test.tsx | 6 +++---
5 files changed, 10 insertions(+), 22 deletions(-)
create mode 100644 changelog/fix-7373-show-dispute-amount-in-shopper-currency
delete mode 100644 changelog/update-7124-transaction-details-dispute-amount-currency
diff --git a/changelog/fix-7373-show-dispute-amount-in-shopper-currency b/changelog/fix-7373-show-dispute-amount-in-shopper-currency
new file mode 100644
index 00000000000..724d7a5fdec
--- /dev/null
+++ b/changelog/fix-7373-show-dispute-amount-in-shopper-currency
@@ -0,0 +1,5 @@
+Significance: patch
+Type: fix
+Comment: Revert pre-release change to show dispute amount in the shopper's currency on the transaction details screen
+
+
diff --git a/changelog/update-7124-transaction-details-dispute-amount-currency b/changelog/update-7124-transaction-details-dispute-amount-currency
deleted file mode 100644
index 0f0d3ded1f7..00000000000
--- a/changelog/update-7124-transaction-details-dispute-amount-currency
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: update
-Comment: Behind feature flag: show transaction details dispute amount in store's currency
-
-
diff --git a/client/disputes/utils.ts b/client/disputes/utils.ts
index b648efc5632..6e5acf2f437 100644
--- a/client/disputes/utils.ts
+++ b/client/disputes/utils.ts
@@ -76,7 +76,7 @@ export const isInquiry = ( dispute: Pick< Dispute, 'status' > ): boolean => {
* Returns the dispute fee balance transaction for a dispute if it exists
* and the deduction has not been reversed.
*/
-export const getDisputeDeductedBalanceTransaction = (
+const getDisputeDeductedBalanceTransaction = (
dispute: Pick< Dispute, 'balance_transactions' >
): BalanceTransaction | undefined => {
// Note that there can only be, at most, two balance transactions for a given dispute:
diff --git a/client/payment-details/dispute-details/dispute-summary-row.tsx b/client/payment-details/dispute-details/dispute-summary-row.tsx
index dcd2bca4235..ac6dada265e 100644
--- a/client/payment-details/dispute-details/dispute-summary-row.tsx
+++ b/client/payment-details/dispute-details/dispute-summary-row.tsx
@@ -19,7 +19,6 @@ import { reasons } from 'wcpay/disputes/strings';
import { formatStringValue } from 'wcpay/utils';
import { ClickTooltip } from 'wcpay/components/tooltip';
import Paragraphs from 'wcpay/components/paragraphs';
-import { getDisputeDeductedBalanceTransaction } from 'wcpay/disputes/utils';
import DisputeDueByDate from './dispute-due-by-date';
interface Props {
@@ -31,22 +30,11 @@ const DisputeSummaryRow: React.FC< Props > = ( { dispute } ) => {
reasons[ dispute.reason ]?.display || dispute.reason
);
const disputeReasonSummary = reasons[ dispute.reason ]?.summary || [];
- const disputeBalanceTransaction = getDisputeDeductedBalanceTransaction(
- dispute
- );
- // If there is a dispute deduction balance transaction, show the dispute amount in the store's currency.
- // Otherwise (if the dispute is an inquiry) use the dispute/charge amount and currency.
- const disputeAmountFormatted = disputeBalanceTransaction
- ? formatExplicitCurrency(
- Math.abs( disputeBalanceTransaction.amount ),
- disputeBalanceTransaction.currency
- )
- : formatExplicitCurrency( dispute.amount, dispute.currency );
const columns = [
{
title: __( 'Dispute Amount', 'woocommerce-payments' ),
- content: disputeAmountFormatted,
+ content: formatExplicitCurrency( dispute.amount, dispute.currency ),
},
{
title: __( 'Disputed On', 'woocommerce-payments' ),
diff --git a/client/payment-details/summary/test/index.test.tsx b/client/payment-details/summary/test/index.test.tsx
index f67b44bf5de..41db8d3dcbf 100755
--- a/client/payment-details/summary/test/index.test.tsx
+++ b/client/payment-details/summary/test/index.test.tsx
@@ -456,10 +456,10 @@ describe( 'PaymentDetailsSummary', () => {
];
renderCharge( charge );
- // Disputed amount should show the store (balance transaction) currency.
+ // Disputed amount should show the shopper/transaction currency.
expect(
screen.getByText( /Dispute Amount/i ).nextSibling
- ).toHaveTextContent( /kr 725.81 NOK/i );
+ ).toHaveTextContent( /¥10,000 JPY/i );
} );
test( 'renders the information of a dispute-reversal charge', () => {
@@ -567,7 +567,7 @@ describe( 'PaymentDetailsSummary', () => {
charge.dispute.balance_transactions = [];
renderCharge( charge );
- // Disputed amount should show the dispute/charge currency.
+ // Disputed amount should show the shopper/transaction currency.
expect(
screen.getByText( /Dispute Amount/i ).nextSibling
).toHaveTextContent( /¥10,000 JPY/i );
From 5603143418224a36430871fa2566082fa01a45f0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?C=C3=A9sar=20Costa?=
<10233985+cesarcosta99@users.noreply.github.com>
Date: Thu, 5 Oct 2023 18:07:56 -0400
Subject: [PATCH 78/98] Only request WooPay session once on Blocks pages
(#7393)
---
.../fix-7357-multiple-woopay-session-requests | 4 +++
.../woopay-express-checkout-payment-method.js | 32 +++++++++++++++----
2 files changed, 29 insertions(+), 7 deletions(-)
create mode 100644 changelog/fix-7357-multiple-woopay-session-requests
diff --git a/changelog/fix-7357-multiple-woopay-session-requests b/changelog/fix-7357-multiple-woopay-session-requests
new file mode 100644
index 00000000000..2c2b2d69641
--- /dev/null
+++ b/changelog/fix-7357-multiple-woopay-session-requests
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Only request WooPay session data once on blocks pages.
diff --git a/client/checkout/woopay/express-button/woopay-express-checkout-payment-method.js b/client/checkout/woopay/express-button/woopay-express-checkout-payment-method.js
index 94637e94988..228032bbf6d 100644
--- a/client/checkout/woopay/express-button/woopay-express-checkout-payment-method.js
+++ b/client/checkout/woopay/express-button/woopay-express-checkout-payment-method.js
@@ -1,3 +1,9 @@
+/**
+ * External dependencies
+ */
+import { useCallback } from 'react';
+import ReactDOM from 'react-dom';
+
/**
* Internal dependencies
*/
@@ -18,15 +24,27 @@ const api = new WCPayAPI(
request
);
+const WooPayExpressCheckoutButtonContainer = () => {
+ const onRefChange = useCallback( ( node ) => {
+ if ( node ) {
+ const root = ReactDOM.createRoot( node );
+
+ root.render(
+
+ );
+ }
+ }, [] );
+
+ return ;
+};
+
const wooPayExpressCheckoutPaymentMethod = () => ( {
name: PAYMENT_METHOD_NAME_WOOPAY_EXPRESS_CHECKOUT,
- content: (
-
- ),
+ content: ,
edit: (
Date: Fri, 6 Oct 2023 12:36:52 +1300
Subject: [PATCH 79/98] Disputes: wording for disputes implying that fee has
not yet been deducted (#7374)
Co-authored-by: Eric Jinks <3147296+Jinksi@users.noreply.github.com>
Co-authored-by: Shendy <73803630+shendy-a8c@users.noreply.github.com>
---
changelog/fix-7254-update-dispute-fee-text | 5 +++++
.../dispute-details/dispute-awaiting-response-details.tsx | 2 +-
client/payment-details/dispute-details/dispute-steps.tsx | 2 +-
3 files changed, 7 insertions(+), 2 deletions(-)
create mode 100644 changelog/fix-7254-update-dispute-fee-text
diff --git a/changelog/fix-7254-update-dispute-fee-text b/changelog/fix-7254-update-dispute-fee-text
new file mode 100644
index 00000000000..d0bf68420b2
--- /dev/null
+++ b/changelog/fix-7254-update-dispute-fee-text
@@ -0,0 +1,5 @@
+Significance: patch
+Type: fix
+Comment: Minor wording change on unreleased feature.
+
+
diff --git a/client/payment-details/dispute-details/dispute-awaiting-response-details.tsx b/client/payment-details/dispute-details/dispute-awaiting-response-details.tsx
index b7efafd7e51..0dc928dfe14 100644
--- a/client/payment-details/dispute-details/dispute-awaiting-response-details.tsx
+++ b/client/payment-details/dispute-details/dispute-awaiting-response-details.tsx
@@ -127,7 +127,7 @@ function getAcceptDisputeProps( dispute: Dispute ): AcceptDisputeProps {
sprintf(
/* translators: %s: dispute fee, : emphasis HTML element. */
__(
- 'Accepting the dispute marks it as Lost. The disputed amount will be returned to the cardholder, with a %s dispute fee deducted from your account.',
+ 'Accepting the dispute marks it as Lost. The disputed amount and the %s dispute fee will not be returned to you.',
'woocommerce-payments'
),
getDisputeFeeFormatted( dispute, true ) ?? '-'
diff --git a/client/payment-details/dispute-details/dispute-steps.tsx b/client/payment-details/dispute-details/dispute-steps.tsx
index e3695c381d3..a074b95ce6d 100644
--- a/client/payment-details/dispute-details/dispute-steps.tsx
+++ b/client/payment-details/dispute-details/dispute-steps.tsx
@@ -142,7 +142,7 @@ export const DisputeSteps: React.FC< Props > = ( {
content={ sprintf(
// Translators: %s is a formatted currency amount, eg $10.00.
__(
- `Accepting this dispute will automatically close it. Your account will be charged a %s fee, and the disputed amount will be refunded to the cardholder.`,
+ `Accepting this dispute will automatically close it. The disputed amount and the %s dispute fee will not be returned to you.`,
'woocommerce-payments'
),
getDisputeFeeFormatted(
From 1fbcd97322b3962637ece826abdb63929a98d591 Mon Sep 17 00:00:00 2001
From: bruce aldridge
Date: Fri, 6 Oct 2023 13:13:06 +1300
Subject: [PATCH 80/98] Disputes: Prevent errors crashing the payment details
page (#7378)
---
changelog/add-7371-dispute-details-error-boundary | 5 +++++
client/payment-details/summary/index.tsx | 5 +++--
2 files changed, 8 insertions(+), 2 deletions(-)
create mode 100644 changelog/add-7371-dispute-details-error-boundary
diff --git a/changelog/add-7371-dispute-details-error-boundary b/changelog/add-7371-dispute-details-error-boundary
new file mode 100644
index 00000000000..6072efc6e45
--- /dev/null
+++ b/changelog/add-7371-dispute-details-error-boundary
@@ -0,0 +1,5 @@
+Significance: patch
+Type: add
+Comment: Part of a larger feature with separate changelog entry
+
+
diff --git a/client/payment-details/summary/index.tsx b/client/payment-details/summary/index.tsx
index cc3866dd085..8b1fc3b68f4 100644
--- a/client/payment-details/summary/index.tsx
+++ b/client/payment-details/summary/index.tsx
@@ -51,6 +51,7 @@ import CancelAuthorizationButton from '../../components/cancel-authorization-but
import { PaymentIntent } from '../../types/payment-intents';
import DisputeAwaitingResponseDetails from '../dispute-details/dispute-awaiting-response-details';
import DisputeResolutionFooter from '../dispute-details/dispute-resolution-footer';
+import ErrorBoundary from 'components/error-boundary';
declare const window: any;
@@ -461,7 +462,7 @@ const PaymentDetailsSummary: React.FC< PaymentDetailsSummaryProps > = ( {
{ charge.dispute && (
- <>
+
{ isAwaitingResponse( charge.dispute.status ) ? (
= ( {
) : (
) }
- >
+
) }
{ isAuthAndCaptureEnabled &&
From 671b39b11b35c975b9ba8e7c30b0b78914ca4c44 Mon Sep 17 00:00:00 2001
From: Shendy <73803630+shendy-a8c@users.noreply.github.com>
Date: Fri, 6 Oct 2023 08:32:36 +0700
Subject: [PATCH 81/98] Gate dispute issuer evidence behind a feature flag.
(#7394)
Co-authored-by: Eric Jinks <3147296+Jinksi@users.noreply.github.com>
---
changelog/update-7372-issuer-evidence | 5 +++
client/globals.d.ts | 1 +
.../dispute-awaiting-response-details.tsx | 15 +++++---
.../dispute-details/evidence-list.tsx | 34 +++++++++---------
client/settings/wcpay-settings-context.js | 1 +
client/types/disputes.d.ts | 2 +-
includes/class-wc-payments-features.php | 35 ++++++++++++-------
7 files changed, 59 insertions(+), 34 deletions(-)
create mode 100644 changelog/update-7372-issuer-evidence
diff --git a/changelog/update-7372-issuer-evidence b/changelog/update-7372-issuer-evidence
new file mode 100644
index 00000000000..d79fece68a4
--- /dev/null
+++ b/changelog/update-7372-issuer-evidence
@@ -0,0 +1,5 @@
+Significance: patch
+Type: update
+Comment: Just putting a feature (dispute issuer evidence) that was never public to real users behind a feature flag.
+
+
diff --git a/client/globals.d.ts b/client/globals.d.ts
index 54d56edfc7d..7132f49949f 100644
--- a/client/globals.d.ts
+++ b/client/globals.d.ts
@@ -15,6 +15,7 @@ declare global {
customSearch: boolean;
isAuthAndCaptureEnabled: boolean;
paymentTimeline: boolean;
+ isDisputeIssuerEvidenceEnabled: boolean;
};
fraudServices: unknown[];
testMode: boolean;
diff --git a/client/payment-details/dispute-details/dispute-awaiting-response-details.tsx b/client/payment-details/dispute-details/dispute-awaiting-response-details.tsx
index 0dc928dfe14..c4877d7030b 100644
--- a/client/payment-details/dispute-details/dispute-awaiting-response-details.tsx
+++ b/client/payment-details/dispute-details/dispute-awaiting-response-details.tsx
@@ -3,7 +3,7 @@
/**
* External dependencies
*/
-import React, { useState } from 'react';
+import React, { useState, useContext } from 'react';
import moment from 'moment';
import { __, sprintf } from '@wordpress/i18n';
import { backup, edit, lock, arrowRight } from '@wordpress/icons';
@@ -34,6 +34,7 @@ import IssuerEvidenceList from './evidence-list';
import DisputeSummaryRow from './dispute-summary-row';
import { DisputeSteps, InquirySteps } from './dispute-steps';
import InlineNotice from 'components/inline-notice';
+import WCPaySettingsContext from 'wcpay/settings/wcpay-settings-context';
import './style.scss';
interface Props {
@@ -165,6 +166,10 @@ const DisputeAwaitingResponseDetails: React.FC< Props > = ( {
const hasStagedEvidence = dispute.evidence_details?.has_evidence;
const { createErrorNotice } = useDispatch( 'core/notices' );
+ const {
+ featureFlags: { isDisputeIssuerEvidenceEnabled },
+ } = useContext( WCPaySettingsContext );
+
const onModalClose = () => {
setModalOpen( false );
};
@@ -222,9 +227,11 @@ const DisputeAwaitingResponseDetails: React.FC< Props > = ( {
/>
) }
-
+ { isDisputeIssuerEvidenceEnabled && (
+
+ ) }
{ /* Dispute Actions */ }
{
diff --git a/client/payment-details/dispute-details/evidence-list.tsx b/client/payment-details/dispute-details/evidence-list.tsx
index e6610b74206..687bb3630ca 100644
--- a/client/payment-details/dispute-details/evidence-list.tsx
+++ b/client/payment-details/dispute-details/evidence-list.tsx
@@ -103,14 +103,15 @@ const FileEvidence: React.FC< {
};
interface Props {
- issuerEvidence: IssuerEvidence | null;
+ issuerEvidence: IssuerEvidence[] | null;
}
const IssuerEvidenceList: React.FC< Props > = ( { issuerEvidence } ) => {
if (
- ! issuerEvidence ||
- ! issuerEvidence.file_evidence.length ||
- ! issuerEvidence.text_evidence
+ ! issuerEvidence?.some(
+ ( evidence ) =>
+ evidence.file_evidence.length || evidence.text_evidence
+ )
) {
return <>>;
}
@@ -122,20 +123,19 @@ const IssuerEvidenceList: React.FC< Props > = ( { issuerEvidence } ) => {
initialOpen={ false }
>
- { issuerEvidence.text_evidence && (
- -
-
+ { issuerEvidence.map( ( evidence, i ) => (
+
-
+ { evidence.text_evidence && (
+
+ ) }
+ { evidence.file_evidence.map( ( fileId ) => (
+
+ ) ) }
- ) }
- { issuerEvidence.file_evidence.map(
- ( fileId: string, i: any ) => (
- -
-
-
- )
- ) }
+ ) ) }
);
diff --git a/client/settings/wcpay-settings-context.js b/client/settings/wcpay-settings-context.js
index 456ce5cc6c5..531e806ff27 100644
--- a/client/settings/wcpay-settings-context.js
+++ b/client/settings/wcpay-settings-context.js
@@ -9,6 +9,7 @@ const WCPaySettingsContext = createContext( {
accountStatus: {},
featureFlags: {
isAuthAndCaptureEnabled: false,
+ isDisputeIssuerEvidenceEnabled: false,
woopay: false,
},
} );
diff --git a/client/types/disputes.d.ts b/client/types/disputes.d.ts
index 8c42cf7315f..0db395b9cc8 100644
--- a/client/types/disputes.d.ts
+++ b/client/types/disputes.d.ts
@@ -95,7 +95,7 @@ export interface Dispute {
};
order: null | OrderDetails;
evidence: Evidence;
- issuer_evidence: IssuerEvidence | null;
+ issuer_evidence: IssuerEvidence[] | null;
fileSize?: Record< string, number >;
reason: DisputeReason;
charge: Charge | string;
diff --git a/includes/class-wc-payments-features.php b/includes/class-wc-payments-features.php
index a5c7136a204..7f6cc7c9b92 100644
--- a/includes/class-wc-payments-features.php
+++ b/includes/class-wc-payments-features.php
@@ -24,6 +24,7 @@ class WC_Payments_Features {
const PROGRESSIVE_ONBOARDING_FLAG_NAME = '_wcpay_feature_progressive_onboarding';
const PAY_FOR_ORDER_FLOW = '_wcpay_feature_pay_for_order_flow';
const DEFERRED_UPE_SERVER_FLAG_NAME = 'is_deferred_intent_creation_upe_enabled';
+ const DISPUTE_ISSUER_EVIDENCE = '_wcpay_feature_dispute_issuer_evidence';
/**
* Checks whether any UPE gateway is enabled.
@@ -422,6 +423,15 @@ public static function is_pay_for_order_flow_enabled() {
return '1' === get_option( self::PAY_FOR_ORDER_FLOW, '0' );
}
+ /**
+ * Checks whether Dispute issuer evidence feature should be enabled. Disabled by default.
+ *
+ * @return bool
+ */
+ public static function is_dispute_issuer_evidence_enabled(): bool {
+ return '1' === get_option( self::DISPUTE_ISSUER_EVIDENCE, '0' );
+ }
+
/**
* Returns feature flags as an array suitable for display on the front-end.
*
@@ -430,18 +440,19 @@ public static function is_pay_for_order_flow_enabled() {
public static function to_array() {
return array_filter(
[
- 'upe' => self::is_upe_enabled(),
- 'upeSplit' => self::is_upe_split_enabled(),
- 'upeDeferred' => self::is_upe_deferred_intent_enabled(),
- 'upeSettingsPreview' => self::is_upe_settings_preview_enabled(),
- 'multiCurrency' => self::is_customer_multi_currency_enabled(),
- 'woopay' => self::is_woopay_eligible(),
- 'documents' => self::is_documents_section_enabled(),
- 'clientSecretEncryption' => self::is_client_secret_encryption_enabled(),
- 'woopayExpressCheckout' => self::is_woopay_express_checkout_enabled(),
- 'isAuthAndCaptureEnabled' => self::is_auth_and_capture_enabled(),
- 'progressiveOnboarding' => self::is_progressive_onboarding_enabled(),
- 'isPayForOrderFlowEnabled' => self::is_pay_for_order_flow_enabled(),
+ 'upe' => self::is_upe_enabled(),
+ 'upeSplit' => self::is_upe_split_enabled(),
+ 'upeDeferred' => self::is_upe_deferred_intent_enabled(),
+ 'upeSettingsPreview' => self::is_upe_settings_preview_enabled(),
+ 'multiCurrency' => self::is_customer_multi_currency_enabled(),
+ 'woopay' => self::is_woopay_eligible(),
+ 'documents' => self::is_documents_section_enabled(),
+ 'clientSecretEncryption' => self::is_client_secret_encryption_enabled(),
+ 'woopayExpressCheckout' => self::is_woopay_express_checkout_enabled(),
+ 'isAuthAndCaptureEnabled' => self::is_auth_and_capture_enabled(),
+ 'progressiveOnboarding' => self::is_progressive_onboarding_enabled(),
+ 'isPayForOrderFlowEnabled' => self::is_pay_for_order_flow_enabled(),
+ 'isDisputeIssuerEvidenceEnabled' => self::is_dispute_issuer_evidence_enabled(),
]
);
}
From 59e8b2fbeb90676deffbe804d5ec66a56485dadc Mon Sep 17 00:00:00 2001
From: Eric Jinks <3147296+Jinksi@users.noreply.github.com>
Date: Fri, 6 Oct 2023 12:25:44 +1000
Subject: [PATCH 82/98] =?UTF-8?q?Change=20`Dispute=20=E2=86=92=20Details`?=
=?UTF-8?q?=20links=20to=20`Transaction=20=E2=86=92=20Details`=20(#7087)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Rua Haszard
Co-authored-by: Shendy <73803630+shendy-a8c@users.noreply.github.com>
---
...pute-details-links-to-transactions-details | 4 ++
client/disputes/index.tsx | 17 +++++--
.../test/__snapshots__/index.tsx.snap | 46 +++++++++---------
client/order/index.js | 9 ++--
.../overview/task-list/tasks/dispute-task.tsx | 8 ++--
.../tasks/class-wc-payments-task-disputes.php | 4 +-
includes/class-wc-payments-order-service.php | 36 +++++++-------
...wc-payments-webhook-processing-service.php | 13 ++---
.../test-class-wc-payments-order-service.php | 48 +++++++++----------
9 files changed, 98 insertions(+), 87 deletions(-)
create mode 100644 changelog/fix-6929-change-dispute-details-links-to-transactions-details
diff --git a/changelog/fix-6929-change-dispute-details-links-to-transactions-details b/changelog/fix-6929-change-dispute-details-links-to-transactions-details
new file mode 100644
index 00000000000..b080d0edf21
--- /dev/null
+++ b/changelog/fix-6929-change-dispute-details-links-to-transactions-details
@@ -0,0 +1,4 @@
+Significance: minor
+Type: update
+
+Update links that pointed to the dispute details screen to point to the transaction details screen
diff --git a/client/disputes/index.tsx b/client/disputes/index.tsx
index 3ba2782f82f..90880e592d2 100644
--- a/client/disputes/index.tsx
+++ b/client/disputes/index.tsx
@@ -207,14 +207,17 @@ export const DisputesList = (): JSX.Element => {
const rows = disputes.map( ( dispute ) => {
const clickable = ( children: React.ReactNode ): JSX.Element => (
{ children }
);
const detailsLink = (
-
+
);
const reasonMapping = reasons[ dispute.reason ];
@@ -303,7 +306,10 @@ export const DisputesList = (): JSX.Element => {
display: (
From 34a37c2e7f306d6dc2e9a3a838f43faf75437c76 Mon Sep 17 00:00:00 2001
From: Matt Allan
Date: Fri, 6 Oct 2023 15:10:03 +1000
Subject: [PATCH 86/98] Update Subscriptions Core to 6.3.0 (#7403)
---
changelog/subscriptions-core-6.3.0 | 4 ++++
changelog/subscriptions-core-6.3.0-1 | 4 ++++
changelog/subscriptions-core-6.3.0-2 | 4 ++++
changelog/subscriptions-core-6.3.0-3 | 4 ++++
changelog/subscriptions-core-6.3.0-4 | 4 ++++
changelog/subscriptions-core-6.3.0-5 | 4 ++++
changelog/subscriptions-core-6.3.0-6 | 4 ++++
changelog/subscriptions-core-6.3.0-7 | 4 ++++
changelog/subscriptions-core-6.3.0-8 | 4 ++++
composer.json | 2 +-
composer.lock | 14 +++++++-------
11 files changed, 44 insertions(+), 8 deletions(-)
create mode 100644 changelog/subscriptions-core-6.3.0
create mode 100644 changelog/subscriptions-core-6.3.0-1
create mode 100644 changelog/subscriptions-core-6.3.0-2
create mode 100644 changelog/subscriptions-core-6.3.0-3
create mode 100644 changelog/subscriptions-core-6.3.0-4
create mode 100644 changelog/subscriptions-core-6.3.0-5
create mode 100644 changelog/subscriptions-core-6.3.0-6
create mode 100644 changelog/subscriptions-core-6.3.0-7
create mode 100644 changelog/subscriptions-core-6.3.0-8
diff --git a/changelog/subscriptions-core-6.3.0 b/changelog/subscriptions-core-6.3.0
new file mode 100644
index 00000000000..5cf498d1688
--- /dev/null
+++ b/changelog/subscriptions-core-6.3.0
@@ -0,0 +1,4 @@
+Significance: minor
+Type: dev
+
+Update subscriptions-core to 6.3.0.
diff --git a/changelog/subscriptions-core-6.3.0-1 b/changelog/subscriptions-core-6.3.0-1
new file mode 100644
index 00000000000..0c42e39eb5b
--- /dev/null
+++ b/changelog/subscriptions-core-6.3.0-1
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Introduce the "Subscription Relationship" column under the Orders list admin page when HPOS is enabled.
diff --git a/changelog/subscriptions-core-6.3.0-2 b/changelog/subscriptions-core-6.3.0-2
new file mode 100644
index 00000000000..fbc58e2599a
--- /dev/null
+++ b/changelog/subscriptions-core-6.3.0-2
@@ -0,0 +1,4 @@
+Significance: minor
+Type: fix
+
+When HPOS is enabled, make the orders_by_type_query filter box work in the WooCommerce orders screen.
diff --git a/changelog/subscriptions-core-6.3.0-3 b/changelog/subscriptions-core-6.3.0-3
new file mode 100644
index 00000000000..7417f089602
--- /dev/null
+++ b/changelog/subscriptions-core-6.3.0-3
@@ -0,0 +1,4 @@
+Significance: minor
+Type: fix
+
+Ensure renewal orders paid via the Block Checkout are correctly linked to their subscription.
diff --git a/changelog/subscriptions-core-6.3.0-4 b/changelog/subscriptions-core-6.3.0-4
new file mode 100644
index 00000000000..329c050a701
--- /dev/null
+++ b/changelog/subscriptions-core-6.3.0-4
@@ -0,0 +1,4 @@
+Significance: minor
+Type: fix
+
+Resolved an issue that caused paying for failed/pending parent orders that include Product Add-ons to not calculate the correct total.
diff --git a/changelog/subscriptions-core-6.3.0-5 b/changelog/subscriptions-core-6.3.0-5
new file mode 100644
index 00000000000..91d0638712f
--- /dev/null
+++ b/changelog/subscriptions-core-6.3.0-5
@@ -0,0 +1,4 @@
+Significance: minor
+Type: fix
+
+Ensure the order needs processing transient is deleted when a subscription order (eg renewal) is created. Fixes issues with renewal orders going straight to a completed status.
diff --git a/changelog/subscriptions-core-6.3.0-6 b/changelog/subscriptions-core-6.3.0-6
new file mode 100644
index 00000000000..852f5ffcb84
--- /dev/null
+++ b/changelog/subscriptions-core-6.3.0-6
@@ -0,0 +1,4 @@
+Significance: minor
+Type: fix
+
+Store the correct subscription start date in postmeta and ordermeta when HPOS and data syncing is being used.
diff --git a/changelog/subscriptions-core-6.3.0-7 b/changelog/subscriptions-core-6.3.0-7
new file mode 100644
index 00000000000..d67e3e1922f
--- /dev/null
+++ b/changelog/subscriptions-core-6.3.0-7
@@ -0,0 +1,4 @@
+Significance: minor
+Type: fix
+
+When HPOS is enabled, deleting a customer will now delete their subscriptions.
diff --git a/changelog/subscriptions-core-6.3.0-8 b/changelog/subscriptions-core-6.3.0-8
new file mode 100644
index 00000000000..2fe3b3489cc
--- /dev/null
+++ b/changelog/subscriptions-core-6.3.0-8
@@ -0,0 +1,4 @@
+Significance: minor
+Type: fix
+
+Missing styles on the Edit Subscription page when HPOS is enabled.
diff --git a/composer.json b/composer.json
index 668bf0f26a7..999b2cab67d 100644
--- a/composer.json
+++ b/composer.json
@@ -27,7 +27,7 @@
"automattic/jetpack-autoloader": "2.11.18",
"automattic/jetpack-identity-crisis": "0.8.43",
"automattic/jetpack-sync": "1.47.7",
- "woocommerce/subscriptions-core": "6.2.0",
+ "woocommerce/subscriptions-core": "6.3.0",
"psr/container": "^1.1"
},
"require-dev": {
diff --git a/composer.lock b/composer.lock
index a5963e45fdb..67b78b093c5 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "86b5f217949dc6931b79653be4a6dca8",
+ "content-hash": "f45536a544c784b5c85d2e507cb43d42",
"packages": [
{
"name": "automattic/jetpack-a8c-mc-stats",
@@ -988,16 +988,16 @@
},
{
"name": "woocommerce/subscriptions-core",
- "version": "6.2.0",
+ "version": "6.3.0",
"source": {
"type": "git",
"url": "https://github.com/Automattic/woocommerce-subscriptions-core.git",
- "reference": "47cfe92d60239d1b8b12a5f640a3772b0e4e1272"
+ "reference": "8ba0249f97df46caafd625950853f8f5e5a29984"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Automattic/woocommerce-subscriptions-core/zipball/47cfe92d60239d1b8b12a5f640a3772b0e4e1272",
- "reference": "47cfe92d60239d1b8b12a5f640a3772b0e4e1272",
+ "url": "https://api.github.com/repos/Automattic/woocommerce-subscriptions-core/zipball/8ba0249f97df46caafd625950853f8f5e5a29984",
+ "reference": "8ba0249f97df46caafd625950853f8f5e5a29984",
"shasum": ""
},
"require": {
@@ -1038,10 +1038,10 @@
"description": "Sell products and services with recurring payments in your WooCommerce Store.",
"homepage": "https://github.com/Automattic/woocommerce-subscriptions-core",
"support": {
- "source": "https://github.com/Automattic/woocommerce-subscriptions-core/tree/6.2.0",
+ "source": "https://github.com/Automattic/woocommerce-subscriptions-core/tree/6.3.0",
"issues": "https://github.com/Automattic/woocommerce-subscriptions-core/issues"
},
- "time": "2023-08-10T23:43:48+00:00"
+ "time": "2023-10-06T04:31:08+00:00"
}
],
"packages-dev": [
From 467b5ea07f49e319d363313f7cde354de182e543 Mon Sep 17 00:00:00 2001
From: Chris Shultz
Date: Thu, 5 Oct 2023 23:45:15 -0700
Subject: [PATCH 87/98] Bump minimum PHP version to 7.4 for WooCommerce beta
matrix only. (#7391)
Co-authored-by: deepakpathania <68396823+deepakpathania@users.noreply.github.com>
---
.github/workflows/compatibility.yml | 2 +-
changelog/fix-bump-min-php-wc-beta | 3 +++
2 files changed, 4 insertions(+), 1 deletion(-)
create mode 100644 changelog/fix-bump-min-php-wc-beta
diff --git a/.github/workflows/compatibility.yml b/.github/workflows/compatibility.yml
index 12439ae65b1..28aeab14f4b 100644
--- a/.github/workflows/compatibility.yml
+++ b/.github/workflows/compatibility.yml
@@ -64,7 +64,7 @@ jobs:
- name: "Generate matrix"
id: generate_matrix
run: |
- PHP_VERSIONS=$( echo "[\"$PHP_MIN_SUPPORTED_VERSION\", \"8.0\", \"8.1\"]" )
+ PHP_VERSIONS=$( echo "[\"7.4\", \"8.0\", \"8.1\"]" )
echo "matrix={\"woocommerce\":[\"beta\"],\"wordpress\":[\"latest\"],\"gutenberg\":[\"latest\"],\"php\":$PHP_VERSIONS}" >> $GITHUB_OUTPUT
# a dedicated job, as allowed to fail
diff --git a/changelog/fix-bump-min-php-wc-beta b/changelog/fix-bump-min-php-wc-beta
new file mode 100644
index 00000000000..2e96582ebd7
--- /dev/null
+++ b/changelog/fix-bump-min-php-wc-beta
@@ -0,0 +1,3 @@
+Significance: patch
+Type: dev
+Comment: Update automated test matrix to remove PHP 7.3 testing against WooCommerce beta.
From c95ef55bd15c7c723a23780183921d7bf423a693 Mon Sep 17 00:00:00 2001
From: Radoslav Georgiev
Date: Fri, 6 Oct 2023 13:41:40 +0300
Subject: [PATCH 88/98] RPP: Minimal Checkout (#7332)
---
changelog/rpp-basic-process | 5 +
includes/class-wc-payment-gateway-wcpay.php | 4 +-
.../class-wc-payments-customer-service.php | 23 ++
.../GenericServiceProvider.php | 1 +
.../PaymentsServiceProvider.php | 21 +-
src/Internal/Payment/PaymentContext.php | 144 +++++++++
src/Internal/Payment/PaymentRequest.php | 41 ++-
.../Payment/State/AbstractPaymentState.php | 13 +-
src/Internal/Payment/State/InitialState.php | 130 +++++++-
.../Payment/State/PaymentErrorState.php | 18 ++
.../Payment/State/SystemErrorState.php | 18 ++
src/Internal/Service/OrderService.php | 128 +++++++-
.../Service/PaymentProcessingService.php | 36 +--
.../Service/PaymentRequestService.php | 48 +++
.../Internal/Payment/PaymentContextTest.php | 64 +++-
.../Internal/Payment/PaymentRequestTest.php | 76 ++++-
.../State/AbstractPaymentStateTest.php | 3 +-
.../Payment/State/InitialStateTest.php | 206 +++++++++++--
.../src/Internal/Service/OrderServiceTest.php | 288 ++++++++++++++++--
.../Service/PaymentProcessingServiceTest.php | 18 +-
.../Service/PaymentRequestServiceTest.php | 93 ++++++
21 files changed, 1262 insertions(+), 116 deletions(-)
create mode 100644 changelog/rpp-basic-process
create mode 100644 src/Internal/Payment/State/PaymentErrorState.php
create mode 100644 src/Internal/Payment/State/SystemErrorState.php
create mode 100644 src/Internal/Service/PaymentRequestService.php
create mode 100644 tests/unit/src/Internal/Service/PaymentRequestServiceTest.php
diff --git a/changelog/rpp-basic-process b/changelog/rpp-basic-process
new file mode 100644
index 00000000000..d576f27fd31
--- /dev/null
+++ b/changelog/rpp-basic-process
@@ -0,0 +1,5 @@
+Significance: patch
+Type: dev
+Comment: Adding a barebones payment process to src.
+
+
diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php
index 099a51db2e4..e294975ac75 100644
--- a/includes/class-wc-payment-gateway-wcpay.php
+++ b/includes/class-wc-payment-gateway-wcpay.php
@@ -813,9 +813,11 @@ function_exists( 'wcs_order_contains_subscription' )
* @throws Exception If the payment process could not be completed.
*/
public function new_process_payment( WC_Order $order ) {
+ $manual_capture = $this->get_capture_type() === Payment_Capture_Type::MANUAL();
+
// Important: No factors are provided here, they were meant just for `Feature`.
$service = wcpay_get_container()->get( PaymentProcessingService::class );
- $state = $service->process_payment( $order->get_id() );
+ $state = $service->process_payment( $order->get_id(), $manual_capture );
if ( $state instanceof CompletedState ) {
return [
diff --git a/includes/class-wc-payments-customer-service.php b/includes/class-wc-payments-customer-service.php
index 26c89f13003..72c2b7ff37b 100644
--- a/includes/class-wc-payments-customer-service.php
+++ b/includes/class-wc-payments-customer-service.php
@@ -147,6 +147,29 @@ public function create_customer_for_user( WP_User $user, array $customer_data ):
return $customer_id;
}
+ /**
+ * Manages customer details held on WCPay server for WordPress user associated with an order.
+ *
+ * @param int $user_id ID of the WP user to associate with the customer.
+ * @param WC_Order $order Woo Order.
+ * @return string WooPayments customer ID.
+ */
+ public function get_or_create_customer_id_from_order( int $user_id, WC_Order $order ): string {
+ // Determine the customer making the payment, create one if we don't have one already.
+ $customer_id = $this->get_customer_id_by_user_id( $user_id );
+
+ if ( null !== $customer_id ) {
+ // @todo: We need to update the customer here.
+ return $customer_id;
+ }
+
+ $customer_data = self::map_customer_data( $order, new WC_Customer( $user_id ) );
+ $user = get_user_by( 'id', $user_id );
+ $customer_id = $this->create_customer_for_user( $user, $customer_data );
+
+ return $customer_id;
+ }
+
/**
* Update the customer details held on the WCPay server associated with the given WordPress user.
*
diff --git a/src/Internal/DependencyManagement/ServiceProvider/GenericServiceProvider.php b/src/Internal/DependencyManagement/ServiceProvider/GenericServiceProvider.php
index a65e4cde721..354d39cabe8 100644
--- a/src/Internal/DependencyManagement/ServiceProvider/GenericServiceProvider.php
+++ b/src/Internal/DependencyManagement/ServiceProvider/GenericServiceProvider.php
@@ -38,6 +38,7 @@ public function register(): void {
$container->addShared( OrderService::class )
->addArgument( WC_Payments_Order_Service::class )
->addArgument( LegacyProxy::class )
+ ->addArgument( WC_Payments_Account::class )
->addArgument( HooksProxy::class );
$container->addShared( Level3Service::class )
diff --git a/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php b/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php
index 6e874479542..8641fe728dd 100644
--- a/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php
+++ b/src/Internal/DependencyManagement/ServiceProvider/PaymentsServiceProvider.php
@@ -8,6 +8,7 @@
namespace WCPay\Internal\DependencyManagement\ServiceProvider;
use Automattic\WooCommerce\Utilities\PluginUtil;
+use WC_Payments_Customer_Service;
use WCPay\Container;
use WCPay\Core\Mode;
use WCPay\Database_Cache;
@@ -15,12 +16,16 @@
use WCPay\Internal\Payment\Router;
use WCPay\Internal\Payment\State\CompletedState;
use WCPay\Internal\Payment\State\InitialState;
+use WCPay\Internal\Payment\State\PaymentErrorState;
use WCPay\Internal\Payment\State\StateFactory;
+use WCPay\Internal\Payment\State\SystemErrorState;
use WCPay\Internal\Proxy\LegacyProxy;
use WCPay\Internal\Service\PaymentProcessingService;
use WCPay\Internal\Service\ExampleService;
use WCPay\Internal\Service\ExampleServiceWithDependencies;
+use WCPay\Internal\Service\Level3Service;
use WCPay\Internal\Service\OrderService;
+use WCPay\Internal\Service\PaymentRequestService;
/**
* WCPay payments service provider.
@@ -37,8 +42,11 @@ class PaymentsServiceProvider extends AbstractServiceProvider {
StateFactory::class,
InitialState::class,
CompletedState::class,
+ SystemErrorState::class,
+ PaymentErrorState::class,
ExampleService::class,
ExampleServiceWithDependencies::class,
+ PaymentRequestService::class,
];
/**
@@ -54,13 +62,24 @@ public function register(): void {
->addArgument( StateFactory::class )
->addArgument( LegacyProxy::class );
+ $container->addShared( PaymentRequestService::class );
+
$container->add( InitialState::class )
->addArgument( StateFactory::class )
- ->addArgument( OrderService::class );
+ ->addArgument( OrderService::class )
+ ->addArgument( WC_Payments_Customer_Service::class )
+ ->addArgument( Level3Service::class )
+ ->addArgument( PaymentRequestService::class );
$container->add( CompletedState::class )
->addArgument( StateFactory::class );
+ $container->add( SystemErrorState::class )
+ ->addArgument( StateFactory::class );
+
+ $container->add( PaymentErrorState::class )
+ ->addArgument( StateFactory::class );
+
$container->addShared( Router::class )
->addArgument( Database_Cache::class );
diff --git a/src/Internal/Payment/PaymentContext.php b/src/Internal/Payment/PaymentContext.php
index 92852d75d5a..9979e614510 100644
--- a/src/Internal/Payment/PaymentContext.php
+++ b/src/Internal/Payment/PaymentContext.php
@@ -84,6 +84,114 @@ public function get_amount(): ?int {
return $this->get( 'amount' );
}
+ /**
+ * Stores the payment currency.
+ *
+ * @param string $currency Lowercase payment currency.
+ */
+ public function set_currency( string $currency ) {
+ $this->set( 'currency', $currency );
+ }
+
+ /**
+ * Returns the payment currency in lowercase.
+ *
+ * @return string|null
+ */
+ public function get_currency(): ?string {
+ return $this->get( 'currency' );
+ }
+
+ /**
+ * Controls whether manual capture is enabled.
+ *
+ * @param bool $manual_capture Whether to enable it or not.
+ */
+ public function toggle_manual_capture( bool $manual_capture ) {
+ $this->set( 'manual_capture', $manual_capture );
+ }
+
+ /**
+ * Indicates whether the payment should be captured manually.
+ *
+ * @return bool
+ */
+ public function should_capture_manually(): bool {
+ return $this->get( 'manual_capture' ) ?? false;
+ }
+
+ /**
+ * Stores the order metadata.
+ *
+ * @param array $metadata Metadata to sent to the API.
+ */
+ public function set_metadata( array $metadata ) {
+ $this->set( 'metadata', $metadata );
+ }
+
+ /**
+ * Returns the order level 3 data if set.
+ *
+ * @return array|null
+ */
+ public function get_level3_data(): ?array {
+ return $this->get( 'level3_data' );
+ }
+
+ /**
+ * Stores the order level 3 data.
+ *
+ * @param array $level3_data level3_data to sent to the API.
+ */
+ public function set_level3_data( array $level3_data ) {
+ $this->set( 'level3_data', $level3_data );
+ }
+
+ /**
+ * Returns the order metadata if set.
+ *
+ * @return array|null
+ */
+ public function get_metadata(): ?array {
+ return $this->get( 'metadata' );
+ }
+
+ /**
+ * Stores the CVC confirmation.
+ *
+ * @param string $cvc_confirmation The confirmation.
+ */
+ public function set_cvc_confirmation( string $cvc_confirmation = null ) {
+ $this->set( 'cvc_confirmation', $cvc_confirmation );
+ }
+
+ /**
+ * Returns the CVC confirmation if set.
+ *
+ * @return string|null
+ */
+ public function get_cvc_confirmation(): ?string {
+ return $this->get( 'cvc_confirmation' );
+ }
+
+ /**
+ * Stores a payment's fingerprint.
+ *
+ * @param string $fingerprint The fingerprint.
+ */
+ public function set_fingerprint( string $fingerprint ) {
+ $this->set( 'fingerprint', $fingerprint );
+ }
+
+ /**
+ * Returns a payment's fingerprint if set.
+ *
+ * @return string|null
+ */
+ public function get_fingerprint(): ?string {
+ return $this->get( 'fingerprint' );
+ }
+
/**
* Stores a payment method within the context.
*
@@ -101,4 +209,40 @@ public function set_payment_method( PaymentMethodInterface $payment_method ) {
public function get_payment_method(): ?PaymentMethodInterface {
return $this->get( 'payment_method' );
}
+
+ /**
+ * Stores the WP user ID, associated with the payment.
+ *
+ * @param int $user_id ID of the user.
+ */
+ public function set_user_id( int $user_id ) {
+ $this->set( 'user_id', $user_id );
+ }
+
+ /**
+ * Returns the ID of the user if any.
+ *
+ * @return int|null
+ */
+ public function get_user_id(): ?int {
+ return $this->get( 'user_id' );
+ }
+
+ /**
+ * Stores the remote customer ID.
+ *
+ * @param string $customer_id ID of the customer.
+ */
+ public function set_customer_id( string $customer_id ) {
+ $this->set( 'customer_id', $customer_id );
+ }
+
+ /**
+ * Returns the remote customer ID.
+ *
+ * @return string|null
+ */
+ public function get_customer_id(): ?string {
+ return $this->get( 'customer_id' );
+ }
}
diff --git a/src/Internal/Payment/PaymentRequest.php b/src/Internal/Payment/PaymentRequest.php
index fed2c8c7b42..806c4341f91 100644
--- a/src/Internal/Payment/PaymentRequest.php
+++ b/src/Internal/Payment/PaymentRequest.php
@@ -146,17 +146,40 @@ public function get_payment_method(): PaymentMethodInterface {
}
/**
- * Populates a payment context before processing a payment.
+ * Extract the payment CVC confirmation from the request.
*
- * This method is the link between the payment request, and the payment process.
- * Use it to make sure that all necessary parameters are provided in advance,
- * or throw an exception otherwise. Once done, the payment process would rely
- * on all needed parameters being in place.
+ * @return string|null
+ */
+ public function get_cvc_confirmation(): ?string {
+ $payment_method = $this->request['payment_method'] ?? null;
+ if ( null === $payment_method ) {
+ return null;
+ }
+
+ $cvc_request_key = 'wc-' . $payment_method . '-payment-cvc-confirmation';
+ if (
+ ! isset( $this->request[ $cvc_request_key ] ) ||
+ 'new' === $this->request[ $cvc_request_key ]
+ ) {
+ return null;
+ }
+
+ return $this->request[ $cvc_request_key ];
+ }
+
+ /**
+ * Extracts the fingerprint data from the request.
*
- * @param PaymentContext $context Context to populate.
- * @throws PaymentRequestException When data is not available or invalid.
+ * @return string
*/
- public function populate_context( PaymentContext $context ) {
- $context->set_payment_method( $this->get_payment_method() );
+ public function get_fingerprint(): ?string {
+ if ( ! empty( $this->request['wcpay-fingerprint'] ) ) {
+ $normalized = wc_clean( $this->request['wcpay-fingerprint'] );
+ if ( is_string( $normalized ) ) {
+ return $normalized;
+ }
+ }
+
+ return null;
}
}
diff --git a/src/Internal/Payment/State/AbstractPaymentState.php b/src/Internal/Payment/State/AbstractPaymentState.php
index f69d085efd5..021b1e05cb3 100644
--- a/src/Internal/Payment/State/AbstractPaymentState.php
+++ b/src/Internal/Payment/State/AbstractPaymentState.php
@@ -10,6 +10,9 @@
use WCPay\Vendor\League\Container\Exception\ContainerException;
use WCPay\Internal\Payment\Exception\StateTransitionException;
use WCPay\Internal\Payment\PaymentContext;
+use WCPay\Internal\Payment\PaymentRequest;
+use WCPay\Internal\Payment\PaymentRequestException;
+use WCPay\Exceptions\Order_Not_Found_Exception;
/**
* Base class for payment states.
@@ -90,11 +93,15 @@ protected function create_state( string $state_class ) {
/**
* Initialtes the payment process.
*
- * @return AbstractPaymentState The next state.
- * @throws StateTransitionException In case the new state was not found or could not be initialized.
+ * @param PaymentRequest $request The incoming payment processing request.
+ * @return CompletedState The next state.
+ * @throws StateTransitionException In case the completed state could not be initialized.
+ * @throws ContainerException When the dependency container cannot instantiate the state.
+ * @throws Order_Not_Found_Exception Order could not be found.
+ * @throws PaymentRequestException When data is not available or invalid.
* @psalm-suppress InvalidReturnType If this method does not throw, it will return a new state.
*/
- public function process() {
+ public function process( PaymentRequest $request ) {
$this->throw_unavailable_method_exception( __METHOD__ );
}
diff --git a/src/Internal/Payment/State/InitialState.php b/src/Internal/Payment/State/InitialState.php
index 3b17a376a4b..061c89ce41f 100644
--- a/src/Internal/Payment/State/InitialState.php
+++ b/src/Internal/Payment/State/InitialState.php
@@ -7,10 +7,19 @@
namespace WCPay\Internal\Payment\State;
+use WC_Payments_Customer_Service;
use WCPay\Vendor\League\Container\Exception\ContainerException;
use WCPay\Internal\Payment\Exception\StateTransitionException;
-use WCPay\Exceptions\Order_Not_Found_Exception;
use WCPay\Internal\Service\OrderService;
+use WCPay\Internal\Service\Level3Service;
+use WCPay\Internal\Service\PaymentRequestService;
+use WCPay\Core\Exceptions\Server\Request\Extend_Request_Exception;
+use WCPay\Core\Exceptions\Server\Request\Immutable_Parameter_Exception;
+use WCPay\Core\Exceptions\Server\Request\Invalid_Request_Parameter_Exception;
+use WCPay\Exceptions\Order_Not_Found_Exception;
+use WCPay\Internal\Payment\PaymentContext;
+use WCPay\Internal\Payment\PaymentRequest;
+use WCPay\Internal\Payment\PaymentRequestException;
/**
* Initial state, representing a freshly created payment.
@@ -23,34 +32,137 @@ class InitialState extends AbstractPaymentState {
*/
private $order_service;
+ /**
+ * Customer service.
+ *
+ * @var WC_Payments_Customer_Service
+ */
+ private $customer_service;
+
+ /**
+ * Level3 Data service.
+ *
+ * @var Level3Service
+ */
+ private $level3_service;
+
+ /**
+ * Payment request service.
+ *
+ * @var PaymentRequestService
+ */
+ private $payment_request_service;
+
/**
* Class constructor, only meant for storing dependencies.
*
- * @param StateFactory $state_factory Factory for payment states.
- * @param OrderService $order_service Service for order-related actions.
+ * @param StateFactory $state_factory Factory for payment states.
+ * @param OrderService $order_service Service for order-related actions.
+ * @param WC_Payments_Customer_Service $customer_service Service for managing remote customers.
+ * @param Level3Service $level3_service Service for Level3 Data.
+ * @param PaymentRequestService $payment_request_service Connection with the server.
*/
public function __construct(
StateFactory $state_factory,
- OrderService $order_service
+ OrderService $order_service,
+ WC_Payments_Customer_Service $customer_service,
+ Level3Service $level3_service,
+ PaymentRequestService $payment_request_service
) {
parent::__construct( $state_factory );
- $this->order_service = $order_service;
+ $this->order_service = $order_service;
+ $this->customer_service = $customer_service;
+ $this->level3_service = $level3_service;
+ $this->payment_request_service = $payment_request_service;
}
/**
* Initialtes the payment process.
*
+ * @param PaymentRequest $request The incoming payment processing request.
* @return CompletedState The next state.
* @throws StateTransitionException In case the completed state could not be initialized.
* @throws ContainerException When the dependency container cannot instantiate the state.
* @throws Order_Not_Found_Exception Order could not be found.
+ * @throws PaymentRequestException When data is not available or invalid.
*/
- public function process() {
- $order_id = $this->get_context()->get_order_id();
- $payment_method = $this->get_context()->get_payment_method();
- $this->order_service->set_payment_method_id( $order_id, $payment_method->get_id() );
+ public function process( PaymentRequest $request ) {
+ $context = $this->get_context();
+ $order_id = $context->get_order_id();
+
+ // Populate basic details from the request.
+ $this->populate_context_from_request( $request );
+
+ // Populate further details from the order.
+ $this->populate_context_from_order();
+
+ // Payments are currently based on intents, request one from the API.
+ try {
+ $intent = $this->payment_request_service->create_intent( $context );
+ } catch ( Invalid_Request_Parameter_Exception | Extend_Request_Exception | Immutable_Parameter_Exception $e ) {
+ return $this->create_state( SystemErrorState::class );
+ }
+ // Intent available, complete processing.
+ $this->order_service->update_order_from_successful_intent( $order_id, $intent, $context );
+
+ // If everything went well, transition to the completed state.
return $this->create_state( CompletedState::class );
}
+
+ /**
+ * Populates the payment context before processing a payment.
+ *
+ * This method is the link between the payment request, and the payment process.
+ * Use it to make sure that all necessary parameters are provided in advance,
+ * or throw an exception otherwise. Once done, the payment process would rely
+ * on all needed parameters being in place.
+ *
+ * @param PaymentRequest $request The request to use.
+ * @throws PaymentRequestException When data is not available or invalid.
+ */
+ protected function populate_context_from_request( PaymentRequest $request ) {
+ $context = $this->get_context();
+
+ $context->set_payment_method( $request->get_payment_method() );
+
+ $cvc_confirmation = $request->get_cvc_confirmation();
+ if ( ! is_null( $cvc_confirmation ) ) {
+ $context->set_cvc_confirmation( $cvc_confirmation );
+ }
+
+ $fingerprint = $request->get_fingerprint();
+ if ( ! is_null( $fingerprint ) ) {
+ $context->set_fingerprint( $fingerprint );
+ }
+ }
+
+ /**
+ * Populates the context with details, available in the order.
+ * This includes the update/creation of a customer.
+ *
+ * @throws Order_Not_Found_Exception In case the order could not be found.
+ */
+ protected function populate_context_from_order() {
+ $context = $this->get_context();
+ $order_id = $context->get_order_id();
+
+ // Start by setting up all local objects.
+ $this->order_service->import_order_data_to_payment_context( $order_id, $context );
+ $context->set_metadata(
+ array_merge(
+ $this->order_service->get_payment_metadata( $order_id ),
+ [ 'gateway_type' => 'src' ]
+ )
+ );
+ $context->set_level3_data( $this->level3_service->get_data_from_order( $order_id ) );
+
+ // Customer management involves a remote call.
+ $customer_id = $this->customer_service->get_or_create_customer_id_from_order(
+ $context->get_user_id(),
+ $this->order_service->_deprecated_get_order( $order_id )
+ );
+ $context->set_customer_id( $customer_id );
+ }
}
diff --git a/src/Internal/Payment/State/PaymentErrorState.php b/src/Internal/Payment/State/PaymentErrorState.php
new file mode 100644
index 00000000000..1f713500ecf
--- /dev/null
+++ b/src/Internal/Payment/State/PaymentErrorState.php
@@ -0,0 +1,18 @@
+legacy_service = $legacy_service;
$this->legacy_proxy = $legacy_proxy;
+ $this->account = $account;
$this->hooks_proxy = $hooks_proxy;
}
@@ -134,6 +150,110 @@ public function get_payment_metadata( int $order_id, Payment_Type $payment_type
return $this->hooks_proxy->apply_filters( 'wcpay_metadata_from_order', $metadata, $order, $payment_type );
}
+ /**
+ * Imports the data from an order to a payment context.
+ *
+ * @param int $order_id ID of the order.
+ * @param PaymentContext $context A payment context, awaiting order data.
+ * @throws Order_Not_Found_Exception
+ */
+ public function import_order_data_to_payment_context( int $order_id, PaymentContext $context ) {
+ $order = $this->get_order( $order_id );
+
+ $currency = strtolower( $order->get_currency() );
+ $amount = WC_Payments_Utils::prepare_amount( $order->get_total(), $currency );
+
+ $user = $order->get_user();
+ if ( false === $user ) { // Default to the current user.
+ $user = $this->legacy_proxy->call_function( 'wp_get_current_user' );
+ }
+
+ $context->set_currency( $currency );
+ $context->set_amount( $amount );
+ $context->set_user_id( $user->ID );
+ }
+
+ /**
+ * God method for updating orders once a payment has succeeded.
+ *
+ * @param int $order_id ID of the order that was just paid.
+ * @param WC_Payments_API_Abstract_Intention $intent Remote object. To be abstracted.
+ * @param PaymentContext $context Context for the payment.
+ * @throws Order_Not_Found_Exception
+ */
+ public function update_order_from_successful_intent(
+ int $order_id,
+ WC_Payments_API_Abstract_Intention $intent,
+ PaymentContext $context
+ ) {
+ $order = $this->get_order( $order_id );
+
+ $charge = null;
+ $charge_id = null;
+ if ( $intent instanceof WC_Payments_API_Payment_Intention ) {
+ $charge = $intent->get_charge();
+ $charge_id = $intent->get_charge()->get_id();
+ }
+
+ $this->legacy_service->attach_intent_info_to_order(
+ $order,
+ $intent->get_id(),
+ $intent->get_status(),
+ $context->get_payment_method()->get_id(),
+ $context->get_customer_id(),
+ $charge_id,
+ $context->get_currency()
+ );
+
+ $this->legacy_service->attach_transaction_fee_to_order( $order, $charge );
+ $this->legacy_service->update_order_status_from_intent( $order, $intent );
+
+ if ( ! is_null( $charge ) ) {
+ $this->attach_exchange_info_to_order( $order_id, $charge );
+ }
+ }
+
+ /**
+ * Given the charge data, checks if there was an exchange and adds it to the given order as metadata
+ *
+ * @param int $order_id The order to update.
+ * @param WC_Payments_API_Charge $charge Charge object.
+ * @throws Order_Not_Found_Exception
+ */
+ public function attach_exchange_info_to_order( int $order_id, WC_Payments_API_Charge $charge ) {
+ $order = $this->get_order( $order_id );
+
+ // This is a good example of something, which should be a service.
+ $currency_store = $this->legacy_proxy->call_function( 'get_option', 'woocommerce_currency' );
+ $currency_store = strtolower( $currency_store );
+ $currency_order = strtolower( $order->get_currency() );
+ $currency_account = strtolower( $this->account->get_account_default_currency() );
+
+ // If the default currency for the store is different from the currency for the merchant's Stripe account,
+ // the conversion rate provided by Stripe won't make sense, so we should not attach it to the order meta data
+ // and instead we'll rely on the _wcpay_multi_currency_order_exchange_rate meta key for analytics.
+ if ( $currency_store !== $currency_account ) {
+ return;
+ }
+
+ // If the account and order currency are the same, there was no exchange.
+ if ( $currency_order === $currency_account ) {
+ return;
+ }
+
+ // Without the balance transaction, we cannot check the exchange rate.
+ $transaction = $charge->get_balance_transaction();
+ $exchange_rate = $transaction['exchange_rate'] ?? null;
+ if ( is_null( $exchange_rate ) ) {
+ return;
+ }
+
+ // This is a pure method and can remain static.
+ $exchange_rate = WC_Payments_Utils::interpret_string_exchange_rate( $exchange_rate, $currency_order, $currency_account );
+ $order->update_meta_data( '_wcpay_multi_currency_stripe_exchange_rate', $exchange_rate );
+ $order->save_meta_data();
+ }
+
/**
* Retrieves the order object.
*
@@ -148,7 +268,7 @@ public function get_payment_metadata( int $order_id, Payment_Type $payment_type
* @return WC_Order Order object.
* @throws Order_Not_Found_Exception If the order could not be found.
*/
- private function get_order( int $order_id ): WC_Order {
+ protected function get_order( int $order_id ): WC_Order {
$order = $this->legacy_proxy->call_function( 'wc_get_order', $order_id );
if ( ! $order instanceof WC_Order ) {
throw new Order_Not_Found_Exception(
diff --git a/src/Internal/Service/PaymentProcessingService.php b/src/Internal/Service/PaymentProcessingService.php
index d81beaa1b27..e32a823ef12 100644
--- a/src/Internal/Service/PaymentProcessingService.php
+++ b/src/Internal/Service/PaymentProcessingService.php
@@ -52,43 +52,35 @@ public function __construct(
/**
* Process payment.
*
- * @param int $order_id Order ID provided by WooCommerce core.
+ * @param int $order_id Order ID provided by WooCommerce core.
+ * @param bool $manual_capture Whether to only create an authorization instead of a charge (optional).
*
* @throws Exception
* @throws StateTransitionException In case a state cannot be initialized.
* @throws PaymentRequestException When the request is malformed. This should be converted to a failure state.
* @throws ContainerException When the dependency container cannot instantiate the state.
*/
- public function process_payment( int $order_id ) {
+ public function process_payment( int $order_id, bool $manual_capture = false ) {
// Start with a basis context.
- $context = $this->create_payment_context( $order_id );
+ $context = $this->create_payment_context( $order_id, $manual_capture );
- // Add details from the request.
- $request = $this->create_payment_request();
- $request->populate_context( $context );
+ $request = new PaymentRequest( $this->legacy_proxy );
+ $initial_state = $this->state_factory->create_state( InitialState::class, $context );
+ $completed_state = $initial_state->process( $request );
- $state = $this->state_factory->create_state( InitialState::class, $context );
- $state = $state->process();
-
- return $state;
+ return $completed_state;
}
/**
* Instantiates a new empty payment context.
*
- * @param int $order_id ID of the order that the context belongs to.
+ * @param int $order_id ID of the order that the context belongs to.
+ * @param bool $manual_capture Whether manual capture is enabled.
* @return PaymentContext
*/
- protected function create_payment_context( int $order_id ): PaymentContext {
- return new PaymentContext( $order_id );
- }
-
- /**
- * Instantiates a new payment request.
- *
- * @return PaymentRequest
- */
- protected function create_payment_request(): PaymentRequest {
- return new PaymentRequest( $this->legacy_proxy );
+ protected function create_payment_context( int $order_id, bool $manual_capture = false ): PaymentContext {
+ $context = new PaymentContext( $order_id );
+ $context->toggle_manual_capture( $manual_capture );
+ return $context;
}
}
diff --git a/src/Internal/Service/PaymentRequestService.php b/src/Internal/Service/PaymentRequestService.php
new file mode 100644
index 00000000000..c7ba7b92428
--- /dev/null
+++ b/src/Internal/Service/PaymentRequestService.php
@@ -0,0 +1,48 @@
+set_amount( $context->get_amount() );
+ $request->set_currency_code( $context->get_currency() );
+ $request->set_payment_method( $context->get_payment_method()->get_id() );
+ $request->set_customer( $context->get_customer_id() );
+ $request->set_capture_method( $context->should_capture_manually() );
+ $request->set_metadata( $context->get_metadata() );
+ $request->set_level3( $context->get_level3_data() );
+ $request->set_payment_methods( [ 'card' ] ); // Initial payment process only supports cards.
+ $request->set_cvc_confirmation( $context->get_cvc_confirmation() );
+ // Empty string to trigger the link to `Buyer_Fingerprinting_Service`.
+ $request->set_fingerprint( $context->get_fingerprint() ?? '' );
+
+ // ToDo: The WooPay service should accept the old and new payment contexts.
+ $request->assign_hook( 'wcpay_create_and_confirm_intent_request_new' );
+ return $request->send();
+ }
+}
diff --git a/tests/unit/src/Internal/Payment/PaymentContextTest.php b/tests/unit/src/Internal/Payment/PaymentContextTest.php
index cb295e22092..71ed252cfcf 100644
--- a/tests/unit/src/Internal/Payment/PaymentContextTest.php
+++ b/tests/unit/src/Internal/Payment/PaymentContextTest.php
@@ -7,7 +7,6 @@
namespace WCPay\Tests\Internal\Payment;
-use PHPUnit\Framework\MockObject\MockObject;
use WCPAY_UnitTestCase;
use WCPay\Internal\Payment\PaymentContext;
use WCPay\Internal\Payment\PaymentMethod\NewPaymentMethod;
@@ -55,4 +54,67 @@ public function test_payment_method() {
$this->sut->set_payment_method( $payment_method );
$this->assertSame( $payment_method, $this->sut->get_payment_method() );
}
+
+ public function test_currency() {
+ $currency = 'eur';
+
+ $this->sut->set_currency( $currency );
+ $this->assertSame( $currency, $this->sut->get_currency() );
+ }
+
+ public function test_manual_capture_disabled() {
+ $toggle_manual_capture = false;
+
+ $this->sut->toggle_manual_capture( $toggle_manual_capture );
+ $this->assertSame( $toggle_manual_capture, $this->sut->should_capture_manually() );
+ }
+
+ public function test_manual_capture_enabled() {
+ $toggle_manual_capture = true;
+
+ $this->sut->toggle_manual_capture( $toggle_manual_capture );
+ $this->assertSame( $toggle_manual_capture, $this->sut->should_capture_manually() );
+ }
+
+ public function test_metadata() {
+ $metadata = [ 'some_meta_key' => 'yes' ];
+
+ $this->sut->set_metadata( $metadata );
+ $this->assertSame( $metadata, $this->sut->get_metadata() );
+ }
+
+ public function test_level3_data() {
+ $level3_data = [ 'items' => [] ];
+
+ $this->sut->set_level3_data( $level3_data );
+ $this->assertSame( $level3_data, $this->sut->get_level3_data() );
+ }
+
+ public function test_cvc_confirmation() {
+ $cvc_confirmation = 'confirmation';
+
+ $this->sut->set_cvc_confirmation( $cvc_confirmation );
+ $this->assertSame( $cvc_confirmation, $this->sut->get_cvc_confirmation() );
+ }
+
+ public function test_fingerprint() {
+ $fingerprint = 'fingerprint';
+
+ $this->sut->set_fingerprint( $fingerprint );
+ $this->assertSame( $fingerprint, $this->sut->get_fingerprint() );
+ }
+
+ public function test_user_id() {
+ $user_id = 123;
+
+ $this->sut->set_user_id( $user_id );
+ $this->assertSame( $user_id, $this->sut->get_user_id() );
+ }
+
+ public function test_customer_id() {
+ $customer_id = 'cus_ZYX';
+
+ $this->sut->set_customer_id( $customer_id );
+ $this->assertSame( $customer_id, $this->sut->get_customer_id() );
+ }
}
diff --git a/tests/unit/src/Internal/Payment/PaymentRequestTest.php b/tests/unit/src/Internal/Payment/PaymentRequestTest.php
index 11cad8ffed4..2d1ac129b74 100644
--- a/tests/unit/src/Internal/Payment/PaymentRequestTest.php
+++ b/tests/unit/src/Internal/Payment/PaymentRequestTest.php
@@ -10,7 +10,6 @@
use PHPUnit\Framework\MockObject\MockObject;
use WC_Payment_Token;
use WC_Payment_Tokens;
-use WCPay\Internal\Payment\PaymentContext;
use WCPay\Internal\Payment\PaymentMethod\NewPaymentMethod;
use WCPay\Internal\Payment\PaymentMethod\SavedPaymentMethod;
use WCPay\Internal\Payment\PaymentRequest;
@@ -251,22 +250,69 @@ public function test_get_payment_method_throw_exception_due_to_no_payment_method
$this->sut->get_payment_method();
}
- public function test_populate_context() {
- $payment_method_id = 'pm_XYZ';
+ public function provider_get_cvc_confirmation() {
+ return [
+ 'No payment method' => [
+ null,
+ null,
+ null,
+ ],
+ 'Payment method set, no CVC' => [
+ 'woocommerce_payments',
+ null,
+ null,
+ ],
+ 'Payment method set, new CVC' => [
+ 'woocommerce_payments',
+ 'new',
+ null,
+ ],
+ 'Payment method set, meaningful CVC' => [
+ 'woocommerce_payments',
+ 'xyz1234',
+ 'xyz1234',
+ ],
+ ];
+ }
- $sut = new PaymentRequest(
- $this->mock_legacy_proxy,
- [
- 'payment_method' => 'woocommerce_payments',
- 'wcpay-payment-method' => $payment_method_id,
- ]
- );
+ /**
+ * @dataProvider provider_get_cvc_confirmation
+ */
+ public function test_get_cvc_confirmation( $payment_method, $cvc, $expected ) {
+ $request = [];
+ if ( $payment_method ) {
+ $request['payment_method'] = $payment_method;
+ }
- $mock_context = $this->createMock( PaymentContext::class );
- $mock_context->expects( $this->once() )
- ->method( 'set_payment_method' )
- ->with( $this->isInstanceOf( NewPaymentMethod::class ) );
+ if ( $cvc ) {
+ $request[ 'wc-' . $payment_method . '-payment-cvc-confirmation' ] = $cvc;
+ }
+
+ $sut = new PaymentRequest( $this->mock_legacy_proxy, $request );
+ $result = $sut->get_cvc_confirmation();
+ $this->assertSame( $expected, $result );
+ }
+
+ public function provider_get_fingerprint() {
+ return [
+ 'Nothing provided' => [ null, null ],
+ 'Empty string' => [ '', null ],
+ 'Normal string' => [ 'abc', 'abc' ],
+ 'Needs normalization' => [ 'populate_context( $mock_context );
+ $sut = new PaymentRequest( $this->mock_legacy_proxy, $request );
+ $result = $sut->get_fingerprint();
+ $this->assertSame( $expected, $result );
}
}
diff --git a/tests/unit/src/Internal/Payment/State/AbstractPaymentStateTest.php b/tests/unit/src/Internal/Payment/State/AbstractPaymentStateTest.php
index 04348a2c1f7..0635600fce3 100644
--- a/tests/unit/src/Internal/Payment/State/AbstractPaymentStateTest.php
+++ b/tests/unit/src/Internal/Payment/State/AbstractPaymentStateTest.php
@@ -11,6 +11,7 @@
use PHPUnit\Framework\MockObject\MockObject;
use WCPay\Internal\Payment\Exception\StateTransitionException;
use WCPay\Internal\Payment\PaymentContext;
+use WCPay\Internal\Payment\PaymentRequest;
use WCPay\Internal\Payment\State\CompletedState;
use WCPay\Internal\Payment\State\AbstractPaymentState;
use WCPay\Internal\Payment\State\StateFactory;
@@ -82,6 +83,6 @@ public function test_create_state_uses_the_factory() {
public function test_process_throws_exception() {
$this->expectException( StateTransitionException::class );
$this->expectExceptionMessage( 'The WCPay\Internal\Payment\State\AbstractPaymentState::process method is not available in the current payment state (' . PureState::class . ').' );
- $this->sut->process();
+ $this->sut->process( $this->createMock( PaymentRequest::class ) );
}
}
diff --git a/tests/unit/src/Internal/Payment/State/InitialStateTest.php b/tests/unit/src/Internal/Payment/State/InitialStateTest.php
index 04b6380dd5b..6e8a0b3f68c 100644
--- a/tests/unit/src/Internal/Payment/State/InitialStateTest.php
+++ b/tests/unit/src/Internal/Payment/State/InitialStateTest.php
@@ -7,14 +7,26 @@
namespace WCPay\Tests\Internal\Payment\State;
+use Exception;
use WCPAY_UnitTestCase;
use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit_Utils;
+use ReflectionClass;
+use WC_Order;
+use WC_Payments_API_Payment_Intention;
+use WC_Payments_Customer_Service;
+use WCPay\Core\Exceptions\Server\Request\Extend_Request_Exception;
+use WCPay\Core\Exceptions\Server\Request\Invalid_Request_Parameter_Exception;
use WCPay\Internal\Payment\State\InitialState;
use WCPay\Internal\Payment\State\StateFactory;
use WCPay\Internal\Payment\PaymentContext;
use WCPay\Internal\Payment\PaymentMethod\NewPaymentMethod;
+use WCPay\Internal\Payment\PaymentRequest;
use WCPay\Internal\Payment\State\CompletedState;
+use WCPay\Internal\Payment\State\SystemErrorState;
+use WCPay\Internal\Service\Level3Service;
use WCPay\Internal\Service\OrderService;
+use WCPay\Internal\Service\PaymentRequestService;
/**
* Tests for the initial payment state.
@@ -37,6 +49,21 @@ class InitialStateTest extends WCPAY_UnitTestCase {
*/
private $mock_order_service;
+ /**
+ * @var WC_Payments_Customer_Service|MockObject
+ */
+ private $mock_customer_service;
+
+ /**
+ * @var Level3Service|MockObject
+ */
+ private $mock_level3_service;
+
+ /**
+ * @var PaymentRequestService|MockObject
+ */
+ private $mock_payment_request_service;
+
/**
* @var PaymentContext|MockObject
*/
@@ -48,40 +75,183 @@ class InitialStateTest extends WCPAY_UnitTestCase {
protected function setUp(): void {
parent::setUp();
- $this->mock_state_factory = $this->createMock( StateFactory::class );
- $this->mock_order_service = $this->createMock( OrderService::class );
- $this->mock_context = $this->createMock( PaymentContext::class );
+ $this->mock_state_factory = $this->createMock( StateFactory::class );
+ $this->mock_order_service = $this->createMock( OrderService::class );
+ $this->mock_context = $this->createMock( PaymentContext::class );
+ $this->mock_customer_service = $this->createMock( WC_Payments_Customer_Service::class );
+ $this->mock_level3_service = $this->createMock( Level3Service::class );
+ $this->mock_payment_request_service = $this->createMock( PaymentRequestService::class );
- $this->sut = new InitialState( $this->mock_state_factory, $this->mock_order_service );
+ $this->sut = new InitialState(
+ $this->mock_state_factory,
+ $this->mock_order_service,
+ $this->mock_customer_service,
+ $this->mock_level3_service,
+ $this->mock_payment_request_service
+ );
$this->sut->set_context( $this->mock_context );
}
+ /**
+ * Different `process` scenarios.
+ *
+ * There will be a single parameter, representing an exception, if any.
+ *
+ * @return array
+ */
+ public function provider_process() {
+ return [
+ [ null ],
+ [ new Invalid_Request_Parameter_Exception( 'A parameter cannot be set.', 'invalid_parameter' ) ],
+ [ new Extend_Request_Exception( 'A parameter cannot be set.', 'cannot_extend' ) ],
+ ];
+ }
+
/**
* Ensures that the `process` method creates a new completed state.
+ *
+ * @param \Exception|null $exception Exception that would be thrown by intent creation.
+ * @dataProvider provider_process
*/
- public function test_process_returns_new_completed_state() {
- $order_id = 123;
- $pm_id = 'pm_ZYX';
+ public function test_process( Exception $exception = null ) {
+ $order_id = 123;
+ $mock_request = $this->createMock( PaymentRequest::class );
+ $mock_intent = $this->createMock( WC_Payments_API_Payment_Intention::class );
+
+ /**
+ * This test works with the root `process` method, which calls a few
+ * internal methods. We want to mock them for the purpose of this test.
+ *
+ * @var MockObject|InitialState
+ */
+ $this->sut = $this->getMockBuilder( InitialState::class )
+ ->onlyMethods( [ 'populate_context_from_request', 'populate_context_from_order' ] )
+ ->setConstructorArgs(
+ [
+ $this->mock_state_factory,
+ $this->mock_order_service,
+ $this->mock_customer_service,
+ $this->mock_level3_service,
+ $this->mock_payment_request_service,
+ ]
+ )
+ ->getMock();
+ $this->sut->set_context( $this->mock_context );
+ // There's a single call to get the order ID.
$this->mock_context->expects( $this->once() )
->method( 'get_order_id' )
->willReturn( $order_id );
- $this->mock_context->expects( $this->once() )
- ->method( 'get_payment_method' )
- ->willReturn( new NewPaymentMethod( $pm_id ) );
+ // Verify that the context is populated.
+ $this->sut->expects( $this->once() )->method( 'populate_context_from_request' )->with( $mock_request );
+ $this->sut->expects( $this->once() )->method( 'populate_context_from_order' );
- $this->mock_order_service->expects( $this->once() )
- ->method( 'set_payment_method_id' )
- ->with( $order_id, $pm_id );
+ // Arrange intent creation.
+ if ( $exception ) {
+ $this->mock_payment_request_service->expects( $this->once() )
+ ->method( 'create_intent' )
+ ->with( $this->mock_context )
+ ->willThrowException( $exception );
+ } else {
+ $this->mock_payment_request_service->expects( $this->once() )
+ ->method( 'create_intent' )
+ ->with( $this->mock_context )
+ ->willReturn( $mock_intent );
+
+ // Assert order update.
+ $this->mock_order_service->expects( $this->once() )
+ ->method( 'update_order_from_successful_intent' )
+ ->with( $order_id, $mock_intent, $this->mock_context );
+ }
- $mock_completed_state = $this->createMock( CompletedState::class );
+ // Arrange the final state.
+ $state_class = $exception ? SystemErrorState::class : CompletedState::class;
+ $mock_final_state = $this->createMock( $state_class );
$this->mock_state_factory->expects( $this->once() )
->method( 'create_state' )
- ->with( CompletedState::class, $this->mock_context )
- ->willReturn( $mock_completed_state );
+ ->with( $state_class, $this->mock_context )
+ ->willReturn( $mock_final_state );
+
+ // Act: Process.
+ $result = $this->sut->process( $mock_request );
+
+ // Assert: Successful transition.
+ $this->assertSame( $mock_final_state, $result );
+ }
+
+ public function test_populate_context_from_request() {
+ $payment_method = new NewPaymentMethod( 'pm_123' );
+ $fingerprint = 'fingerprint';
+ $cvc_confirmation = 'CVCConfirmation';
+
+ // Setup the mock request.
+ $mock_request = $this->createMock( PaymentRequest::class );
+ $mock_request->expects( $this->once() )->method( 'get_payment_method' )->willReturn( $payment_method );
+ $mock_request->expects( $this->once() )->method( 'get_cvc_confirmation' )->willReturn( $cvc_confirmation );
+ $mock_request->expects( $this->once() )->method( 'get_fingerprint' )->willReturn( $fingerprint );
+
+ // Assume that everything from the request would be imported into the context.
+ $this->mock_context->expects( $this->once() )->method( 'set_payment_method' )->with( $payment_method );
+ $this->mock_context->expects( $this->once() )->method( 'set_cvc_confirmation' )->with( $cvc_confirmation );
+ $this->mock_context->expects( $this->once() )->method( 'set_fingerprint' )->with( $fingerprint );
+
+ // Use reflection to acces.
+ PHPUnit_Utils::call_method( $this->sut, 'populate_context_from_request', [ $mock_request ] );
+ }
+
+ public function test_populate_context_from_order() {
+ $order_id = 123;
+ $user_id = 456;
+ $customer_id = 'cus_123';
+ $metadata = [ 'sample' => 'true' ];
+ $level3_data = [ 'items' => [] ];
+ $mock_order = $this->createMock( WC_Order::class );
+
+ // Prepare the order ID.
+ $this->mock_context->expects( $this->once() )
+ ->method( 'get_order_id' )
+ ->willReturn( $order_id );
+
+ // Arrange the import of order data to the payment.
+ $this->mock_order_service->expects( $this->once() )
+ ->method( 'import_order_data_to_payment_context' )
+ ->with( $order_id, $this->mock_context );
+
+ // Arrange metadata import.
+ $this->mock_order_service->expects( $this->once() )
+ ->method( 'get_payment_metadata' )
+ ->with( $order_id )
+ ->willReturn( $metadata );
+ $this->mock_context->expects( $this->once() )
+ ->method( 'set_metadata' )
+ ->with( array_merge( $metadata, [ 'gateway_type' => 'src' ] ) );
+
+ // Arrange level 3 data import.
+ $this->mock_level3_service->expects( $this->once() )
+ ->method( 'get_data_from_order' )
+ ->with( $order_id )
+ ->willReturn( $level3_data );
+ $this->mock_context->expects( $this->once() )
+ ->method( 'set_level3_data' )
+ ->with( $level3_data );
+
+ // Arrange customer management.
+ $this->mock_context->expects( $this->once() )
+ ->method( 'get_user_id' )
+ ->willReturn( $user_id );
+ $this->mock_order_service->expects( $this->once() )
+ ->method( '_deprecated_get_order' )
+ ->with( $order_id )
+ ->willReturn( $mock_order );
+ $this->mock_customer_service->expects( $this->once() )
+ ->method( 'get_or_create_customer_id_from_order' )
+ ->with( $user_id, $mock_order )
+ ->willReturn( $customer_id );
+ $this->mock_context->expects( $this->once() )
+ ->method( 'set_customer_id' )
+ ->with( $customer_id );
- $result = $this->sut->process();
- $this->assertSame( $mock_completed_state, $result );
+ PHPUnit_Utils::call_method( $this->sut, 'populate_context_from_order', [] );
}
}
diff --git a/tests/unit/src/Internal/Service/OrderServiceTest.php b/tests/unit/src/Internal/Service/OrderServiceTest.php
index ba45a8afc50..362cb725fa9 100644
--- a/tests/unit/src/Internal/Service/OrderServiceTest.php
+++ b/tests/unit/src/Internal/Service/OrderServiceTest.php
@@ -9,14 +9,21 @@
use PHPUnit\Framework\MockObject\MockObject;
use WC_Order;
+use WC_Payments_Account;
+use WC_Payments_API_Charge;
+use WC_Payments_API_Payment_Intention;
+use WC_Payments_API_Setup_Intention;
use WC_Payments_Features;
use WC_Payments_Order_Service;
use WCPay\Constants\Payment_Type;
use WCPay\Exceptions\Order_Not_Found_Exception;
+use WCPay\Internal\Payment\PaymentContext;
+use WCPay\Internal\Payment\PaymentMethod\NewPaymentMethod;
use WCPay\Internal\Proxy\HooksProxy;
use WCPay\Internal\Proxy\LegacyProxy;
use WCPAY_UnitTestCase;
use WCPay\Internal\Service\OrderService;
+use WP_User;
/**
* Order service unit tests.
@@ -25,7 +32,7 @@ class OrderServiceTest extends WCPAY_UnitTestCase {
/**
* Service under test.
*
- * @var OrderService
+ * @var MockObject|OrderService
*/
private $sut;
@@ -39,6 +46,11 @@ class OrderServiceTest extends WCPAY_UnitTestCase {
*/
private $mock_legacy_proxy;
+ /**
+ * @var WC_Payments_Account|MockObject
+ */
+ private $mock_account;
+
/**
* @var HooksProxy|MockObject
*/
@@ -59,29 +71,50 @@ protected function setUp(): void {
$this->mock_legacy_proxy = $this->createMock( LegacyProxy::class );
$this->mock_legacy_service = $this->createMock( WC_Payments_Order_Service::class );
+ $this->mock_account = $this->createMock( WC_Payments_Account::class );
$this->mock_hooks_proxy = $this->createMock( HooksProxy::class );
- // Main service under test: OrderService.
+ // Service under test, but with mockable methods.
+ $this->sut = $this->getMockBuilder( OrderService::class )
+ ->onlyMethods( [ 'get_order', 'attach_exchange_info_to_order' ] )
+ ->setConstructorArgs(
+ [
+ $this->mock_legacy_service,
+ $this->mock_legacy_proxy,
+ $this->mock_account,
+ $this->mock_hooks_proxy,
+ ]
+ )
+ ->getMock();
+ }
+
+ public function test_get_order_returns_order() {
$this->sut = new OrderService(
$this->mock_legacy_service,
$this->mock_legacy_proxy,
+ $this->mock_account,
$this->mock_hooks_proxy
);
- }
- public function test_get_order_returns_order() {
$mock_order = $this->createMock( WC_Order::class );
-
$this->mock_legacy_proxy->expects( $this->once() )
->method( 'call_function' )
->with( 'wc_get_order', $this->order_id )
->willReturn( $mock_order );
+ // Go through `_deprecated_get_order` to call `get_order`.
$result = $this->sut->_deprecated_get_order( $this->order_id );
$this->assertSame( $mock_order, $result );
}
public function test_get_order_throws_exception() {
+ $this->sut = new OrderService(
+ $this->mock_legacy_service,
+ $this->mock_legacy_proxy,
+ $this->mock_account,
+ $this->mock_hooks_proxy
+ );
+
$this->mock_legacy_proxy->expects( $this->once() )
->method( 'call_function' )
->with( 'wc_get_order', $this->order_id )
@@ -89,9 +122,18 @@ public function test_get_order_throws_exception() {
$this->expectException( Order_Not_Found_Exception::class );
$this->expectExceptionMessage( "The requested order (ID $this->order_id) was not found." );
+
+ // Go through `_deprecated_get_order` to call `get_order`.
$this->sut->_deprecated_get_order( $this->order_id );
}
+ public function test__deprecated_get_order_returns_order() {
+ $mock_order = $this->mock_get_order();
+
+ $result = $this->sut->_deprecated_get_order( $this->order_id );
+ $this->assertSame( $mock_order, $result );
+ }
+
public function test_set_payment_method_id() {
$pm_id = 'pm_XYZ';
@@ -159,22 +201,16 @@ public function test_get_payment_metadata_without_subscriptions() {
* @dataProvider provider_subscription_details
*/
public function test_get_payment_metadata_with_subscription( bool $is_renewal, bool $wcpay_subscription ) {
- $mock_order = $this->createMock( WC_Order::class );
+ $mock_order = $this->mock_get_order();
- $this->mock_legacy_proxy->expects( $this->exactly( 4 ) )
+ $this->mock_legacy_proxy->expects( $this->exactly( 3 ) )
->method( 'call_function' )
->withConsecutive(
- [ 'wc_get_order', $this->order_id ],
[ 'function_exists', 'wcs_order_contains_subscription' ],
[ 'wcs_order_contains_subscription', $mock_order, 'any' ],
[ 'wcs_order_contains_renewal', $mock_order ]
)
- ->willReturnOnConsecutiveCalls(
- $mock_order,
- true,
- true,
- $is_renewal
- );
+ ->willReturnOnConsecutiveCalls( true, true, $is_renewal );
$this->mock_legacy_proxy->expects( $this->once() )
->method( 'call_static' )
@@ -206,6 +242,224 @@ public function provider_subscription_details() {
];
}
+ public function provider_import_order_data_to_payment_context() {
+ $existing_user = new WP_User();
+ $existing_user->ID = 10;
+
+ return [
+ 'No User' => [ null ],
+ 'User' => [ $existing_user ],
+ ];
+ }
+
+ /**
+ * @dataProvider provider_import_order_data_to_payment_context
+ */
+ public function test_import_order_data_to_payment_context( $user ) {
+ // Create a mock order that will be used to extract data.
+ $mock_order = $this->createMock( WC_Order::class );
+ $this->sut->expects( $this->once() )
+ ->method( 'get_order' )
+ ->willReturn( $mock_order );
+
+ // Create a context where data will be imported.
+ $mock_context = $this->createMock( PaymentContext::class );
+
+ // Currency and amount calls.
+ $currency = 'usd';
+ $mock_order->expects( $this->once() )->method( 'get_currency' )->willReturn( $currency );
+ $mock_context->expects( $this->once() )->method( 'set_currency' )->with( $currency );
+ $amount = 1234;
+ $mock_order->expects( $this->once() )->method( 'get_total' )->willReturn( $amount / 100 );
+ $mock_context->expects( $this->once() )->method( 'set_amount' )->with( $amount );
+
+ // Mock the user.
+ $mock_order->expects( $this->once() )
+ ->method( 'get_user' )
+ ->willReturn( $user ?? false );
+ if ( ! $user ) {
+ $user = $this->createMock( WP_User::class );
+ $user->ID = 10;
+
+ $this->mock_legacy_proxy->expects( $this->once() )
+ ->method( 'call_function' )
+ ->with( 'wp_get_current_user' )
+ ->willReturn( $user );
+ }
+ $mock_context->expects( $this->once() )
+ ->method( 'set_user_id' )
+ ->with( 10 );
+
+ // Act.
+ $this->sut->import_order_data_to_payment_context( $this->order_id, $mock_context );
+ }
+
+ public function provider_update_order_from_successful_intent() {
+ $pi = $this->createMock( WC_Payments_API_Payment_Intention::class );
+ $si = $this->createMock( WC_Payments_API_Setup_Intention::class );
+
+ return [
+ 'Payment Intent' => [ $pi ],
+ 'Setup Intent' => [ $si ],
+ ];
+ }
+
+ /**
+ * @param WC_Payments_API_Payment_Intention|WC_Payments_API_Setup_Intention|MockObject $intent
+ * @dataProvider provider_update_order_from_successful_intent
+ */
+ public function test_update_order_from_successful_intent( $intent ) {
+ $charge_id = null;
+ $mock_charge = null;
+ $intent_id = 'pi_XYZ';
+ $intent_status = 'success';
+ $customer_id = 'cus_XYZ';
+ $currency = 'usd';
+ $payment_method_id = 'pm_XYZ';
+
+ // Create a mock order that will be used.
+ $mock_order = $this->createMock( WC_Order::class );
+ $this->sut->expects( $this->once() )
+ ->method( 'get_order' )
+ ->with( $this->order_id )
+ ->willReturn( $mock_order );
+
+ if ( is_a( $intent, WC_Payments_API_Payment_Intention::class ) ) {
+ $charge_id = 'ch_XYZ';
+ $mock_charge = $this->createMock( WC_Payments_API_Charge::class );
+
+ $mock_charge->expects( $this->once() )
+ ->method( 'get_id' )
+ ->willReturn( $charge_id );
+
+ $intent->expects( $this->exactly( 2 ) )
+ ->method( 'get_charge' )
+ ->willReturn( $mock_charge );
+ }
+
+ // Prepare all parameters for `attach_intent_info_to_order`.
+ $intent->expects( $this->once() )
+ ->method( 'get_id' )
+ ->willReturn( $intent_id );
+ $intent->expects( $this->once() )
+ ->method( 'get_status' )
+ ->willReturn( $intent_status );
+
+ $mock_context = $this->createMock( PaymentContext::class );
+ $mock_context->expects( $this->once() )
+ ->method( 'get_payment_method' )
+ ->willReturn( new NewPaymentMethod( $payment_method_id ) );
+ $mock_context->expects( $this->once() )
+ ->method( 'get_customer_id' )
+ ->willReturn( $customer_id );
+ $mock_context->expects( $this->once() )
+ ->method( 'get_currency' )
+ ->willReturn( $currency );
+
+ $this->mock_legacy_service->expects( $this->once() )
+ ->method( 'attach_intent_info_to_order' )
+ ->with(
+ $mock_order,
+ $intent_id,
+ $intent_status,
+ $payment_method_id,
+ $customer_id,
+ $charge_id,
+ $currency
+ );
+
+ // Prepare all additional calls.
+ $this->mock_legacy_service->expects( $this->once() )
+ ->method( 'attach_transaction_fee_to_order' )
+ ->with( $mock_order, $mock_charge );
+ $this->mock_legacy_service->expects( $this->once() )
+ ->method( 'update_order_status_from_intent' )
+ ->with( $mock_order, $intent );
+ if ( ! is_null( $mock_charge ) ) {
+ $this->sut->expects( $this->once() )
+ ->method( 'attach_exchange_info_to_order' )
+ ->with( $this->order_id, $mock_charge );
+ }
+
+ // Act.
+ $this->sut->update_order_from_successful_intent( $this->order_id, $intent, $mock_context );
+ }
+
+ public function provider_attach_exchange_info_to_order() {
+ return [
+ 'Different store and account currencies' => [ 'USD', 'USD', 'EUR', null, null ],
+ 'Same order and account currencies' => [ 'EUR', 'EUR', 'EUR', null, null ],
+ 'No exchange rate' => [ 'USD', 'EUR', 'USD', true, null ],
+ 'With exchange rate' => [ 'USD', 'EUR', 'USD', true, 3.0 ],
+ ];
+ }
+
+ /**
+ * @dataProvider provider_attach_exchange_info_to_order
+ */
+ public function test_attach_exchange_info_to_order( $store_currency, $order_currency, $account_currency, $has_charge = false, $exchange_rate = null ) {
+ /**
+ * Create a SUT that doesn't mock the method here.
+ *
+ * @var OrderService|MockObject
+ */
+ $this->sut = $this->getMockBuilder( OrderService::class )
+ ->onlyMethods( [ 'get_order' ] )
+ ->setConstructorArgs(
+ [
+ $this->mock_legacy_service,
+ $this->mock_legacy_proxy,
+ $this->mock_account,
+ $this->mock_hooks_proxy,
+ ]
+ )
+ ->getMock();
+
+ // Mock the store currency.
+ $this->mock_legacy_proxy->expects( $this->once() )
+ ->method( 'call_function' )
+ ->with( 'get_option', 'woocommerce_currency' )
+ ->willReturn( $store_currency );
+
+ // Mock the order currency.
+ $mock_order = $this->mock_get_order();
+ $mock_order->expects( $this->once() )->method( 'get_currency' )->willReturn( $order_currency );
+
+ // Mock the account currency.
+ $this->mock_account->expects( $this->once() )
+ ->method( 'get_account_default_currency' )
+ ->willReturn( $account_currency );
+
+ // No charge means that the charge object should never be reached.
+ $mock_charge = $this->createMock( WC_Payments_API_Charge::class );
+ if ( ! $has_charge ) {
+ $mock_charge->expects( $this->never() )->method( 'get_balance_transaction' );
+ $this->sut->attach_exchange_info_to_order( $this->order_id, $mock_charge );
+ return;
+ }
+
+ $transaction = [ 'exchange_rate' => $exchange_rate ];
+ $mock_charge->expects( $this->once() )
+ ->method( 'get_balance_transaction' )
+ ->willReturn( $transaction );
+
+ // No exchange rate means that the order will never be updated.
+ if ( ! $exchange_rate ) {
+ $mock_order->expects( $this->never() )->method( 'update_meta_data' );
+ $this->sut->attach_exchange_info_to_order( $this->order_id, $mock_charge );
+ return;
+ }
+
+ $mock_order->expects( $this->once() )
+ ->method( 'update_meta_data' )
+ ->with( '_wcpay_multi_currency_stripe_exchange_rate', $exchange_rate );
+ $mock_order->expects( $this->once() )
+ ->method( 'save_meta_data' );
+
+ // Act.
+ $this->sut->attach_exchange_info_to_order( $this->order_id, $mock_charge );
+ }
+
/**
* Mocks order retrieval.
*
@@ -216,9 +470,9 @@ private function mock_get_order( int $order_id = null ) {
$order_id = $order_id ?? $this->order_id;
$mock_order = $this->createMock( WC_Order::class );
- $this->mock_legacy_proxy->expects( $this->once() )
- ->method( 'call_function' )
- ->with( 'wc_get_order', $order_id )
+ $this->sut->expects( $this->once() )
+ ->method( 'get_order' )
+ ->with( $order_id )
->willReturn( $mock_order );
return $mock_order;
diff --git a/tests/unit/src/Internal/Service/PaymentProcessingServiceTest.php b/tests/unit/src/Internal/Service/PaymentProcessingServiceTest.php
index b5a5b261504..9b993019506 100644
--- a/tests/unit/src/Internal/Service/PaymentProcessingServiceTest.php
+++ b/tests/unit/src/Internal/Service/PaymentProcessingServiceTest.php
@@ -55,12 +55,7 @@ protected function setUp(): void {
$this->mock_legacy_proxy,
]
)
- ->onlyMethods(
- [
- 'create_payment_context',
- 'create_payment_request',
- ]
- )
+ ->onlyMethods( [ 'create_payment_context' ] )
->getMock();
}
@@ -70,7 +65,6 @@ protected function setUp(): void {
public function test_process_payment_happy_path() {
// Prepare all required mocks.
$mock_context = $this->createMock( PaymentContext::class );
- $mock_request = $this->createMock( PaymentRequest::class );
$mock_initial_state = $this->createMock( InitialState::class );
$mock_completed_state = $this->createMock( CompletedState::class );
@@ -80,14 +74,6 @@ public function test_process_payment_happy_path() {
->with( 1 )
->willReturn( $mock_context );
- $this->sut->expects( $this->once() )
- ->method( 'create_payment_request' )
- ->willReturn( $mock_request );
-
- $mock_request->expects( $this->once() )
- ->method( 'populate_context' )
- ->with( $mock_context );
-
$this->mock_state_factory->expects( $this->once() )
->method( 'create_state' )
->with( InitialState::class, $this->isInstanceOf( PaymentContext::class ) )
@@ -95,6 +81,7 @@ public function test_process_payment_happy_path() {
$mock_initial_state->expects( $this->once() )
->method( 'process' )
+ ->with( $this->isInstanceOf( PaymentRequest::class ) )
->willReturn( $mock_completed_state );
$result = $this->sut->process_payment( 1 );
@@ -121,6 +108,7 @@ public function test_process_payment_happy_path_without_mock_builder() {
$mock_initial_state->expects( $this->once() )
->method( 'process' )
+ ->with( $this->isInstanceOf( PaymentRequest::class ) )
->willReturn( $mock_completed_state );
$result = $sut->process_payment( 1 );
diff --git a/tests/unit/src/Internal/Service/PaymentRequestServiceTest.php b/tests/unit/src/Internal/Service/PaymentRequestServiceTest.php
new file mode 100644
index 00000000000..f56fbdfcf46
--- /dev/null
+++ b/tests/unit/src/Internal/Service/PaymentRequestServiceTest.php
@@ -0,0 +1,93 @@
+sut = new PaymentRequestService();
+ }
+
+ public function provider_create_intent() {
+ return [
+ 'With fingerprint' => [ 'fingerprint' ],
+ 'Without fingerprint' => [ null ],
+ ];
+ }
+
+ /**
+ * Tests the method, which creates and confirms intents.
+ *
+ * @dataProvider provider_create_intent
+ */
+ public function test_create_intent( $fingerprint ) {
+ $context_data = [
+ 'get_amount' => 123,
+ 'get_currency' => 'usd',
+ 'get_payment_method' => new NewPaymentMethod( 'pm_XYZ' ),
+ 'get_customer_id' => 'cus_XYZ',
+ 'should_capture_manually' => false,
+ 'get_metadata' => [ 'metadata' ],
+ 'get_level3_data' => [ 'level3data' ],
+ 'get_cvc_confirmation' => 'confirmation',
+ 'get_fingerprint' => $fingerprint,
+ ];
+
+ $request_data = [
+ 'set_amount' => 123,
+ 'set_currency_code' => 'usd',
+ 'set_payment_method' => 'pm_XYZ',
+ 'set_customer' => 'cus_XYZ',
+ 'set_capture_method' => false, // No manual capture.
+ 'set_metadata' => [ 'metadata' ],
+ 'set_level3' => [ 'level3data' ],
+ 'set_payment_methods' => [ 'card' ],
+ 'set_cvc_confirmation' => 'confirmation',
+ 'set_fingerprint' => $fingerprint ?? '',
+ ];
+
+ $context = $this->createMock( PaymentContext::class );
+ $intent = $this->createMock( WC_Payments_API_Payment_Intention::class );
+ $request = $this->mock_wcpay_request( Create_And_Confirm_Intention::class );
+
+ foreach ( $context_data as $method => $value ) {
+ $context->expects( $this->once() )->method( $method )->willReturn( $value );
+ }
+
+ foreach ( $request_data as $method => $value ) {
+ $request->expects( $this->once() )->method( $method )->with( $value );
+ }
+ $request->expects( $this->once() )
+ ->method( 'format_response' )
+ ->willReturn( $intent );
+
+ $result = $this->sut->create_intent( $context );
+ $this->assertSame( $intent, $result );
+ }
+}
From 7176d57023fa1f6c69a9134584ee7b821a3699b7 Mon Sep 17 00:00:00 2001
From: Cvetan Cvetanov
Date: Fri, 6 Oct 2023 15:06:31 +0300
Subject: [PATCH 89/98] Update JCB request capability docs link and capitalize
it on the transaction details page (#7404)
---
changelog/fix-finish-setup-link | 4 ++++
client/payment-details/payment-method/card/index.js | 4 +++-
.../capability-request/capability-request-map.ts | 2 +-
3 files changed, 8 insertions(+), 2 deletions(-)
create mode 100644 changelog/fix-finish-setup-link
diff --git a/changelog/fix-finish-setup-link b/changelog/fix-finish-setup-link
new file mode 100644
index 00000000000..b4acb564d44
--- /dev/null
+++ b/changelog/fix-finish-setup-link
@@ -0,0 +1,4 @@
+Significance: minor
+Type: dev
+
+Capitalize the JCB label on transactions details page.
diff --git a/client/payment-details/payment-method/card/index.js b/client/payment-details/payment-method/card/index.js
index 718e536e092..15893f0e299 100644
--- a/client/payment-details/payment-method/card/index.js
+++ b/client/payment-details/payment-method/card/index.js
@@ -52,7 +52,9 @@ const formatPaymentMethodDetails = ( charge ) => {
? sprintf(
// Translators: %1$s card brand, %2$s card funding (prepaid, credit, etc.).
__( '%1$s %2$s card', 'woocommerce-payments' ),
- network.charAt( 0 ).toUpperCase() + network.slice( 1 ), // Brand
+ network === 'jcb'
+ ? network.toUpperCase()
+ : network.charAt( 0 ).toUpperCase() + network.slice( 1 ), // Brand
fundingTypes[ funding ]
)
: undefined;
diff --git a/client/payment-methods/capability-request/capability-request-map.ts b/client/payment-methods/capability-request/capability-request-map.ts
index 86a91673dd0..3c2cceebce6 100644
--- a/client/payment-methods/capability-request/capability-request-map.ts
+++ b/client/payment-methods/capability-request/capability-request-map.ts
@@ -27,7 +27,7 @@ const CapabilityRequestList: Array< CapabilityRequestMap > = [
),
actions: 'link',
actionUrl:
- 'https://woocommerce.com/document/woopayments/payment-methods/',
+ 'https://woocommerce.com/document/woopayments/payment-methods/#jcb',
actionsLabel: __( 'Finish setup', 'woocommerce-payments' ),
},
pending: {
From 74c9136e16ae2c83ead016a00d0ed3e06bd722a2 Mon Sep 17 00:00:00 2001
From: Miguel Gasca
Date: Fri, 6 Oct 2023 17:12:16 +0200
Subject: [PATCH 90/98] Enhance design of bnpl payment methods status in
settings screen (#7406)
---
...ance-design-of-bnpl-payment-methods-status | 4 +
client/components/loadable-checkbox/index.js | 18 +--
.../payment-methods-list/payment-method.scss | 15 +-
.../payment-methods-list/payment-method.tsx | 131 ++++++++++++------
.../payment-methods-list/style.scss | 7 +
5 files changed, 109 insertions(+), 66 deletions(-)
create mode 100644 changelog/update-6914-enhance-design-of-bnpl-payment-methods-status
diff --git a/changelog/update-6914-enhance-design-of-bnpl-payment-methods-status b/changelog/update-6914-enhance-design-of-bnpl-payment-methods-status
new file mode 100644
index 00000000000..f0dd71a9eb8
--- /dev/null
+++ b/changelog/update-6914-enhance-design-of-bnpl-payment-methods-status
@@ -0,0 +1,4 @@
+Significance: patch
+Type: update
+
+Enhance design of bnpl payment methods status in settings screen
diff --git a/client/components/loadable-checkbox/index.js b/client/components/loadable-checkbox/index.js
index 31ac7c60d70..8df3c49f8fe 100644
--- a/client/components/loadable-checkbox/index.js
+++ b/client/components/loadable-checkbox/index.js
@@ -27,6 +27,7 @@ const LoadableCheckboxControl = ( {
setupTooltip = '',
delayMsOnCheck = 0,
delayMsOnUncheck = 0,
+ needsAttention = false,
} ) => {
const [ isLoading, setLoading ] = useState( false );
const [ checkedState, setCheckedState ] = useState( checked );
@@ -87,24 +88,15 @@ const LoadableCheckboxControl = ( {
) }
{ ( isManualCaptureEnabled && ! isAllowingManualCapture ) ||
- isSetupRequired ? (
+ isSetupRequired ||
+ needsAttention ? (
{
return (
<>
@@ -65,43 +65,28 @@ const PaymentMethodLabel = ( {
) }
{ upeCapabilityStatuses.PENDING_APPROVAL === status && (
-
-
- { __( 'Pending approval', 'woocommerce-payments' ) }
-
-
+
) }
{ upeCapabilityStatuses.PENDING_VERIFICATION === status && (
-
-
- { __( 'Pending activation', 'woocommerce-payments' ) }
-
-
+ type="warning"
+ />
) }
{ disabled && (
-
-
- { __(
- 'More information needed',
- 'woocommerce-payments'
- ) }
-
-
+
) }
>
);
@@ -136,9 +121,16 @@ const PaymentMethod = ( {
);
const [ isManualCaptureEnabled ] = useManualCapture();
+ const needsAttention = [
+ upeCapabilityStatuses.INACTIVE,
+ upeCapabilityStatuses.PENDING_APPROVAL,
+ upeCapabilityStatuses.PENDING_VERIFICATION,
+ ].includes( status );
+
const needsOverlay =
( isManualCaptureEnabled && ! isAllowingManualCapture ) ||
- isSetupRequired;
+ isSetupRequired ||
+ needsAttention;
const handleChange = ( newStatus: string ) => {
// If the payment method control is locked, reject any changes.
@@ -152,6 +144,68 @@ const PaymentMethod = ( {
return onUncheckClick( id );
};
+ const getTooltipContent = ( paymentMethodId: string ) => {
+ if ( upeCapabilityStatuses.PENDING_APPROVAL === status ) {
+ return __(
+ 'This payment method is pending approval. Once approved, you will be able to use it.',
+ 'woocommerce-payments'
+ );
+ }
+
+ if ( upeCapabilityStatuses.PENDING_VERIFICATION === status ) {
+ return sprintf(
+ __(
+ "%s won't be visible to your customers until you provide the required " +
+ 'information. Follow the instructions sent by our partner Stripe to %s.',
+ 'woocommerce-payments'
+ ),
+ label,
+ wcpaySettings?.accountEmail ?? ''
+ );
+ }
+
+ if ( isSetupRequired ) {
+ return setupTooltip;
+ }
+
+ if ( needsAttention ) {
+ return interpolateComponents( {
+ // translators: {{learnMoreLink}}: placeholders are opening and closing anchor tags.
+ mixedString: __(
+ 'We need more information from you to enable this method. ' +
+ '{{learnMoreLink}}Learn more.{{/learnMoreLink}}',
+ 'woocommerce-payments'
+ ),
+ components: {
+ learnMoreLink: (
+ // eslint-disable-next-line jsx-a11y/anchor-has-content
+
+ ),
+ },
+ } );
+ }
+
+ return sprintf(
+ /* translators: %s: a payment method name. */
+ __(
+ '%s is not available to your customers when the "manual capture" setting is enabled.',
+ 'woocommerce-payments'
+ ),
+ label
+ );
+ };
+
return (
@@ -185,7 +240,6 @@ const PaymentMethod = ( {
required={ required }
status={ status }
disabled={ disabled }
- id={ id }
/>
@@ -196,7 +250,6 @@ const PaymentMethod = ( {
required={ required }
status={ status }
disabled={ disabled }
- id={ id }
/>
diff --git a/client/components/payment-methods-list/style.scss b/client/components/payment-methods-list/style.scss
index 8c4e140dee5..7cf4f570e85 100644
--- a/client/components/payment-methods-list/style.scss
+++ b/client/components/payment-methods-list/style.scss
@@ -5,3 +5,10 @@
.woopayments-request-jcb {
margin: $grid-unit-30 $grid-unit-30 0 $grid-unit-30 !important;
}
+
+.wcpay-tooltip__tooltip {
+ &.wcpay-tooltip__tooltip--dark a {
+ color: $white;
+ text-decoration: underline;
+ }
+}
From c605942fdeab773009eea32a4fbc6396e6762e1e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?K=C4=81rlis=20Janisels?=
Date: Fri, 6 Oct 2023 20:08:48 +0300
Subject: [PATCH 91/98] Point Klarna documentation to correct url from 'Contact
WooCommerce Support' badge (#7410)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Kārlis Janisels
---
changelog/fix-correct-documentation-url-for-klarna | 5 +++++
client/components/payment-method-disabled-tooltip/index.tsx | 1 +
2 files changed, 6 insertions(+)
create mode 100644 changelog/fix-correct-documentation-url-for-klarna
diff --git a/changelog/fix-correct-documentation-url-for-klarna b/changelog/fix-correct-documentation-url-for-klarna
new file mode 100644
index 00000000000..3092cfe2b34
--- /dev/null
+++ b/changelog/fix-correct-documentation-url-for-klarna
@@ -0,0 +1,5 @@
+Significance: patch
+Type: fix
+Comment: Adding missed functionality from another PR
+
+
diff --git a/client/components/payment-method-disabled-tooltip/index.tsx b/client/components/payment-method-disabled-tooltip/index.tsx
index 8df2e14f594..a2b169bf98c 100644
--- a/client/components/payment-method-disabled-tooltip/index.tsx
+++ b/client/components/payment-method-disabled-tooltip/index.tsx
@@ -26,6 +26,7 @@ export const getDocumentationUrlForDisabledPaymentMethod = (
switch ( paymentMethodId ) {
case PAYMENT_METHOD_IDS.AFTERPAY_CLEARPAY:
case PAYMENT_METHOD_IDS.AFFIRM:
+ case PAYMENT_METHOD_IDS.KLARNA:
url = DocumentationUrlForDisabledPaymentMethod.BNPLS;
break;
default:
From 74d9de8c385b8e24189efbdbfb04aba134d1a099 Mon Sep 17 00:00:00 2001
From: Ricardo Metring
Date: Fri, 6 Oct 2023 14:33:55 -0300
Subject: [PATCH 92/98] Implement loading state to WooPay express checkout
button (#7366)
---
.../add-7295-loading-state-to-woopay-button | 4 ++
.../woopay-express-checkout-button.js | 38 +++++++++++++++++--
client/checkout/woopay/style.scss | 17 +++++++--
3 files changed, 53 insertions(+), 6 deletions(-)
create mode 100644 changelog/add-7295-loading-state-to-woopay-button
diff --git a/changelog/add-7295-loading-state-to-woopay-button b/changelog/add-7295-loading-state-to-woopay-button
new file mode 100644
index 00000000000..7295b647d79
--- /dev/null
+++ b/changelog/add-7295-loading-state-to-woopay-button
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Add loading state to WooPay button
diff --git a/client/checkout/woopay/express-button/woopay-express-checkout-button.js b/client/checkout/woopay/express-button/woopay-express-checkout-button.js
index 291dba45df8..ea53795faf4 100644
--- a/client/checkout/woopay/express-button/woopay-express-checkout-button.js
+++ b/client/checkout/woopay/express-button/woopay-express-checkout-button.js
@@ -3,6 +3,7 @@
*/
import { sprintf, __ } from '@wordpress/i18n';
import React, { useCallback, useEffect, useState, useRef } from 'react';
+import classNames from 'classnames';
/**
* Internal dependencies
@@ -36,6 +37,7 @@ export const WoopayExpressCheckoutButton = ( {
const buttonRef = useRef( null );
const isLoadingRef = useRef( false );
const { type: buttonType, height, size, theme, context } = buttonSettings;
+ const [ isLoading, setIsLoading ] = useState( false );
const [ buttonWidthType, setButtonWidthType ] = useState(
buttonWidthTypes.wide
);
@@ -183,6 +185,7 @@ export const WoopayExpressCheckoutButton = ( {
// Set isLoadingRef to true to prevent multiple clicks.
isLoadingRef.current = true;
+ setIsLoading( true );
wcpayTracks.recordUserEvent(
wcpayTracks.events.WOOPAY_BUTTON_CLICK,
@@ -234,6 +237,7 @@ export const WoopayExpressCheckoutButton = ( {
);
showErrorMessage( context, errorMessage );
isLoadingRef.current = false;
+ setIsLoading( false );
} );
} );
} else {
@@ -255,6 +259,7 @@ export const WoopayExpressCheckoutButton = ( {
);
showErrorMessage( context, errorMessage );
isLoadingRef.current = false;
+ setIsLoading( false );
} );
}
};
@@ -307,6 +312,7 @@ export const WoopayExpressCheckoutButton = ( {
// Set button's default onClick handle to use modal checkout flow.
initWoopayRef.current = defaultOnClick;
isLoadingRef.current = false;
+ setIsLoading( false );
}
};
@@ -315,6 +321,7 @@ export const WoopayExpressCheckoutButton = ( {
return () => {
window.removeEventListener( 'message', onMessage );
};
+ // Note: Any changes to this dependency array may cause a duplicate iframe to be appended.
}, [ context, defaultOnClick, isPreview, isProductPage, newIframe ] );
useEffect( () => {
@@ -322,21 +329,46 @@ export const WoopayExpressCheckoutButton = ( {
initWoopayRef.current = defaultOnClick;
}, [ defaultOnClick ] );
+ useEffect( () => {
+ const handlePageShow = ( event ) => {
+ // Re-enable the button after navigating back/forward to the page if bfcache is used.
+ if ( event?.persisted ) {
+ isLoadingRef.current = false;
+ setIsLoading( false );
+ }
+ };
+
+ window.addEventListener( 'pageshow', handlePageShow );
+
+ return () => {
+ window.removeEventListener( 'pageshow', handlePageShow );
+ };
+ }, [] );
+
return (
);
};
diff --git a/client/checkout/woopay/style.scss b/client/checkout/woopay/style.scss
index 79cff325e79..97f6a4ff3c2 100644
--- a/client/checkout/woopay/style.scss
+++ b/client/checkout/woopay/style.scss
@@ -142,11 +142,22 @@
white-space: nowrap;
text-transform: none;
- &:hover {
+ &:not( :disabled ):hover {
background: #d9baff !important;
cursor: pointer;
}
+ &:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ }
+
+ &.is-loading,
+ &.is-loading:hover,
+ &.is-loading:disabled {
+ opacity: 1 !important;
+ }
+
svg {
fill: $studio-woocommerce-purple-60;
position: relative;
@@ -179,14 +190,14 @@
svg {
fill: $white;
}
- &:hover {
+ &:not( :disabled ):hover {
background: #533582 !important;
}
}
&[data-theme='light-outline'] {
border-color: #674399 !important;
- &:hover {
+ &:not( :disabled ):hover {
background: #d9baff !important;
}
}
From 193d40998e298e22127c5d7958763ba62c613091 Mon Sep 17 00:00:00 2001
From: Ricardo Metring
Date: Fri, 6 Oct 2023 19:04:36 -0300
Subject: [PATCH 93/98] Add backup loading spinner style from WC blocks to
WooPay button (#7432)
---
.../fix-add-wc-blocks-loading-spinner-css | 4 ++
client/checkout/woopay/style.scss | 47 +++++++++++++++++++
2 files changed, 51 insertions(+)
create mode 100644 changelog/fix-add-wc-blocks-loading-spinner-css
diff --git a/changelog/fix-add-wc-blocks-loading-spinner-css b/changelog/fix-add-wc-blocks-loading-spinner-css
new file mode 100644
index 00000000000..d3fbea23ade
--- /dev/null
+++ b/changelog/fix-add-wc-blocks-loading-spinner-css
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Add WC blocks spinner to the WooPay checkout styles.
diff --git a/client/checkout/woopay/style.scss b/client/checkout/woopay/style.scss
index 97f6a4ff3c2..67b212331e3 100644
--- a/client/checkout/woopay/style.scss
+++ b/client/checkout/woopay/style.scss
@@ -270,3 +270,50 @@
}
}
}
+
+@keyframes spinner__animation {
+ 0% {
+ animation-timing-function: cubic-bezier(
+ 0.5856,
+ 0.0703,
+ 0.4143,
+ 0.9297
+ );
+ transform: rotate( 0deg );
+ }
+ 100% {
+ transform: rotate( 360deg );
+ }
+}
+
+/**
+ * Sourced from https://github.com/woocommerce/woocommerce-blocks/blob/4dfe904f761423c1ac494f0d6319c602965b5efe/assets/js/base/components/spinner/style.scss.
+ * Depending on the wc-blocks version, these styles are not loaded, so they need to be included here.
+ */
+.wc-block-components-spinner {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ color: inherit;
+ box-sizing: content-box;
+ text-align: center;
+ font-size: 1.25em;
+
+ &::after {
+ content: ' ';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ margin: -0.5em 0 0 -0.5em;
+ width: 1em;
+ height: 1em;
+ box-sizing: border-box;
+ transform-origin: 50% 50%;
+ transform: translateZ( 0 ) scale( 0.5 );
+ backface-visibility: hidden;
+ border-radius: 50%;
+ border: 0.2em solid currentColor;
+ border-left-color: transparent;
+ animation: spinner__animation 1s infinite linear;
+ }
+}
From c1f7b63d4102703841099f5e2234b720e431a483 Mon Sep 17 00:00:00 2001
From: Alefe Souza
Date: Sat, 7 Oct 2023 00:43:54 -0300
Subject: [PATCH 94/98] Show survey for merchants that disable WooPay (#7401)
Co-authored-by: Malith Senaweera <6216000+malithsen@users.noreply.github.com>
---
.../add-show-survey-users-disable-woopay | 4 ++
.../settings/save-settings-section/index.js | 61 ++++++++++++++++++-
.../settings/woopay-disable-feedback/index.js | 50 +++++++++++++++
.../woopay-disable-feedback/style.scss | 23 +++++++
includes/admin/class-wc-payments-admin.php | 1 +
includes/class-wc-payment-gateway-wcpay.php | 4 ++
6 files changed, 141 insertions(+), 2 deletions(-)
create mode 100644 changelog/add-show-survey-users-disable-woopay
create mode 100644 client/settings/woopay-disable-feedback/index.js
create mode 100644 client/settings/woopay-disable-feedback/style.scss
diff --git a/changelog/add-show-survey-users-disable-woopay b/changelog/add-show-survey-users-disable-woopay
new file mode 100644
index 00000000000..edfd7514b8b
--- /dev/null
+++ b/changelog/add-show-survey-users-disable-woopay
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Show survey for merchants that disable WooPay.
diff --git a/client/settings/save-settings-section/index.js b/client/settings/save-settings-section/index.js
index 09f4aecf24b..63673f873ea 100644
--- a/client/settings/save-settings-section/index.js
+++ b/client/settings/save-settings-section/index.js
@@ -13,6 +13,7 @@ import { useSettings } from '../../data';
import wcpayTracks from '../../tracks';
import SettingsSection from '../settings-section';
import './style.scss';
+import WooPayDisableFeedback from '../woopay-disable-feedback';
const SaveSettingsSection = ( { disabled = false } ) => {
const { saveSettings, isSaving, isLoading, settings } = useSettings();
@@ -23,6 +24,15 @@ const SaveSettingsSection = ( { disabled = false } ) => {
initialIsPaymentRequestEnabled,
setInitialIsPaymentRequestEnabled,
] = useState( null );
+ // Keep the inital value of is_woopay_enabled
+ // in state for showing the feedback modal on change.
+ const [ initialIsWooPayEnabled, setInitialIsWooPayEnabled ] = useState(
+ null
+ );
+ const [
+ isWooPayDisableFeedbackOpen,
+ setIsWooPayDisableFeedbackOpen,
+ ] = useState( false );
if (
initialIsPaymentRequestEnabled === null &&
@@ -34,15 +44,26 @@ const SaveSettingsSection = ( { disabled = false } ) => {
);
}
+ if (
+ initialIsWooPayEnabled === null &&
+ settings &&
+ typeof settings.is_woopay_enabled !== 'undefined'
+ ) {
+ setInitialIsWooPayEnabled( settings.is_woopay_enabled );
+ }
+
const saveOnClick = async () => {
const isSuccess = await saveSettings();
+ if ( ! isSuccess ) {
+ return;
+ }
+
// Track the event when the value changed and the
// settings were successfully saved.
if (
- isSuccess &&
initialIsPaymentRequestEnabled !==
- settings.is_payment_request_enabled
+ settings.is_payment_request_enabled
) {
wcpayTracks.recordEvent(
wcpayTracks.events.PAYMENT_REQUEST_SETTINGS_CHANGE,
@@ -56,6 +77,35 @@ const SaveSettingsSection = ( { disabled = false } ) => {
settings.is_payment_request_enabled
);
}
+
+ // Show the feedback modal when WooPay is disabled.
+ if ( initialIsWooPayEnabled && ! settings.is_woopay_enabled ) {
+ const { woopayLastDisableDate } = wcpaySettings;
+
+ // Do not show feedback modal if WooPay
+ // was disabled in the last 7 days.
+ if ( woopayLastDisableDate ) {
+ const date1 = new Date( woopayLastDisableDate );
+ const date2 = new Date();
+ const diffTime = Math.abs( date2 - date1 );
+ const diffDays = Math.ceil(
+ diffTime / ( 1000 * 60 * 60 * 24 )
+ );
+
+ if ( diffDays < 7 ) {
+ return;
+ }
+ }
+
+ setIsWooPayDisableFeedbackOpen( true );
+
+ // Prevent show modal again.
+ setInitialIsPaymentRequestEnabled( true );
+ // Set last disable date to prevent feedback window opening up
+ // on successive "Save button" clicks. This value is overwritten
+ // on page refresh.
+ wcpaySettings.woopayLastDisableDate = new Date();
+ }
};
return (
@@ -68,6 +118,13 @@ const SaveSettingsSection = ( { disabled = false } ) => {
>
{ __( 'Save changes', 'woocommerce-payments' ) }
+ { isWooPayDisableFeedbackOpen ? (
+
+ setIsWooPayDisableFeedbackOpen( false )
+ }
+ />
+ ) : null }
);
};
diff --git a/client/settings/woopay-disable-feedback/index.js b/client/settings/woopay-disable-feedback/index.js
new file mode 100644
index 00000000000..a46f093e888
--- /dev/null
+++ b/client/settings/woopay-disable-feedback/index.js
@@ -0,0 +1,50 @@
+/**
+ * External dependencies
+ */
+import React, { useState } from 'react';
+import { __ } from '@wordpress/i18n';
+import { Modal } from '@wordpress/components';
+
+/**
+ * Internal dependencies
+ */
+import './style.scss';
+import Loadable from 'wcpay/components/loadable';
+import WooPayIcon from 'assets/images/woopay.svg?asset';
+
+const WooPayDisableFeedback = ( { onRequestClose } ) => {
+ const [ isLoading, setIsLoading ] = useState( true );
+
+ return (
+
+ }
+ isDismissible={ true }
+ shouldCloseOnClickOutside={ false } // Should be false because of the iframe.
+ shouldCloseOnEsc={ true }
+ onRequestClose={ onRequestClose }
+ className="woopay-disable-feedback"
+ >
+
+
+
+ );
+};
+
+export default WooPayDisableFeedback;
diff --git a/client/settings/woopay-disable-feedback/style.scss b/client/settings/woopay-disable-feedback/style.scss
new file mode 100644
index 00000000000..08580261c61
--- /dev/null
+++ b/client/settings/woopay-disable-feedback/style.scss
@@ -0,0 +1,23 @@
+.woopay-disable-feedback {
+ @media ( min-width: 960px ) {
+ max-height: calc( 100% - 120px );
+ }
+
+ .components-modal__content {
+ padding: 0;
+ }
+
+ &-iframe {
+ width: 100%;
+ height: 100%;
+
+ @media ( min-width: 600px ) {
+ width: 600px;
+ height: 650px;
+ }
+ }
+
+ &-logo {
+ height: 40px;
+ }
+}
diff --git a/includes/admin/class-wc-payments-admin.php b/includes/admin/class-wc-payments-admin.php
index e77918581d6..2e2f309a4fb 100644
--- a/includes/admin/class-wc-payments-admin.php
+++ b/includes/admin/class-wc-payments-admin.php
@@ -854,6 +854,7 @@ private function get_js_settings(): array {
'storeCurrency' => get_option( 'woocommerce_currency' ),
'isBnplAffirmAfterpayEnabled' => WC_Payments_Features::is_bnpl_affirm_afterpay_enabled(),
'isWooPayStoreCountryAvailable' => WooPay_Utilities::is_store_country_available(),
+ 'woopayLastDisableDate' => $this->wcpay_gateway->get_option( 'platform_checkout_last_disable_date' ),
'isStripeBillingEnabled' => WC_Payments_Features::is_stripe_billing_enabled(),
'isStripeBillingEligible' => WC_Payments_Features::is_stripe_billing_eligible(),
'capabilityRequestNotices' => get_option( 'wcpay_capability_request_dismissed_notices ', [] ),
diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php
index e294975ac75..cc33a732379 100644
--- a/includes/class-wc-payment-gateway-wcpay.php
+++ b/includes/class-wc-payment-gateway-wcpay.php
@@ -2035,6 +2035,10 @@ public function update_is_woopay_enabled( $is_woopay_enabled ) {
$this->update_option( 'platform_checkout', $is_woopay_enabled ? 'yes' : 'no' );
+ if ( ! $is_woopay_enabled ) {
+ $this->update_option( 'platform_checkout_last_disable_date', gmdate( 'Y-m-d' ) );
+ }
+
if ( ! $is_woopay_enabled ) {
WooPay_Order_Status_Sync::remove_webhook();
} elseif ( WC_Payments_Features::is_upe_legacy_enabled() ) {
From 703835a096cb03ed8b7595b9d13059d1dabae1d9 Mon Sep 17 00:00:00 2001
From: bruce aldridge
Date: Mon, 9 Oct 2023 00:03:03 +1300
Subject: [PATCH 95/98] Disputes: Use existing tracks events where possible
(#7402)
Co-authored-by: Shendy <73803630+shendy-a8c@users.noreply.github.com>
---
changelog/fix-7395 | 5 +++++
.../dispute-awaiting-response-details.tsx | 6 +++++-
.../dispute-details/dispute-resolution-footer.tsx | 15 ++++++++++-----
client/tracks/index.js | 6 +++---
4 files changed, 23 insertions(+), 9 deletions(-)
create mode 100644 changelog/fix-7395
diff --git a/changelog/fix-7395 b/changelog/fix-7395
new file mode 100644
index 00000000000..9d6115e20b2
--- /dev/null
+++ b/changelog/fix-7395
@@ -0,0 +1,5 @@
+Significance: patch
+Type: fix
+Comment: Updating tracks events
+
+
diff --git a/client/payment-details/dispute-details/dispute-awaiting-response-details.tsx b/client/payment-details/dispute-details/dispute-awaiting-response-details.tsx
index c4877d7030b..fe3aa20c942 100644
--- a/client/payment-details/dispute-details/dispute-awaiting-response-details.tsx
+++ b/client/payment-details/dispute-details/dispute-awaiting-response-details.tsx
@@ -255,9 +255,10 @@ const DisputeAwaitingResponseDetails: React.FC< Props > = ( {
onClick={ () => {
wcpayTracks.recordEvent(
wcpayTracks.events
- .DISPUTE_CHALLENGE_CLICK,
+ .DISPUTE_CHALLENGE_CLICKED,
{
dispute_status: dispute.status,
+ on_page: 'transaction_details',
}
);
} }
@@ -279,6 +280,7 @@ const DisputeAwaitingResponseDetails: React.FC< Props > = ( {
disputeAcceptAction.acceptButtonTracksEvent,
{
dispute_status: dispute.status,
+ on_page: 'transaction_details',
}
);
setModalOpen( true );
@@ -337,6 +339,8 @@ const DisputeAwaitingResponseDetails: React.FC< Props > = ( {
{
dispute_status:
dispute.status,
+ on_page:
+ 'transaction_details',
}
);
setModalOpen( false );
diff --git a/client/payment-details/dispute-details/dispute-resolution-footer.tsx b/client/payment-details/dispute-details/dispute-resolution-footer.tsx
index 163956f7a3b..92d7e0a987c 100644
--- a/client/payment-details/dispute-details/dispute-resolution-footer.tsx
+++ b/client/payment-details/dispute-details/dispute-resolution-footer.tsx
@@ -70,9 +70,10 @@ const DisputeUnderReviewFooter: React.FC< {
onClick={ () => {
wcpayTracks.recordEvent(
wcpayTracks.events
- .PAYMENT_DETAILS_VIEW_DISPUTE_EVIDENCE_BUTTON_CLICK,
+ .PAYMENT_DETAILS_VIEW_DISPUTE_EVIDENCE_BUTTON_CLICKED,
{
dispute_status: dispute.status,
+ on_page: 'transaction_details',
}
);
} }
@@ -141,9 +142,10 @@ const DisputeWonFooter: React.FC< {
onClick={ () => {
wcpayTracks.recordEvent(
wcpayTracks.events
- .PAYMENT_DETAILS_VIEW_DISPUTE_EVIDENCE_BUTTON_CLICK,
+ .PAYMENT_DETAILS_VIEW_DISPUTE_EVIDENCE_BUTTON_CLICKED,
{
dispute_status: dispute.status,
+ on_page: 'transaction_details',
}
);
} }
@@ -250,9 +252,10 @@ const DisputeLostFooter: React.FC< {
onClick={ () => {
wcpayTracks.recordEvent(
wcpayTracks.events
- .PAYMENT_DETAILS_VIEW_DISPUTE_EVIDENCE_BUTTON_CLICK,
+ .PAYMENT_DETAILS_VIEW_DISPUTE_EVIDENCE_BUTTON_CLICKED,
{
dispute_status: dispute.status,
+ on_page: 'transaction_details',
}
);
} }
@@ -322,9 +325,10 @@ const InquiryUnderReviewFooter: React.FC< {
onClick={ () => {
wcpayTracks.recordEvent(
wcpayTracks.events
- .PAYMENT_DETAILS_VIEW_DISPUTE_EVIDENCE_BUTTON_CLICK,
+ .PAYMENT_DETAILS_VIEW_DISPUTE_EVIDENCE_BUTTON_CLICKED,
{
dispute_status: dispute.status,
+ on_page: 'transaction_details',
}
);
} }
@@ -396,9 +400,10 @@ const InquiryClosedFooter: React.FC< {
onClick={ () => {
wcpayTracks.recordEvent(
wcpayTracks.events
- .PAYMENT_DETAILS_VIEW_DISPUTE_EVIDENCE_BUTTON_CLICK,
+ .PAYMENT_DETAILS_VIEW_DISPUTE_EVIDENCE_BUTTON_CLICKED,
{
dispute_status: dispute.status,
+ on_page: 'transaction_details',
}
);
} }
diff --git a/client/tracks/index.js b/client/tracks/index.js
index 4b065bceefd..4dc253734ec 100644
--- a/client/tracks/index.js
+++ b/client/tracks/index.js
@@ -68,7 +68,7 @@ const events = {
DEPOSITS_ROW_CLICK: 'wcpay_deposits_row_click',
DEPOSITS_DOWNLOAD_CSV_CLICK: 'wcpay_deposits_download',
DISPUTES_ROW_ACTION_CLICK: 'wcpay_disputes_row_action_click',
- DISPUTE_CHALLENGE_CLICK: 'wcpay_dispute_challenge_click',
+ DISPUTE_CHALLENGE_CLICKED: 'wcpay_dispute_challenge_clicked',
DISPUTE_ACCEPT_CLICK: 'wcpay_dispute_accept_click',
DISPUTE_ACCEPT_MODAL_VIEW: 'wcpay_dispute_accept_modal_view',
DISPUTE_INQUIRY_REFUND_CLICK: 'wcpay_dispute_inquiry_refund_click',
@@ -83,8 +83,8 @@ const events = {
OVERVIEW_DEPOSITS_CHANGE_SCHEDULE_CLICK:
'wcpay_overview_deposits_change_schedule_click',
OVERVIEW_TASK_CLICK: 'wcpay_overview_task_click',
- PAYMENT_DETAILS_VIEW_DISPUTE_EVIDENCE_BUTTON_CLICK:
- 'wcpay_payment_details_view_dispute_evidence_button_click',
+ PAYMENT_DETAILS_VIEW_DISPUTE_EVIDENCE_BUTTON_CLICKED:
+ 'wcpay_view_submitted_evidence_clicked',
SETTINGS_DEPOSITS_MANAGE_IN_STRIPE_CLICK:
'wcpay_settings_deposits_manage_in_stripe_click',
MULTI_CURRENCY_ENABLED_CURRENCIES_UPDATED:
From 91d6dd86c6794908afd93985648dcd429b0fde7a Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Sun, 8 Oct 2023 12:07:49 +0000
Subject: [PATCH 96/98] Update version and add changelog entries for release
6.6.0
---
changelog.txt | 78 ++++++++++++++++++
.../add-2094-pass-checkout-data-before-auth | 4 -
...add-6203-check-for-order-status-on-capture | 4 -
changelog/add-6925-steps-to-resolve | 5 --
...6927-dispute-details-not-awaiting-response | 4 -
changelog/add-7122-more-details-to-woo-ssr | 4 -
changelog/add-7193-dispute-cta-for-inquries | 5 --
changelog/add-7243-introduce-klarna-pm | 4 -
...ansaction-details-inquiry-steps-to-resolve | 5 --
...e-transaction-dispute-details-feature-flag | 4 -
.../add-7295-loading-state-to-woopay-button | 4 -
changelog/add-7322-saved-payment-methods-api | 4 -
...nd-merchant-fingerprint-data-on-onboarding | 4 -
...-api-documentation-for-payment-methods-api | 4 -
.../add-7371-dispute-details-error-boundary | 5 --
...ion-for-create-payment-intent-api-endpoint | 4 -
changelog/add-constructor-hooks-sniffer | 4 -
...entation-for-cancel-authorization-endpoint | 4 -
...-feature-flag-check-for-pay-for-order-flow | 4 -
changelog/add-jcb-capability-request | 4 -
changelog/add-settings-save-error-notice | 4 -
.../add-show-survey-users-disable-woopay | 4 -
...-6214-e2e-tests-for-progressive-onboarding | 4 -
...move-hooks-from-multi-currency-constructor | 4 -
...ve-hooks-from-fraud-services-constructor-2 | 4 -
.../dev-7262-remove-hooks-from-constructors | 4 -
changelog/dev-7285-test-payment-method-title | 5 --
changelog/feat-handle-server-side-dupe-flag | 4 -
...eature-enable-sepa-for-deferred-intent-upe | 4 -
changelog/fix-2141-3 | 4 -
...disable-currency-switcher-on-pay-for-order | 4 -
.../fix-5524-price-conversion-helper-function | 4 -
...fix-5548-modal-header-alignement-on-safari | 4 -
changelog/fix-5945-add-default-cc-enabled | 4 -
changelog/fix-6182-hide-storefront-tooltip | 4 -
changelog/fix-6466-mc-errors | 4 -
.../fix-6714-request-button-virtual-variable | 4 -
...819-payment-method-messaging-invalid-value | 4 -
...891-redirect-dispute-detail-to-transaction | 4 -
...x-6926-transaction-dispute-details-actions | 5 --
...move-transaction-details-view-dispute-link | 5 --
...pute-details-links-to-transactions-details | 4 -
...filter-name-from-request-class-send-method | 4 -
changelog/fix-7151-multi-currency-onboarding | 4 -
changelog/fix-7172-nyp-cart-editing | 4 -
...x-7191-dispute-resolution-footer-inquiries | 5 --
...te-details-when-awaiting-response-past-due | 5 --
...e-dispute-docs-links-in-transaction-detail | 4 -
changelog/fix-7254-update-dispute-fee-text | 5 --
.../fix-7303-disable-wcpay-with-core-disabled | 4 -
.../fix-7329-missing-multi-currency-check | 4 -
...ge-for-affirm-and-afterpay-payment-methods | 4 -
.../fix-7357-multiple-woopay-session-requests | 4 -
...-7360-non-connected-onboarding-flow-access | 4 -
...73-show-dispute-amount-in-shopper-currency | 5 --
changelog/fix-7395 | 5 --
changelog/fix-7396-disputestatuschip | 5 --
.../fix-add-wc-blocks-loading-spinner-css | 4 -
...d-woopay-woocommerce-mix-and-match-support | 4 -
changelog/fix-bump-min-php-wc-beta | 3 -
.../fix-correct-documentation-url-for-klarna | 5 --
...dd-missing-sidebar-links-for-intent-and-pm | 4 -
changelog/fix-finish-setup-link | 4 -
...-on-product-woopay-express-checkout-button | 4 -
.../fix-improve-escaping-around-attributes | 4 -
changelog/fix-init-woopay-error | 4 -
changelog/fix-optimise-capture-charge-calls | 4 -
.../fix-redundant-webhooks-modifications | 4 -
changelog/fix-save-customer-info-checkout | 4 -
...x-subscription-migration-in-progress-check | 5 --
changelog/fix-tracking-conditions | 4 -
changelog/fix-woopay-billing-phone-fallback | 4 -
changelog/fix-woopay-multiple-redirects | 4 -
.../issue-6510-deprecate-wcpay-subscriptions | 4 -
changelog/rpp-6686-payments-skeleton | 5 --
changelog/rpp-6688-working-data | 5 --
changelog/rpp-basic-process | 5 --
changelog/rpp-container-exceptions | 5 --
changelog/rpp-extract-gateway-methods | 4 -
...-billing-deactivate-plugin-warning-updates | 4 -
changelog/subscriptions-core-6.3.0 | 4 -
changelog/subscriptions-core-6.3.0-1 | 4 -
changelog/subscriptions-core-6.3.0-2 | 4 -
changelog/subscriptions-core-6.3.0-3 | 4 -
changelog/subscriptions-core-6.3.0-4 | 4 -
changelog/subscriptions-core-6.3.0-5 | 4 -
changelog/subscriptions-core-6.3.0-6 | 4 -
changelog/subscriptions-core-6.3.0-7 | 4 -
changelog/subscriptions-core-6.3.0-8 | 4 -
changelog/task-woopay-first-party-default | 4 -
changelog/update-2179-woopay-connection-url | 4 -
...mize-success-banner-message-account-status | 4 -
...ance-design-of-bnpl-payment-methods-status | 4 -
...nsaction-details-breakdown-labels-and-fees | 4 -
...te-7000-improve-analytics-call-performance | 4 -
...pay-uk-transaction-upper-limit-to-1200-gbp | 4 -
changelog/update-7372-issuer-evidence | 5 --
.../update-7380-steps-to-resolve-wording | 5 --
.../update-payment-intent-create-endpoint | 4 -
package-lock.json | 4 +-
package.json | 2 +-
readme.txt | 80 ++++++++++++++++++-
woocommerce-payments.php | 2 +-
103 files changed, 161 insertions(+), 417 deletions(-)
delete mode 100644 changelog/add-2094-pass-checkout-data-before-auth
delete mode 100644 changelog/add-6203-check-for-order-status-on-capture
delete mode 100644 changelog/add-6925-steps-to-resolve
delete mode 100644 changelog/add-6927-dispute-details-not-awaiting-response
delete mode 100644 changelog/add-7122-more-details-to-woo-ssr
delete mode 100644 changelog/add-7193-dispute-cta-for-inquries
delete mode 100644 changelog/add-7243-introduce-klarna-pm
delete mode 100644 changelog/add-7245-transaction-details-inquiry-steps-to-resolve
delete mode 100644 changelog/add-7289-remove-transaction-dispute-details-feature-flag
delete mode 100644 changelog/add-7295-loading-state-to-woopay-button
delete mode 100644 changelog/add-7322-saved-payment-methods-api
delete mode 100644 changelog/add-7342-send-merchant-fingerprint-data-on-onboarding
delete mode 100644 changelog/add-7368-add-api-documentation-for-payment-methods-api
delete mode 100644 changelog/add-7371-dispute-details-error-boundary
delete mode 100644 changelog/add-7381-add-documentation-for-create-payment-intent-api-endpoint
delete mode 100644 changelog/add-constructor-hooks-sniffer
delete mode 100644 changelog/add-documentation-for-cancel-authorization-endpoint
delete mode 100644 changelog/add-feature-flag-check-for-pay-for-order-flow
delete mode 100644 changelog/add-jcb-capability-request
delete mode 100644 changelog/add-settings-save-error-notice
delete mode 100644 changelog/add-show-survey-users-disable-woopay
delete mode 100644 changelog/dev-6214-e2e-tests-for-progressive-onboarding
delete mode 100644 changelog/dev-7258-remove-hooks-from-multi-currency-constructor
delete mode 100644 changelog/dev-7261-remove-hooks-from-fraud-services-constructor-2
delete mode 100644 changelog/dev-7262-remove-hooks-from-constructors
delete mode 100644 changelog/dev-7285-test-payment-method-title
delete mode 100644 changelog/feat-handle-server-side-dupe-flag
delete mode 100644 changelog/feature-enable-sepa-for-deferred-intent-upe
delete mode 100644 changelog/fix-2141-3
delete mode 100644 changelog/fix-5031-disable-currency-switcher-on-pay-for-order
delete mode 100644 changelog/fix-5524-price-conversion-helper-function
delete mode 100644 changelog/fix-5548-modal-header-alignement-on-safari
delete mode 100644 changelog/fix-5945-add-default-cc-enabled
delete mode 100644 changelog/fix-6182-hide-storefront-tooltip
delete mode 100644 changelog/fix-6466-mc-errors
delete mode 100644 changelog/fix-6714-request-button-virtual-variable
delete mode 100644 changelog/fix-6819-payment-method-messaging-invalid-value
delete mode 100644 changelog/fix-6891-redirect-dispute-detail-to-transaction
delete mode 100644 changelog/fix-6926-transaction-dispute-details-actions
delete mode 100644 changelog/fix-6928-remove-transaction-details-view-dispute-link
delete mode 100644 changelog/fix-6929-change-dispute-details-links-to-transactions-details
delete mode 100644 changelog/fix-6980-remove-passing-filter-name-from-request-class-send-method
delete mode 100644 changelog/fix-7151-multi-currency-onboarding
delete mode 100644 changelog/fix-7172-nyp-cart-editing
delete mode 100644 changelog/fix-7191-dispute-resolution-footer-inquiries
delete mode 100644 changelog/fix-7228-dispute-details-when-awaiting-response-past-due
delete mode 100644 changelog/fix-7241-update-live-dispute-docs-links-in-transaction-detail
delete mode 100644 changelog/fix-7254-update-dispute-fee-text
delete mode 100644 changelog/fix-7303-disable-wcpay-with-core-disabled
delete mode 100644 changelog/fix-7329-missing-multi-currency-check
delete mode 100644 changelog/fix-7343-missing-payment-details-section-in-payment-details-page-for-affirm-and-afterpay-payment-methods
delete mode 100644 changelog/fix-7357-multiple-woopay-session-requests
delete mode 100644 changelog/fix-7360-non-connected-onboarding-flow-access
delete mode 100644 changelog/fix-7373-show-dispute-amount-in-shopper-currency
delete mode 100644 changelog/fix-7395
delete mode 100644 changelog/fix-7396-disputestatuschip
delete mode 100644 changelog/fix-add-wc-blocks-loading-spinner-css
delete mode 100644 changelog/fix-add-woopay-woocommerce-mix-and-match-support
delete mode 100644 changelog/fix-bump-min-php-wc-beta
delete mode 100644 changelog/fix-correct-documentation-url-for-klarna
delete mode 100644 changelog/fix-docs-api-add-missing-sidebar-links-for-intent-and-pm
delete mode 100644 changelog/fix-finish-setup-link
delete mode 100644 changelog/fix-fix-bundles-on-product-woopay-express-checkout-button
delete mode 100644 changelog/fix-improve-escaping-around-attributes
delete mode 100644 changelog/fix-init-woopay-error
delete mode 100644 changelog/fix-optimise-capture-charge-calls
delete mode 100644 changelog/fix-redundant-webhooks-modifications
delete mode 100644 changelog/fix-save-customer-info-checkout
delete mode 100644 changelog/fix-subscription-migration-in-progress-check
delete mode 100644 changelog/fix-tracking-conditions
delete mode 100644 changelog/fix-woopay-billing-phone-fallback
delete mode 100644 changelog/fix-woopay-multiple-redirects
delete mode 100644 changelog/issue-6510-deprecate-wcpay-subscriptions
delete mode 100644 changelog/rpp-6686-payments-skeleton
delete mode 100644 changelog/rpp-6688-working-data
delete mode 100644 changelog/rpp-basic-process
delete mode 100644 changelog/rpp-container-exceptions
delete mode 100644 changelog/rpp-extract-gateway-methods
delete mode 100644 changelog/stripe-billing-deactivate-plugin-warning-updates
delete mode 100644 changelog/subscriptions-core-6.3.0
delete mode 100644 changelog/subscriptions-core-6.3.0-1
delete mode 100644 changelog/subscriptions-core-6.3.0-2
delete mode 100644 changelog/subscriptions-core-6.3.0-3
delete mode 100644 changelog/subscriptions-core-6.3.0-4
delete mode 100644 changelog/subscriptions-core-6.3.0-5
delete mode 100644 changelog/subscriptions-core-6.3.0-6
delete mode 100644 changelog/subscriptions-core-6.3.0-7
delete mode 100644 changelog/subscriptions-core-6.3.0-8
delete mode 100644 changelog/task-woopay-first-party-default
delete mode 100644 changelog/update-2179-woopay-connection-url
delete mode 100644 changelog/update-6569-customize-success-banner-message-account-status
delete mode 100644 changelog/update-6914-enhance-design-of-bnpl-payment-methods-status
delete mode 100644 changelog/update-6974-transaction-details-breakdown-labels-and-fees
delete mode 100644 changelog/update-7000-improve-analytics-call-performance
delete mode 100644 changelog/update-7222-bnpl-increase-afterpayclearpay-uk-transaction-upper-limit-to-1200-gbp
delete mode 100644 changelog/update-7372-issuer-evidence
delete mode 100644 changelog/update-7380-steps-to-resolve-wording
delete mode 100644 changelog/update-payment-intent-create-endpoint
diff --git a/changelog.txt b/changelog.txt
index 760c80618f3..655ddaebbba 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,5 +1,83 @@
*** WooPayments Changelog ***
+= 6.6.0 - 2023-10-11 =
+* Add - Add a notice on the Settings page to request JCB capability for Japanese customers.
+* Add - Add current user data to the onboarding init request payload. This data is used for fraud prevention.
+* Add - Added API endpoint to fetch customer's saved payment methods.
+* Add - Added docs for cancel_authorization endpoint
+* Add - Added documentation for create payment intent API endpoint.
+* Add - Added documentation for payment methods API endpoint
+* Add - Add functionality to enable WooPay first party auth behind feature flag.
+* Add - Add helper function/method for raw currency amount conversion.
+* Add - Add Klarna payment method
+* Add - Add loading state to WooPay button
+* Add - Add payment intent creation endpoint
+* Add - Add the feature flag check for pay-for-order flow
+* Add - Add WC blocks spinner to the WooPay checkout styles.
+* Add - Behind a feature flag: dispute message added to transactions screen for disputes not needing a response.
+* Add - Display dispute information, recommended resolution steps, and actions directly on the transaction details screen to help merchants with dispute resolution.
+* Add - Display server error messages on Settings save
+* Add - Expand the data points added to the WooCommerce SSR to include all the main WooPayments features.
+* Add - Handle server-side feature flag for new UPE type enablement.
+* Add - Introduce the "Subscription Relationship" column under the Orders list admin page when HPOS is enabled.
+* Add - Show survey for merchants that disable WooPay.
+* Fix - Add Mix and Match Products support on WooPay.
+* Fix - Add multi-currency enablement check in WooPay session handling.
+* Fix - Comment: Behind a feature flag: Update documentation links (new/changed docs content) when notifying merchant that a dispute needs response.
+* Fix - Disable automatic currency switching and switcher widgets on pay_for_order page.
+* Fix - Ensure renewal orders paid via the Block Checkout are correctly linked to their subscription.
+* Fix - Ensure the order needs processing transient is deleted when a subscription order (eg renewal) is created. Fixes issues with renewal orders going straight to a completed status.
+* Fix - fix: save platform checkout info on blocks
+* Fix - Fix Apple Pay and Google Pay if card payments are disabled.
+* Fix - Fix error when disabling WCPay with core disabled.
+* Fix - Fix init WooPay and empty cart error
+* Fix - Fix modal header alignment on safari browser
+* Fix - Fix onboarding section on MultiCurrency settings page.
+* Fix - Fix WooPay express checkout button with product bundles on product page.
+* Fix - Hide tooltip related to Storefront theme in Multi-Currency settings when Storefront is not the active theme
+* Fix - Improved product details script with enhanced price calculation, and fallbacks for potential undefined values.
+* Fix - Improve escaping around attributes.
+* Fix - Load multi-currency class on setup page.
+* Fix - Missing styles on the Edit Subscription page when HPOS is enabled.
+* Fix - Only request WooPay session data once on blocks pages.
+* Fix - Payment method section missing for Affirm and Afterpay on transaction details page
+* Fix - Prevent charging completed or processing orders with a new payment intent ID
+* Fix - Prevent WooPay-related implementation to modify non-WooPay-specific webhooks by changing their data.
+* Fix - Prevent WooPay multiple redirect requests.
+* Fix - Redirect back to the connect page when attempting to access the new onboarding flow without a server connection.
+* Fix - Redirect back to the pay-for-order page when it's pay-for-order order
+* Fix - Resolved an issue that caused paying for failed/pending parent orders that include Product Add-ons to not calculate the correct total.
+* Fix - Speed up capturing terminal and authorized payments.
+* Fix - Store the correct subscription start date in postmeta and ordermeta when HPOS and data syncing is being used.
+* Fix - Tracking conditions
+* Fix - Virtual variable products no longer require shipping details when checking out with Apple Pay and Google Pay
+* Fix - When HPOS is enabled, deleting a customer will now delete their subscriptions.
+* Fix - When HPOS is enabled, make the orders_by_type_query filter box work in the WooCommerce orders screen.
+* Fix - WooPay save my info phone number fallback for virtual products
+* Update - Adapt the PO congratulations card copy for pending account status.
+* Update - Allow deferred intent creation UPE to support SEPA payments.
+* Update - Enhance design of bnpl payment methods status in settings screen
+* Update - Increase GBP transaction limit for Afterpay
+* Update - Only display the WCPay Subscriptions setting to existing users as part of deprecating this feature.
+* Update - Set WooPay First Party Authentication feature flag to default on.
+* Update - Store customer currencies as an option to avoid expensive calculation.
+* Update - Updated Transaction Details summary with added fee breakdown tooltip for disputed transactions.
+* Update - Update links that pointed to the dispute details screen to point to the transaction details screen
+* Update - Update Name Your Price compatibility to use new Compatibility methods.
+* Update - Update the content of modals that are displayed when deactivating the WooPayments or Woo Subscriptions plugins when the store has active Stripe Billing subscriptions.
+* Update - Update URL used to communicate with WooPay from the iFrame in the merchant site.
+* Dev - Added missing API docs links for payment intents and payment methods API endpoints
+* Dev - Capitalize the JCB label on transactions details page.
+* Dev - e2e tests for progressive onboarding
+* Dev - Extracting payment metadata and level 3 data generation into services.
+* Dev - Migrate away from hooking into actions in certain classes
+* Dev - Move fraud related service hooks out of class constructors and into new init_hooks methods.
+* Dev - Move hooks out of MultiCurrency constructor into own init_hooks method.
+* Dev - Refactored request class send() method
+* Dev - Refactor to move hook initialisation out of constructors.
+* Dev - This work is part of a UI improvements to increase disputes response that is behind a feature flag. A changelog entry will be added to represent the work as a whole.
+* Dev - Update subscriptions-core to 6.3.0.
+
= 6.5.1 - 2023-09-26 =
* Fix - fix incorrect payment method title for non-WooPayments gateways
diff --git a/changelog/add-2094-pass-checkout-data-before-auth b/changelog/add-2094-pass-checkout-data-before-auth
deleted file mode 100644
index e59b9c588a8..00000000000
--- a/changelog/add-2094-pass-checkout-data-before-auth
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: add
-
-Add functionality to enable WooPay first party auth behind feature flag.
diff --git a/changelog/add-6203-check-for-order-status-on-capture b/changelog/add-6203-check-for-order-status-on-capture
deleted file mode 100644
index dfb3a3f5e7a..00000000000
--- a/changelog/add-6203-check-for-order-status-on-capture
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: fix
-
-Prevent charging completed or processing orders with a new payment intent ID
diff --git a/changelog/add-6925-steps-to-resolve b/changelog/add-6925-steps-to-resolve
deleted file mode 100644
index 9ef328c1dba..00000000000
--- a/changelog/add-6925-steps-to-resolve
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: dev
-Comment: This work is part of a UI improvements to increase disputes response that is behind a feature flag. A changelog entry will be added to represent the work as a whole.
-
-
diff --git a/changelog/add-6927-dispute-details-not-awaiting-response b/changelog/add-6927-dispute-details-not-awaiting-response
deleted file mode 100644
index ae55b441d0d..00000000000
--- a/changelog/add-6927-dispute-details-not-awaiting-response
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: add
-
-Behind a feature flag: dispute message added to transactions screen for disputes not needing a response.
diff --git a/changelog/add-7122-more-details-to-woo-ssr b/changelog/add-7122-more-details-to-woo-ssr
deleted file mode 100644
index 1def541bdbb..00000000000
--- a/changelog/add-7122-more-details-to-woo-ssr
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: add
-
-Expand the data points added to the WooCommerce SSR to include all the main WooPayments features.
diff --git a/changelog/add-7193-dispute-cta-for-inquries b/changelog/add-7193-dispute-cta-for-inquries
deleted file mode 100644
index 3650c8263aa..00000000000
--- a/changelog/add-7193-dispute-cta-for-inquries
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: dev
-Comment: Add CTA for Inquiries, behind a feature flag.
-
-
diff --git a/changelog/add-7243-introduce-klarna-pm b/changelog/add-7243-introduce-klarna-pm
deleted file mode 100644
index a2ab1a34134..00000000000
--- a/changelog/add-7243-introduce-klarna-pm
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: add
-
-Add Klarna payment method
diff --git a/changelog/add-7245-transaction-details-inquiry-steps-to-resolve b/changelog/add-7245-transaction-details-inquiry-steps-to-resolve
deleted file mode 100644
index c6f31448275..00000000000
--- a/changelog/add-7245-transaction-details-inquiry-steps-to-resolve
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: add
-Comment: Behind feature flag: add steps to resolve section to the transaction dispute details
-
-
diff --git a/changelog/add-7289-remove-transaction-dispute-details-feature-flag b/changelog/add-7289-remove-transaction-dispute-details-feature-flag
deleted file mode 100644
index 4f7ee73ac77..00000000000
--- a/changelog/add-7289-remove-transaction-dispute-details-feature-flag
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: major
-Type: add
-
-Display dispute information, recommended resolution steps, and actions directly on the transaction details screen to help merchants with dispute resolution.
diff --git a/changelog/add-7295-loading-state-to-woopay-button b/changelog/add-7295-loading-state-to-woopay-button
deleted file mode 100644
index 7295b647d79..00000000000
--- a/changelog/add-7295-loading-state-to-woopay-button
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: add
-
-Add loading state to WooPay button
diff --git a/changelog/add-7322-saved-payment-methods-api b/changelog/add-7322-saved-payment-methods-api
deleted file mode 100644
index d656309c6a1..00000000000
--- a/changelog/add-7322-saved-payment-methods-api
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: add
-
-Added API endpoint to fetch customer's saved payment methods.
diff --git a/changelog/add-7342-send-merchant-fingerprint-data-on-onboarding b/changelog/add-7342-send-merchant-fingerprint-data-on-onboarding
deleted file mode 100644
index b477c6892d0..00000000000
--- a/changelog/add-7342-send-merchant-fingerprint-data-on-onboarding
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: add
-
-Add current user data to the onboarding init request payload. This data is used for fraud prevention.
diff --git a/changelog/add-7368-add-api-documentation-for-payment-methods-api b/changelog/add-7368-add-api-documentation-for-payment-methods-api
deleted file mode 100644
index b2e92105371..00000000000
--- a/changelog/add-7368-add-api-documentation-for-payment-methods-api
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: add
-
-Added documentation for payment methods API endpoint
diff --git a/changelog/add-7371-dispute-details-error-boundary b/changelog/add-7371-dispute-details-error-boundary
deleted file mode 100644
index 6072efc6e45..00000000000
--- a/changelog/add-7371-dispute-details-error-boundary
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: add
-Comment: Part of a larger feature with separate changelog entry
-
-
diff --git a/changelog/add-7381-add-documentation-for-create-payment-intent-api-endpoint b/changelog/add-7381-add-documentation-for-create-payment-intent-api-endpoint
deleted file mode 100644
index 2d4310612e6..00000000000
--- a/changelog/add-7381-add-documentation-for-create-payment-intent-api-endpoint
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: add
-
-Added documentation for create payment intent API endpoint.
diff --git a/changelog/add-constructor-hooks-sniffer b/changelog/add-constructor-hooks-sniffer
deleted file mode 100644
index 14e54f24c3c..00000000000
--- a/changelog/add-constructor-hooks-sniffer
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: dev
-
-Migrate away from hooking into actions in certain classes
diff --git a/changelog/add-documentation-for-cancel-authorization-endpoint b/changelog/add-documentation-for-cancel-authorization-endpoint
deleted file mode 100644
index fad3372ddbf..00000000000
--- a/changelog/add-documentation-for-cancel-authorization-endpoint
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: add
-
-Added docs for cancel_authorization endpoint
diff --git a/changelog/add-feature-flag-check-for-pay-for-order-flow b/changelog/add-feature-flag-check-for-pay-for-order-flow
deleted file mode 100644
index 067d45ed80b..00000000000
--- a/changelog/add-feature-flag-check-for-pay-for-order-flow
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: add
-
-Add the feature flag check for pay-for-order flow
diff --git a/changelog/add-jcb-capability-request b/changelog/add-jcb-capability-request
deleted file mode 100644
index 3fa9ab76aac..00000000000
--- a/changelog/add-jcb-capability-request
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: add
-
-Add a notice on the Settings page to request JCB capability for Japanese customers.
diff --git a/changelog/add-settings-save-error-notice b/changelog/add-settings-save-error-notice
deleted file mode 100644
index 088a0c4d746..00000000000
--- a/changelog/add-settings-save-error-notice
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: add
-
-Display server error messages on Settings save
diff --git a/changelog/add-show-survey-users-disable-woopay b/changelog/add-show-survey-users-disable-woopay
deleted file mode 100644
index edfd7514b8b..00000000000
--- a/changelog/add-show-survey-users-disable-woopay
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: add
-
-Show survey for merchants that disable WooPay.
diff --git a/changelog/dev-6214-e2e-tests-for-progressive-onboarding b/changelog/dev-6214-e2e-tests-for-progressive-onboarding
deleted file mode 100644
index 73526b51ed3..00000000000
--- a/changelog/dev-6214-e2e-tests-for-progressive-onboarding
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: dev
-
-e2e tests for progressive onboarding
diff --git a/changelog/dev-7258-remove-hooks-from-multi-currency-constructor b/changelog/dev-7258-remove-hooks-from-multi-currency-constructor
deleted file mode 100644
index caba16f39ee..00000000000
--- a/changelog/dev-7258-remove-hooks-from-multi-currency-constructor
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: dev
-
-Move hooks out of MultiCurrency constructor into own init_hooks method.
diff --git a/changelog/dev-7261-remove-hooks-from-fraud-services-constructor-2 b/changelog/dev-7261-remove-hooks-from-fraud-services-constructor-2
deleted file mode 100644
index d1f5bfa6723..00000000000
--- a/changelog/dev-7261-remove-hooks-from-fraud-services-constructor-2
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: dev
-
-Move fraud related service hooks out of class constructors and into new init_hooks methods.
diff --git a/changelog/dev-7262-remove-hooks-from-constructors b/changelog/dev-7262-remove-hooks-from-constructors
deleted file mode 100644
index 9d3cb4ee5e8..00000000000
--- a/changelog/dev-7262-remove-hooks-from-constructors
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: dev
-
-Refactor to move hook initialisation out of constructors.
diff --git a/changelog/dev-7285-test-payment-method-title b/changelog/dev-7285-test-payment-method-title
deleted file mode 100644
index a825d60b99e..00000000000
--- a/changelog/dev-7285-test-payment-method-title
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: dev
-Comment: Adding a few unit tests
-
-
diff --git a/changelog/feat-handle-server-side-dupe-flag b/changelog/feat-handle-server-side-dupe-flag
deleted file mode 100644
index cdf484a3b1d..00000000000
--- a/changelog/feat-handle-server-side-dupe-flag
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: add
-
-Handle server-side feature flag for new UPE type enablement.
diff --git a/changelog/feature-enable-sepa-for-deferred-intent-upe b/changelog/feature-enable-sepa-for-deferred-intent-upe
deleted file mode 100644
index 8cabbf6a2ec..00000000000
--- a/changelog/feature-enable-sepa-for-deferred-intent-upe
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: update
-
-Allow deferred intent creation UPE to support SEPA payments.
diff --git a/changelog/fix-2141-3 b/changelog/fix-2141-3
deleted file mode 100644
index c4bec32f55b..00000000000
--- a/changelog/fix-2141-3
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: fix
-
-Redirect back to the pay-for-order page when it's pay-for-order order
diff --git a/changelog/fix-5031-disable-currency-switcher-on-pay-for-order b/changelog/fix-5031-disable-currency-switcher-on-pay-for-order
deleted file mode 100644
index 1a8faa30591..00000000000
--- a/changelog/fix-5031-disable-currency-switcher-on-pay-for-order
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: fix
-
-Disable automatic currency switching and switcher widgets on pay_for_order page.
diff --git a/changelog/fix-5524-price-conversion-helper-function b/changelog/fix-5524-price-conversion-helper-function
deleted file mode 100644
index 5f1d4951f67..00000000000
--- a/changelog/fix-5524-price-conversion-helper-function
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: add
-
-Add helper function/method for raw currency amount conversion.
diff --git a/changelog/fix-5548-modal-header-alignement-on-safari b/changelog/fix-5548-modal-header-alignement-on-safari
deleted file mode 100644
index 4292f7a24be..00000000000
--- a/changelog/fix-5548-modal-header-alignement-on-safari
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: fix
-
-Fix modal header alignment on safari browser
diff --git a/changelog/fix-5945-add-default-cc-enabled b/changelog/fix-5945-add-default-cc-enabled
deleted file mode 100644
index ead1017db74..00000000000
--- a/changelog/fix-5945-add-default-cc-enabled
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: fix
-
-Fix Apple Pay and Google Pay if card payments are disabled.
diff --git a/changelog/fix-6182-hide-storefront-tooltip b/changelog/fix-6182-hide-storefront-tooltip
deleted file mode 100644
index aa54714f13d..00000000000
--- a/changelog/fix-6182-hide-storefront-tooltip
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: fix
-
-Hide tooltip related to Storefront theme in Multi-Currency settings when Storefront is not the active theme
diff --git a/changelog/fix-6466-mc-errors b/changelog/fix-6466-mc-errors
deleted file mode 100644
index a0f78a106ca..00000000000
--- a/changelog/fix-6466-mc-errors
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: fix
-
-Fix onboarding section on MultiCurrency settings page.
diff --git a/changelog/fix-6714-request-button-virtual-variable b/changelog/fix-6714-request-button-virtual-variable
deleted file mode 100644
index cb7b26a6be6..00000000000
--- a/changelog/fix-6714-request-button-virtual-variable
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: fix
-
-Virtual variable products no longer require shipping details when checking out with Apple Pay and Google Pay
diff --git a/changelog/fix-6819-payment-method-messaging-invalid-value b/changelog/fix-6819-payment-method-messaging-invalid-value
deleted file mode 100644
index 417a064040f..00000000000
--- a/changelog/fix-6819-payment-method-messaging-invalid-value
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: fix
-
-Improved product details script with enhanced price calculation, and fallbacks for potential undefined values.
diff --git a/changelog/fix-6891-redirect-dispute-detail-to-transaction b/changelog/fix-6891-redirect-dispute-detail-to-transaction
deleted file mode 100644
index 1a7a59f3d4e..00000000000
--- a/changelog/fix-6891-redirect-dispute-detail-to-transaction
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: dev
-
-This work is part of a UI improvements to increase disputes response that is behind a feature flag. A changelog entry will be added to represent the work as a whole.
diff --git a/changelog/fix-6926-transaction-dispute-details-actions b/changelog/fix-6926-transaction-dispute-details-actions
deleted file mode 100644
index 114fdfd9da0..00000000000
--- a/changelog/fix-6926-transaction-dispute-details-actions
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: add
-Comment: Behind a feature flag: add challenge and accept action buttons to Transaction Details screen
-
-
diff --git a/changelog/fix-6928-remove-transaction-details-view-dispute-link b/changelog/fix-6928-remove-transaction-details-view-dispute-link
deleted file mode 100644
index 9d4180fb8cf..00000000000
--- a/changelog/fix-6928-remove-transaction-details-view-dispute-link
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: update
-Comment: Behind a feature flag, remove View dispute link on transaction details page.
-
-
diff --git a/changelog/fix-6929-change-dispute-details-links-to-transactions-details b/changelog/fix-6929-change-dispute-details-links-to-transactions-details
deleted file mode 100644
index b080d0edf21..00000000000
--- a/changelog/fix-6929-change-dispute-details-links-to-transactions-details
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: update
-
-Update links that pointed to the dispute details screen to point to the transaction details screen
diff --git a/changelog/fix-6980-remove-passing-filter-name-from-request-class-send-method b/changelog/fix-6980-remove-passing-filter-name-from-request-class-send-method
deleted file mode 100644
index a0b39737587..00000000000
--- a/changelog/fix-6980-remove-passing-filter-name-from-request-class-send-method
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: dev
-
-Refactored request class send() method
diff --git a/changelog/fix-7151-multi-currency-onboarding b/changelog/fix-7151-multi-currency-onboarding
deleted file mode 100644
index 880b2a905a1..00000000000
--- a/changelog/fix-7151-multi-currency-onboarding
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: fix
-
-Load multi-currency class on setup page.
diff --git a/changelog/fix-7172-nyp-cart-editing b/changelog/fix-7172-nyp-cart-editing
deleted file mode 100644
index 8c13ebfa01e..00000000000
--- a/changelog/fix-7172-nyp-cart-editing
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: update
-
-Update Name Your Price compatibility to use new Compatibility methods.
diff --git a/changelog/fix-7191-dispute-resolution-footer-inquiries b/changelog/fix-7191-dispute-resolution-footer-inquiries
deleted file mode 100644
index b273f083239..00000000000
--- a/changelog/fix-7191-dispute-resolution-footer-inquiries
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: add
-Comment: Behind feature flag: Show dispute resolution footer for inquiries under review or closed.
-
-
diff --git a/changelog/fix-7228-dispute-details-when-awaiting-response-past-due b/changelog/fix-7228-dispute-details-when-awaiting-response-past-due
deleted file mode 100644
index 2de5d68f988..00000000000
--- a/changelog/fix-7228-dispute-details-when-awaiting-response-past-due
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: fix
-Comment: Behind feature flag: Show dispute details on the transaction details screen when a dispute is awaiting response and past due
-
-
diff --git a/changelog/fix-7241-update-live-dispute-docs-links-in-transaction-detail b/changelog/fix-7241-update-live-dispute-docs-links-in-transaction-detail
deleted file mode 100644
index 98e640d387f..00000000000
--- a/changelog/fix-7241-update-live-dispute-docs-links-in-transaction-detail
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: fix
-
-Comment: Behind a feature flag: Update documentation links (new/changed docs content) when notifying merchant that a dispute needs response.
diff --git a/changelog/fix-7254-update-dispute-fee-text b/changelog/fix-7254-update-dispute-fee-text
deleted file mode 100644
index d0bf68420b2..00000000000
--- a/changelog/fix-7254-update-dispute-fee-text
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: fix
-Comment: Minor wording change on unreleased feature.
-
-
diff --git a/changelog/fix-7303-disable-wcpay-with-core-disabled b/changelog/fix-7303-disable-wcpay-with-core-disabled
deleted file mode 100644
index 4d2d85b0916..00000000000
--- a/changelog/fix-7303-disable-wcpay-with-core-disabled
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: fix
-
-Fix error when disabling WCPay with core disabled.
diff --git a/changelog/fix-7329-missing-multi-currency-check b/changelog/fix-7329-missing-multi-currency-check
deleted file mode 100644
index af48c9f30e1..00000000000
--- a/changelog/fix-7329-missing-multi-currency-check
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: fix
-
-Add multi-currency enablement check in WooPay session handling.
diff --git a/changelog/fix-7343-missing-payment-details-section-in-payment-details-page-for-affirm-and-afterpay-payment-methods b/changelog/fix-7343-missing-payment-details-section-in-payment-details-page-for-affirm-and-afterpay-payment-methods
deleted file mode 100644
index d4be499a665..00000000000
--- a/changelog/fix-7343-missing-payment-details-section-in-payment-details-page-for-affirm-and-afterpay-payment-methods
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: fix
-
-Payment method section missing for Affirm and Afterpay on transaction details page
diff --git a/changelog/fix-7357-multiple-woopay-session-requests b/changelog/fix-7357-multiple-woopay-session-requests
deleted file mode 100644
index 2c2b2d69641..00000000000
--- a/changelog/fix-7357-multiple-woopay-session-requests
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: fix
-
-Only request WooPay session data once on blocks pages.
diff --git a/changelog/fix-7360-non-connected-onboarding-flow-access b/changelog/fix-7360-non-connected-onboarding-flow-access
deleted file mode 100644
index 3af6b6a4ca2..00000000000
--- a/changelog/fix-7360-non-connected-onboarding-flow-access
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: fix
-
-Redirect back to the connect page when attempting to access the new onboarding flow without a server connection.
diff --git a/changelog/fix-7373-show-dispute-amount-in-shopper-currency b/changelog/fix-7373-show-dispute-amount-in-shopper-currency
deleted file mode 100644
index 724d7a5fdec..00000000000
--- a/changelog/fix-7373-show-dispute-amount-in-shopper-currency
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: fix
-Comment: Revert pre-release change to show dispute amount in the shopper's currency on the transaction details screen
-
-
diff --git a/changelog/fix-7395 b/changelog/fix-7395
deleted file mode 100644
index 9d6115e20b2..00000000000
--- a/changelog/fix-7395
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: fix
-Comment: Updating tracks events
-
-
diff --git a/changelog/fix-7396-disputestatuschip b/changelog/fix-7396-disputestatuschip
deleted file mode 100644
index ab5a4382fa5..00000000000
--- a/changelog/fix-7396-disputestatuschip
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: fix
-Comment: Minor UI change, background color on StatusChip for disputes
-
-
diff --git a/changelog/fix-add-wc-blocks-loading-spinner-css b/changelog/fix-add-wc-blocks-loading-spinner-css
deleted file mode 100644
index d3fbea23ade..00000000000
--- a/changelog/fix-add-wc-blocks-loading-spinner-css
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: add
-
-Add WC blocks spinner to the WooPay checkout styles.
diff --git a/changelog/fix-add-woopay-woocommerce-mix-and-match-support b/changelog/fix-add-woopay-woocommerce-mix-and-match-support
deleted file mode 100644
index 4d5507bd040..00000000000
--- a/changelog/fix-add-woopay-woocommerce-mix-and-match-support
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: fix
-
-Add Mix and Match Products support on WooPay.
diff --git a/changelog/fix-bump-min-php-wc-beta b/changelog/fix-bump-min-php-wc-beta
deleted file mode 100644
index 2e96582ebd7..00000000000
--- a/changelog/fix-bump-min-php-wc-beta
+++ /dev/null
@@ -1,3 +0,0 @@
-Significance: patch
-Type: dev
-Comment: Update automated test matrix to remove PHP 7.3 testing against WooCommerce beta.
diff --git a/changelog/fix-correct-documentation-url-for-klarna b/changelog/fix-correct-documentation-url-for-klarna
deleted file mode 100644
index 3092cfe2b34..00000000000
--- a/changelog/fix-correct-documentation-url-for-klarna
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: fix
-Comment: Adding missed functionality from another PR
-
-
diff --git a/changelog/fix-docs-api-add-missing-sidebar-links-for-intent-and-pm b/changelog/fix-docs-api-add-missing-sidebar-links-for-intent-and-pm
deleted file mode 100644
index 5147a9663af..00000000000
--- a/changelog/fix-docs-api-add-missing-sidebar-links-for-intent-and-pm
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: dev
-
-Added missing API docs links for payment intents and payment methods API endpoints
diff --git a/changelog/fix-finish-setup-link b/changelog/fix-finish-setup-link
deleted file mode 100644
index b4acb564d44..00000000000
--- a/changelog/fix-finish-setup-link
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: dev
-
-Capitalize the JCB label on transactions details page.
diff --git a/changelog/fix-fix-bundles-on-product-woopay-express-checkout-button b/changelog/fix-fix-bundles-on-product-woopay-express-checkout-button
deleted file mode 100644
index 37aad14f216..00000000000
--- a/changelog/fix-fix-bundles-on-product-woopay-express-checkout-button
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: fix
-
-Fix WooPay express checkout button with product bundles on product page.
diff --git a/changelog/fix-improve-escaping-around-attributes b/changelog/fix-improve-escaping-around-attributes
deleted file mode 100644
index f33b62dbcbe..00000000000
--- a/changelog/fix-improve-escaping-around-attributes
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: fix
-
-Improve escaping around attributes.
diff --git a/changelog/fix-init-woopay-error b/changelog/fix-init-woopay-error
deleted file mode 100644
index 58cbd587cec..00000000000
--- a/changelog/fix-init-woopay-error
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: fix
-
-Fix init WooPay and empty cart error
diff --git a/changelog/fix-optimise-capture-charge-calls b/changelog/fix-optimise-capture-charge-calls
deleted file mode 100644
index b8c1ab4451e..00000000000
--- a/changelog/fix-optimise-capture-charge-calls
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: fix
-
-Speed up capturing terminal and authorized payments.
diff --git a/changelog/fix-redundant-webhooks-modifications b/changelog/fix-redundant-webhooks-modifications
deleted file mode 100644
index 5c7d87846af..00000000000
--- a/changelog/fix-redundant-webhooks-modifications
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: fix
-
-Prevent WooPay-related implementation to modify non-WooPay-specific webhooks by changing their data.
diff --git a/changelog/fix-save-customer-info-checkout b/changelog/fix-save-customer-info-checkout
deleted file mode 100644
index cf33c4822c6..00000000000
--- a/changelog/fix-save-customer-info-checkout
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: fix
-
-fix: save platform checkout info on blocks
diff --git a/changelog/fix-subscription-migration-in-progress-check b/changelog/fix-subscription-migration-in-progress-check
deleted file mode 100644
index 650e1b1d627..00000000000
--- a/changelog/fix-subscription-migration-in-progress-check
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: dev
-Comment: This change fixes a bug in unreleased code. No changelog entry needed.
-
-
diff --git a/changelog/fix-tracking-conditions b/changelog/fix-tracking-conditions
deleted file mode 100644
index 3f24fe7b360..00000000000
--- a/changelog/fix-tracking-conditions
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: fix
-
-Tracking conditions
diff --git a/changelog/fix-woopay-billing-phone-fallback b/changelog/fix-woopay-billing-phone-fallback
deleted file mode 100644
index 98b41427580..00000000000
--- a/changelog/fix-woopay-billing-phone-fallback
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: fix
-
-WooPay save my info phone number fallback for virtual products
diff --git a/changelog/fix-woopay-multiple-redirects b/changelog/fix-woopay-multiple-redirects
deleted file mode 100644
index 71ce26816e0..00000000000
--- a/changelog/fix-woopay-multiple-redirects
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: fix
-
-Prevent WooPay multiple redirect requests.
diff --git a/changelog/issue-6510-deprecate-wcpay-subscriptions b/changelog/issue-6510-deprecate-wcpay-subscriptions
deleted file mode 100644
index b40eaae8139..00000000000
--- a/changelog/issue-6510-deprecate-wcpay-subscriptions
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: update
-
-Only display the WCPay Subscriptions setting to existing users as part of deprecating this feature.
diff --git a/changelog/rpp-6686-payments-skeleton b/changelog/rpp-6686-payments-skeleton
deleted file mode 100644
index 77b8ee16cea..00000000000
--- a/changelog/rpp-6686-payments-skeleton
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: dev
-Comment: Adding the basic structure for payment states and context"
-
-
diff --git a/changelog/rpp-6688-working-data b/changelog/rpp-6688-working-data
deleted file mode 100644
index 0c026b421bf..00000000000
--- a/changelog/rpp-6688-working-data
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: dev
-Comment: Making the mechanism for working payment data functional.
-
-
diff --git a/changelog/rpp-basic-process b/changelog/rpp-basic-process
deleted file mode 100644
index d576f27fd31..00000000000
--- a/changelog/rpp-basic-process
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: dev
-Comment: Adding a barebones payment process to src.
-
-
diff --git a/changelog/rpp-container-exceptions b/changelog/rpp-container-exceptions
deleted file mode 100644
index 9a4b58afd7a..00000000000
--- a/changelog/rpp-container-exceptions
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: dev
-Comment: Adding proper exceptions to the depenedency container.
-
-
diff --git a/changelog/rpp-extract-gateway-methods b/changelog/rpp-extract-gateway-methods
deleted file mode 100644
index 90a2391c727..00000000000
--- a/changelog/rpp-extract-gateway-methods
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: dev
-
-Extracting payment metadata and level 3 data generation into services.
diff --git a/changelog/stripe-billing-deactivate-plugin-warning-updates b/changelog/stripe-billing-deactivate-plugin-warning-updates
deleted file mode 100644
index fb52189cd61..00000000000
--- a/changelog/stripe-billing-deactivate-plugin-warning-updates
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: update
-
-Update the content of modals that are displayed when deactivating the WooPayments or Woo Subscriptions plugins when the store has active Stripe Billing subscriptions.
diff --git a/changelog/subscriptions-core-6.3.0 b/changelog/subscriptions-core-6.3.0
deleted file mode 100644
index 5cf498d1688..00000000000
--- a/changelog/subscriptions-core-6.3.0
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: dev
-
-Update subscriptions-core to 6.3.0.
diff --git a/changelog/subscriptions-core-6.3.0-1 b/changelog/subscriptions-core-6.3.0-1
deleted file mode 100644
index 0c42e39eb5b..00000000000
--- a/changelog/subscriptions-core-6.3.0-1
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: add
-
-Introduce the "Subscription Relationship" column under the Orders list admin page when HPOS is enabled.
diff --git a/changelog/subscriptions-core-6.3.0-2 b/changelog/subscriptions-core-6.3.0-2
deleted file mode 100644
index fbc58e2599a..00000000000
--- a/changelog/subscriptions-core-6.3.0-2
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: fix
-
-When HPOS is enabled, make the orders_by_type_query filter box work in the WooCommerce orders screen.
diff --git a/changelog/subscriptions-core-6.3.0-3 b/changelog/subscriptions-core-6.3.0-3
deleted file mode 100644
index 7417f089602..00000000000
--- a/changelog/subscriptions-core-6.3.0-3
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: fix
-
-Ensure renewal orders paid via the Block Checkout are correctly linked to their subscription.
diff --git a/changelog/subscriptions-core-6.3.0-4 b/changelog/subscriptions-core-6.3.0-4
deleted file mode 100644
index 329c050a701..00000000000
--- a/changelog/subscriptions-core-6.3.0-4
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: fix
-
-Resolved an issue that caused paying for failed/pending parent orders that include Product Add-ons to not calculate the correct total.
diff --git a/changelog/subscriptions-core-6.3.0-5 b/changelog/subscriptions-core-6.3.0-5
deleted file mode 100644
index 91d0638712f..00000000000
--- a/changelog/subscriptions-core-6.3.0-5
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: fix
-
-Ensure the order needs processing transient is deleted when a subscription order (eg renewal) is created. Fixes issues with renewal orders going straight to a completed status.
diff --git a/changelog/subscriptions-core-6.3.0-6 b/changelog/subscriptions-core-6.3.0-6
deleted file mode 100644
index 852f5ffcb84..00000000000
--- a/changelog/subscriptions-core-6.3.0-6
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: fix
-
-Store the correct subscription start date in postmeta and ordermeta when HPOS and data syncing is being used.
diff --git a/changelog/subscriptions-core-6.3.0-7 b/changelog/subscriptions-core-6.3.0-7
deleted file mode 100644
index d67e3e1922f..00000000000
--- a/changelog/subscriptions-core-6.3.0-7
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: fix
-
-When HPOS is enabled, deleting a customer will now delete their subscriptions.
diff --git a/changelog/subscriptions-core-6.3.0-8 b/changelog/subscriptions-core-6.3.0-8
deleted file mode 100644
index 2fe3b3489cc..00000000000
--- a/changelog/subscriptions-core-6.3.0-8
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: fix
-
-Missing styles on the Edit Subscription page when HPOS is enabled.
diff --git a/changelog/task-woopay-first-party-default b/changelog/task-woopay-first-party-default
deleted file mode 100644
index bc73b3251f1..00000000000
--- a/changelog/task-woopay-first-party-default
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: update
-
-Set WooPay First Party Authentication feature flag to default on.
diff --git a/changelog/update-2179-woopay-connection-url b/changelog/update-2179-woopay-connection-url
deleted file mode 100644
index 03950bcbbae..00000000000
--- a/changelog/update-2179-woopay-connection-url
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: update
-
-Update URL used to communicate with WooPay from the iFrame in the merchant site.
diff --git a/changelog/update-6569-customize-success-banner-message-account-status b/changelog/update-6569-customize-success-banner-message-account-status
deleted file mode 100644
index 704ae0c6db3..00000000000
--- a/changelog/update-6569-customize-success-banner-message-account-status
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: update
-
-Adapt the PO congratulations card copy for pending account status.
diff --git a/changelog/update-6914-enhance-design-of-bnpl-payment-methods-status b/changelog/update-6914-enhance-design-of-bnpl-payment-methods-status
deleted file mode 100644
index f0dd71a9eb8..00000000000
--- a/changelog/update-6914-enhance-design-of-bnpl-payment-methods-status
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: update
-
-Enhance design of bnpl payment methods status in settings screen
diff --git a/changelog/update-6974-transaction-details-breakdown-labels-and-fees b/changelog/update-6974-transaction-details-breakdown-labels-and-fees
deleted file mode 100644
index 3613ca6d6c2..00000000000
--- a/changelog/update-6974-transaction-details-breakdown-labels-and-fees
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: update
-
-Updated Transaction Details summary with added fee breakdown tooltip for disputed transactions.
diff --git a/changelog/update-7000-improve-analytics-call-performance b/changelog/update-7000-improve-analytics-call-performance
deleted file mode 100644
index 474e263290d..00000000000
--- a/changelog/update-7000-improve-analytics-call-performance
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: update
-
-Store customer currencies as an option to avoid expensive calculation.
diff --git a/changelog/update-7222-bnpl-increase-afterpayclearpay-uk-transaction-upper-limit-to-1200-gbp b/changelog/update-7222-bnpl-increase-afterpayclearpay-uk-transaction-upper-limit-to-1200-gbp
deleted file mode 100644
index d3c9c465e35..00000000000
--- a/changelog/update-7222-bnpl-increase-afterpayclearpay-uk-transaction-upper-limit-to-1200-gbp
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: update
-
-Increase GBP transaction limit for Afterpay
diff --git a/changelog/update-7372-issuer-evidence b/changelog/update-7372-issuer-evidence
deleted file mode 100644
index d79fece68a4..00000000000
--- a/changelog/update-7372-issuer-evidence
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: update
-Comment: Just putting a feature (dispute issuer evidence) that was never public to real users behind a feature flag.
-
-
diff --git a/changelog/update-7380-steps-to-resolve-wording b/changelog/update-7380-steps-to-resolve-wording
deleted file mode 100644
index 6ab1236fd7b..00000000000
--- a/changelog/update-7380-steps-to-resolve-wording
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: update
-Comment: Wording change of dispute details section on transaction details page that has not been public to users.
-
-
diff --git a/changelog/update-payment-intent-create-endpoint b/changelog/update-payment-intent-create-endpoint
deleted file mode 100644
index 875ac643709..00000000000
--- a/changelog/update-payment-intent-create-endpoint
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: minor
-Type: add
-
-Add payment intent creation endpoint
diff --git a/package-lock.json b/package-lock.json
index 185fe217534..4b620347e41 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "woocommerce-payments",
- "version": "6.5.1",
+ "version": "6.6.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "woocommerce-payments",
- "version": "6.5.1",
+ "version": "6.6.0",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {
diff --git a/package.json b/package.json
index 08ce1bc783b..a49df63fa09 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "woocommerce-payments",
- "version": "6.5.1",
+ "version": "6.6.0",
"main": "webpack.config.js",
"author": "Automattic",
"license": "GPL-3.0-or-later",
diff --git a/readme.txt b/readme.txt
index 2a6554048ac..a411d06ab55 100644
--- a/readme.txt
+++ b/readme.txt
@@ -4,7 +4,7 @@ Tags: payment gateway, payment, apple pay, credit card, google pay, woocommerce
Requires at least: 6.0
Tested up to: 6.2
Requires PHP: 7.3
-Stable tag: 6.5.1
+Stable tag: 6.6.0
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
@@ -94,6 +94,84 @@ Please note that our support for the checkout block is still experimental and th
== Changelog ==
+= 6.6.0 - 2023-10-11 =
+* Add - Add a notice on the Settings page to request JCB capability for Japanese customers.
+* Add - Add current user data to the onboarding init request payload. This data is used for fraud prevention.
+* Add - Added API endpoint to fetch customer's saved payment methods.
+* Add - Added docs for cancel_authorization endpoint
+* Add - Added documentation for create payment intent API endpoint.
+* Add - Added documentation for payment methods API endpoint
+* Add - Add functionality to enable WooPay first party auth behind feature flag.
+* Add - Add helper function/method for raw currency amount conversion.
+* Add - Add Klarna payment method
+* Add - Add loading state to WooPay button
+* Add - Add payment intent creation endpoint
+* Add - Add the feature flag check for pay-for-order flow
+* Add - Add WC blocks spinner to the WooPay checkout styles.
+* Add - Behind a feature flag: dispute message added to transactions screen for disputes not needing a response.
+* Add - Display dispute information, recommended resolution steps, and actions directly on the transaction details screen to help merchants with dispute resolution.
+* Add - Display server error messages on Settings save
+* Add - Expand the data points added to the WooCommerce SSR to include all the main WooPayments features.
+* Add - Handle server-side feature flag for new UPE type enablement.
+* Add - Introduce the "Subscription Relationship" column under the Orders list admin page when HPOS is enabled.
+* Add - Show survey for merchants that disable WooPay.
+* Fix - Add Mix and Match Products support on WooPay.
+* Fix - Add multi-currency enablement check in WooPay session handling.
+* Fix - Comment: Behind a feature flag: Update documentation links (new/changed docs content) when notifying merchant that a dispute needs response.
+* Fix - Disable automatic currency switching and switcher widgets on pay_for_order page.
+* Fix - Ensure renewal orders paid via the Block Checkout are correctly linked to their subscription.
+* Fix - Ensure the order needs processing transient is deleted when a subscription order (eg renewal) is created. Fixes issues with renewal orders going straight to a completed status.
+* Fix - fix: save platform checkout info on blocks
+* Fix - Fix Apple Pay and Google Pay if card payments are disabled.
+* Fix - Fix error when disabling WCPay with core disabled.
+* Fix - Fix init WooPay and empty cart error
+* Fix - Fix modal header alignment on safari browser
+* Fix - Fix onboarding section on MultiCurrency settings page.
+* Fix - Fix WooPay express checkout button with product bundles on product page.
+* Fix - Hide tooltip related to Storefront theme in Multi-Currency settings when Storefront is not the active theme
+* Fix - Improved product details script with enhanced price calculation, and fallbacks for potential undefined values.
+* Fix - Improve escaping around attributes.
+* Fix - Load multi-currency class on setup page.
+* Fix - Missing styles on the Edit Subscription page when HPOS is enabled.
+* Fix - Only request WooPay session data once on blocks pages.
+* Fix - Payment method section missing for Affirm and Afterpay on transaction details page
+* Fix - Prevent charging completed or processing orders with a new payment intent ID
+* Fix - Prevent WooPay-related implementation to modify non-WooPay-specific webhooks by changing their data.
+* Fix - Prevent WooPay multiple redirect requests.
+* Fix - Redirect back to the connect page when attempting to access the new onboarding flow without a server connection.
+* Fix - Redirect back to the pay-for-order page when it's pay-for-order order
+* Fix - Resolved an issue that caused paying for failed/pending parent orders that include Product Add-ons to not calculate the correct total.
+* Fix - Speed up capturing terminal and authorized payments.
+* Fix - Store the correct subscription start date in postmeta and ordermeta when HPOS and data syncing is being used.
+* Fix - Tracking conditions
+* Fix - Virtual variable products no longer require shipping details when checking out with Apple Pay and Google Pay
+* Fix - When HPOS is enabled, deleting a customer will now delete their subscriptions.
+* Fix - When HPOS is enabled, make the orders_by_type_query filter box work in the WooCommerce orders screen.
+* Fix - WooPay save my info phone number fallback for virtual products
+* Update - Adapt the PO congratulations card copy for pending account status.
+* Update - Allow deferred intent creation UPE to support SEPA payments.
+* Update - Enhance design of bnpl payment methods status in settings screen
+* Update - Increase GBP transaction limit for Afterpay
+* Update - Only display the WCPay Subscriptions setting to existing users as part of deprecating this feature.
+* Update - Set WooPay First Party Authentication feature flag to default on.
+* Update - Store customer currencies as an option to avoid expensive calculation.
+* Update - Updated Transaction Details summary with added fee breakdown tooltip for disputed transactions.
+* Update - Update links that pointed to the dispute details screen to point to the transaction details screen
+* Update - Update Name Your Price compatibility to use new Compatibility methods.
+* Update - Update the content of modals that are displayed when deactivating the WooPayments or Woo Subscriptions plugins when the store has active Stripe Billing subscriptions.
+* Update - Update URL used to communicate with WooPay from the iFrame in the merchant site.
+* Dev - Added missing API docs links for payment intents and payment methods API endpoints
+* Dev - Capitalize the JCB label on transactions details page.
+* Dev - e2e tests for progressive onboarding
+* Dev - Extracting payment metadata and level 3 data generation into services.
+* Dev - Migrate away from hooking into actions in certain classes
+* Dev - Move fraud related service hooks out of class constructors and into new init_hooks methods.
+* Dev - Move hooks out of MultiCurrency constructor into own init_hooks method.
+* Dev - Refactored request class send() method
+* Dev - Refactor to move hook initialisation out of constructors.
+* Dev - This work is part of a UI improvements to increase disputes response that is behind a feature flag. A changelog entry will be added to represent the work as a whole.
+* Dev - Update subscriptions-core to 6.3.0.
+
= 6.5.1 - 2023-09-26 =
* Fix - fix incorrect payment method title for non-WooPayments gateways
diff --git a/woocommerce-payments.php b/woocommerce-payments.php
index 457f5262caf..82845cf0a74 100644
--- a/woocommerce-payments.php
+++ b/woocommerce-payments.php
@@ -12,7 +12,7 @@
* WC tested up to: 7.8.0
* Requires at least: 6.0
* Requires PHP: 7.3
- * Version: 6.5.1
+ * Version: 6.6.0
*
* @package WooCommerce\Payments
*/
From bf327edbc4d0f834a98f07c7a389e5f6f3729c41 Mon Sep 17 00:00:00 2001
From: jessy <32092402+jessy-p@users.noreply.github.com>
Date: Wed, 11 Oct 2023 00:10:58 +0530
Subject: [PATCH 97/98] Fix for issue during plugin update (#7447)
Co-authored-by: Jessy P
---
changelog/update-6705-temp-fix-arg-issue | 5 +
...st-payments-payment-intents-controller.php | 341 ----------------
...ents-payment-intents-create-controller.php | 376 ++++++++++++++++++
includes/class-wc-payments.php | 8 +-
...st-payments-payment-intents-controller.php | 10 +-
5 files changed, 392 insertions(+), 348 deletions(-)
create mode 100644 changelog/update-6705-temp-fix-arg-issue
create mode 100644 includes/admin/class-wc-rest-payments-payment-intents-create-controller.php
diff --git a/changelog/update-6705-temp-fix-arg-issue b/changelog/update-6705-temp-fix-arg-issue
new file mode 100644
index 00000000000..c94fd968d6a
--- /dev/null
+++ b/changelog/update-6705-temp-fix-arg-issue
@@ -0,0 +1,5 @@
+Significance: patch
+Type: dev
+Comment: Temp fix by reverting controller constructor and moving code to new file
+
+
diff --git a/includes/admin/class-wc-rest-payments-payment-intents-controller.php b/includes/admin/class-wc-rest-payments-payment-intents-controller.php
index 58ea31b456b..53d02e8afa3 100644
--- a/includes/admin/class-wc-rest-payments-payment-intents-controller.php
+++ b/includes/admin/class-wc-rest-payments-payment-intents-controller.php
@@ -19,27 +19,6 @@
*/
class WC_REST_Payments_Payment_Intents_Controller extends WC_Payments_REST_Controller {
- /**
- * Instance of WC_Payment_Gateway_WCPay
- *
- * @var WC_Payment_Gateway_WCPay
- */
- private $gateway;
-
- /**
- * Order service instance.
- *
- * @var OrderService
- */
- private $order_service;
-
- /**
- * Level3 service instance.
- *
- * @var Level3Service
- */
- private $level3_service;
-
/**
* Endpoint path.
*
@@ -60,37 +39,6 @@ public function register_routes() {
'permission_callback' => [ $this, 'check_permission' ],
]
);
- register_rest_route(
- $this->namespace,
- '/' . $this->rest_base,
- [
- 'methods' => WP_REST_Server::CREATABLE,
- 'callback' => [ $this, 'create_payment_intent' ],
- 'permission_callback' => [ $this, 'check_permission' ],
- 'schema' => [ $this, 'get_item_schema' ],
- ]
- );
- }
-
- /**
- * WC_REST_Payments_Payment_Intents_Controller constructor.
- *
- * @param WC_Payments_API_Client $api_client WooCommerce Payments API client.
- * @param WC_Payment_Gateway_WCPay $gateway WooCommerce Payments payment gateway.
- * @param OrderService $order_service The new order servie.
- * @param Level3Service $level3_service Level3 service instance.
- */
- public function __construct(
- WC_Payments_API_Client $api_client,
- WC_Payment_Gateway_WCPay $gateway,
- OrderService $order_service,
- Level3Service $level3_service
- ) {
- parent::__construct( $api_client );
-
- $this->gateway = $gateway;
- $this->order_service = $order_service;
- $this->level3_service = $level3_service;
}
/**
@@ -104,293 +52,4 @@ public function get_payment_intent( $request ) {
return $this->forward_request( 'get_intent', [ $payment_intent_id ] );
}
- /**
- * Create a payment intent.
- *
- * @param WP_REST_Request $request data about the request.
- *
- * @throws Rest_Request_Exception
- */
- public function create_payment_intent( $request ) {
- try {
-
- $order_id = $request->get_param( 'order_id' );
- $order = wc_get_order( $order_id );
- if ( ! $order ) {
- throw new Rest_Request_Exception( __( 'Order not found', 'woocommerce-payments' ) );
- }
-
- $wcpay_server_request = Create_And_Confirm_Intention::create();
-
- $currency = strtolower( $order->get_currency() );
- $amount = WC_Payments_Utils::prepare_amount( $order->get_total(), $currency );
- $wcpay_server_request->set_currency_code( $currency );
- $wcpay_server_request->set_amount( $amount );
-
- $metadata = $this->order_service->get_payment_metadata( $order_id, Payment_Type::SINGLE() );
- $wcpay_server_request->set_metadata( $metadata );
-
- $wcpay_server_request->set_customer( $request->get_param( 'customer' ) );
- $wcpay_server_request->set_level3( $this->level3_service->get_data_from_order( $order_id ) );
- $wcpay_server_request->set_payment_method( $request->get_param( 'payment_method' ) );
- $wcpay_server_request->set_payment_method_types( [ 'card' ] );
- $wcpay_server_request->set_off_session( true );
- $wcpay_server_request->set_capture_method( $this->gateway->get_option( 'manual_capture' ) && ( 'yes' === $this->gateway->get_option( 'manual_capture' ) ) );
-
- $wcpay_server_request->assign_hook( 'wcpay_create_and_confirm_intent_request_api' );
- $intent = $wcpay_server_request->send();
-
- $response = $this->prepare_item_for_response( $intent, $request );
- return rest_ensure_response( $this->prepare_response_for_collection( $response ) );
-
- } catch ( \Throwable $e ) {
- Logger::error( 'Failed to create an intention via REST API: ' . $e );
- return new WP_Error( 'wcpay_server_error', $e->getMessage(), [ 'status' => 500 ] );
- }
- }
-
-
- /**
- * Item schema.
- *
- * @return array
- */
- public function get_item_schema() {
- return [
- '$schema' => 'http://json-schema.org/draft-04/schema#',
- 'title' => 'payment_intent',
- 'type' => 'object',
- 'properties' => [
- 'id' => [
- 'description' => __( 'ID for the payment intent.', 'woocommerce-payments' ),
- 'type' => 'string',
- 'context' => [ 'view' ],
- ],
- 'amount' => [
- 'description' => __( 'The amount of the transaction.', 'woocommerce-payments' ),
- 'type' => 'integer',
- 'context' => [ 'view' ],
- ],
- 'currency' => [
- 'description' => __( 'The currency of the transaction.', 'woocommerce-payments' ),
- 'type' => 'string',
- 'context' => [ 'view' ],
- ],
- 'created' => [
- 'description' => __( 'Timestamp for when the payment intent was created.', 'woocommerce-payments' ),
- 'type' => 'string',
- 'context' => [ 'view' ],
- ],
- 'customer' => [
- 'description' => __( 'The customer id of the intent', 'woocommerce-payments' ),
- 'type' => 'string',
- 'context' => [ 'view' ],
- ],
- 'status' => [
- 'description' => __( 'The status of the payment intent.', 'woocommerce-payments' ),
- 'type' => 'string',
- 'context' => [ 'view' ],
- ],
- 'charge' => [
- 'description' => __( 'Charge object associated with this payment intention.', 'woocommerce-payments' ),
- 'type' => 'object',
- 'context' => [ 'view' ],
- 'properties' => [
- 'id' => [
- 'description' => 'ID for the charge.',
- 'type' => 'string',
- 'context' => [ 'view' ],
- ],
- 'amount' => [
- 'description' => 'The amount of the charge.',
- 'type' => 'integer',
- 'context' => [ 'view' ],
- ],
- 'payment_method_details' => [
- 'description' => 'Details for the payment method used for the charge.',
- 'type' => 'object',
- 'properties' => [
- 'card' => [
- 'description' => 'Details for a card payment method.',
- 'type' => 'object',
- 'properties' => [
- 'amount_authorized' => [
- 'description' => 'The amount authorized by the card.',
- 'type' => 'integer',
- ],
- 'brand' => [
- 'description' => 'The brand of the card.',
- 'type' => 'string',
- ],
- 'capture_before' => [
- 'description' => 'Timestamp for when the authorization must be captured.',
- 'type' => 'string',
- ],
- 'country' => [
- 'description' => 'The ISO country code.',
- 'type' => 'string',
- ],
- 'exp_month' => [
- 'description' => 'The expiration month of the card.',
- 'type' => 'integer',
- ],
- 'exp_year' => [
- 'description' => 'The expiration year of the card.',
- 'type' => 'integer',
- ],
- 'last4' => [
- 'description' => 'The last 4 digits of the card.',
- 'type' => 'string',
- ],
- 'three_d_secure' => [
- 'description' => 'Details for 3D Secure authentication.',
- 'type' => 'object',
- ],
- ],
- ],
- ],
- ],
- 'billing_details' => [
- 'description' => __( 'Billing details for the payment method.', 'woocommerce-payments' ),
- 'type' => 'object',
- 'context' => [ 'view' ],
- 'properties' => [
- 'address' => [
- 'description' => __( 'Address associated with the billing details.', 'woocommerce-payments' ),
- 'type' => 'object',
- 'context' => [ 'view' ],
- 'properties' => [
- 'city' => [
- 'description' => __( 'City of the billing address.', 'woocommerce-payments' ),
- 'type' => 'string',
- 'context' => [ 'view' ],
- ],
- 'country' => [
- 'description' => __( 'Country of the billing address.', 'woocommerce-payments' ),
- 'type' => 'string',
- 'context' => [ 'view' ],
- ],
- 'line1' => [
- 'description' => __( 'Line 1 of the billing address.', 'woocommerce-payments' ),
- 'type' => 'string',
- 'context' => [ 'view' ],
- ],
- 'line2' => [
- 'description' => __( 'Line 2 of the billing address.', 'woocommerce-payments' ),
- 'type' => 'string',
- 'context' => [ 'view' ],
- ],
- 'postal_code' => [
- 'description' => __( 'Postal code of the billing address.', 'woocommerce-payments' ),
- 'type' => 'string',
- 'context' => [ 'view' ],
- ],
- 'state' => [
- 'description' => __( 'State of the billing address.', 'woocommerce-payments' ),
- 'type' => 'string',
- 'context' => [ 'view' ],
- ],
- ],
- ],
- 'email' => [
- 'description' => __( 'Email associated with the billing details.', 'woocommerce-payments' ),
- 'type' => 'string',
- 'format' => 'email',
- 'context' => [ 'view' ],
- ],
- 'name' => [
- 'description' => __( 'Name associated with the billing details.', 'woocommerce-payments' ),
- 'type' => 'string',
- 'context' => [ 'view' ],
- ],
- 'phone' => [
- 'description' => __( 'Phone number associated with the billing details.', 'woocommerce-payments' ),
- 'type' => 'string',
- 'context' => [ 'view' ],
- ],
- ],
- ],
- 'payment_method' => [
- 'description' => 'The payment method associated with this charge.',
- 'type' => 'string',
- 'context' => [ 'view' ],
- ],
- 'application_fee_amount' => [
- 'description' => 'The application fee amount.',
- 'type' => 'integer',
- 'context' => [ 'view' ],
- ],
- 'status' => [
- 'description' => 'The status of the payment intent created.',
- 'type' => 'string',
- 'context' => [ 'view' ],
- ],
- ],
- ],
-
- ],
- ];
- }
-
- /**
- * Prepare each item for response.
- *
- * @param array|mixed $item Item to prepare.
- * @param WP_REST_Request $request Request instance.
- *
- * @return WP_REST_Response|WP_Error|WP_REST_Response
- */
- public function prepare_item_for_response( $item, $request ) {
- $prepared_item = [];
- $prepared_item['id'] = $item->get_id();
- $prepared_item['amount'] = $item->get_amount();
- $prepared_item['currency'] = $item->get_currency();
- $prepared_item['created'] = $item->get_created()->getTimestamp();
- $prepared_item['customer'] = $item->get_customer_id();
- $prepared_item['payment_method'] = $item->get_payment_method_id();
- $prepared_item['status'] = $item->get_status();
-
- try {
- $charge = $item->get_charge();
- $prepared_item['charge']['id'] = $charge->get_id();
- $prepared_item['charge']['amount'] = $charge->get_amount();
- $prepared_item['charge']['application_fee_amount'] = $charge->get_application_fee_amount();
- $prepared_item['charge']['status'] = $charge->get_status();
-
- $billing_details = $charge->get_billing_details();
- if ( isset( $billing_details['address'] ) ) {
- $prepared_item['charge']['billing_details']['address']['city'] = $billing_details['address']['city'] ?? '';
- $prepared_item['charge']['billing_details']['address']['country'] = $billing_details['address']['country'] ?? '';
- $prepared_item['charge']['billing_details']['address']['line1'] = $billing_details['address']['line1'] ?? '';
- $prepared_item['charge']['billing_details']['address']['line2'] = $billing_details['address']['line2'] ?? '';
- $prepared_item['charge']['billing_details']['address']['postal_code'] = $billing_details['address']['postal_code'] ?? '';
- $prepared_item['charge']['billing_details']['address']['state'] = $billing_details['address']['state'] ?? '';
- }
- $prepared_item['charge']['billing_details']['email'] = $billing_details['email'] ?? '';
- $prepared_item['charge']['billing_details']['name'] = $billing_details['name'] ?? '';
- $prepared_item['charge']['billing_details']['phone'] = $billing_details['phone'] ?? '';
-
- $payment_method_details = $charge->get_payment_method_details();
- if ( isset( $payment_method_details['card'] ) ) {
- $prepared_item['charge']['payment_method_details']['card']['amount_authorized'] = $payment_method_details['card']['amount_authorized'] ?? '';
- $prepared_item['charge']['payment_method_details']['card']['brand'] = $payment_method_details['card']['brand'] ?? '';
- $prepared_item['charge']['payment_method_details']['card']['capture_before'] = $payment_method_details['card']['capture_before'] ?? '';
- $prepared_item['charge']['payment_method_details']['card']['country'] = $payment_method_details['card']['country'] ?? '';
- $prepared_item['charge']['payment_method_details']['card']['exp_month'] = $payment_method_details['card']['exp_month'] ?? '';
- $prepared_item['charge']['payment_method_details']['card']['exp_year'] = $payment_method_details['card']['exp_year'] ?? '';
- $prepared_item['charge']['payment_method_details']['card']['last4'] = $payment_method_details['card']['last4'] ?? '';
- $prepared_item['charge']['payment_method_details']['card']['three_d_secure'] = $payment_method_details['card']['three_d_secure'] ?? '';
- }
- } catch ( \Throwable $e ) {
- Logger::error( 'Failed to prepare payment intent for response: ' . $e );
- }
-
- $context = $request['context'] ?? 'view';
- $prepared_item = $this->add_additional_fields_to_object( $prepared_item, $request );
- $prepared_item = $this->filter_response_by_context( $prepared_item, $context );
-
- return rest_ensure_response( $prepared_item );
- }
-
-
}
diff --git a/includes/admin/class-wc-rest-payments-payment-intents-create-controller.php b/includes/admin/class-wc-rest-payments-payment-intents-create-controller.php
new file mode 100644
index 00000000000..37b711ebc2e
--- /dev/null
+++ b/includes/admin/class-wc-rest-payments-payment-intents-create-controller.php
@@ -0,0 +1,376 @@
+namespace,
+ '/' . $this->rest_base,
+ [
+ 'methods' => WP_REST_Server::CREATABLE,
+ 'callback' => [ $this, 'create_payment_intent' ],
+ 'permission_callback' => [ $this, 'check_permission' ],
+ 'schema' => [ $this, 'get_item_schema' ],
+ ]
+ );
+ }
+
+ /**
+ * WC_REST_Payments_Payment_Intents_Create_Controller constructor.
+ *
+ * @param WC_Payments_API_Client $api_client WooCommerce Payments API client.
+ * @param WC_Payment_Gateway_WCPay $gateway WooCommerce Payments payment gateway.
+ * @param OrderService $order_service The new order servie.
+ * @param Level3Service $level3_service Level3 service instance.
+ */
+ public function __construct(
+ WC_Payments_API_Client $api_client,
+ WC_Payment_Gateway_WCPay $gateway,
+ OrderService $order_service,
+ Level3Service $level3_service
+ ) {
+ parent::__construct( $api_client );
+
+ $this->gateway = $gateway;
+ $this->order_service = $order_service;
+ $this->level3_service = $level3_service;
+ }
+
+ /**
+ * Create a payment intent.
+ *
+ * @param WP_REST_Request $request data about the request.
+ *
+ * @throws Rest_Request_Exception
+ */
+ public function create_payment_intent( $request ) {
+ try {
+
+ $order_id = $request->get_param( 'order_id' );
+ $order = wc_get_order( $order_id );
+ if ( ! $order ) {
+ throw new Rest_Request_Exception( __( 'Order not found', 'woocommerce-payments' ) );
+ }
+
+ $wcpay_server_request = Create_And_Confirm_Intention::create();
+
+ $currency = strtolower( $order->get_currency() );
+ $amount = WC_Payments_Utils::prepare_amount( $order->get_total(), $currency );
+ $wcpay_server_request->set_currency_code( $currency );
+ $wcpay_server_request->set_amount( $amount );
+
+ $metadata = $this->order_service->get_payment_metadata( $order_id, Payment_Type::SINGLE() );
+ $wcpay_server_request->set_metadata( $metadata );
+
+ $wcpay_server_request->set_customer( $request->get_param( 'customer' ) );
+ $wcpay_server_request->set_level3( $this->level3_service->get_data_from_order( $order_id ) );
+ $wcpay_server_request->set_payment_method( $request->get_param( 'payment_method' ) );
+ $wcpay_server_request->set_payment_method_types( [ 'card' ] );
+ $wcpay_server_request->set_off_session( true );
+ $wcpay_server_request->set_capture_method( $this->gateway->get_option( 'manual_capture' ) && ( 'yes' === $this->gateway->get_option( 'manual_capture' ) ) );
+
+ $wcpay_server_request->assign_hook( 'wcpay_create_and_confirm_intent_request_api' );
+ $intent = $wcpay_server_request->send();
+
+ $response = $this->prepare_item_for_response( $intent, $request );
+ return rest_ensure_response( $this->prepare_response_for_collection( $response ) );
+
+ } catch ( \Throwable $e ) {
+ Logger::error( 'Failed to create an intention via REST API: ' . $e );
+ return new WP_Error( 'wcpay_server_error', $e->getMessage(), [ 'status' => 500 ] );
+ }
+ }
+
+
+ /**
+ * Item schema.
+ *
+ * @return array
+ */
+ public function get_item_schema() {
+ return [
+ '$schema' => 'http://json-schema.org/draft-04/schema#',
+ 'title' => 'payment_intent',
+ 'type' => 'object',
+ 'properties' => [
+ 'id' => [
+ 'description' => __( 'ID for the payment intent.', 'woocommerce-payments' ),
+ 'type' => 'string',
+ 'context' => [ 'view' ],
+ ],
+ 'amount' => [
+ 'description' => __( 'The amount of the transaction.', 'woocommerce-payments' ),
+ 'type' => 'integer',
+ 'context' => [ 'view' ],
+ ],
+ 'currency' => [
+ 'description' => __( 'The currency of the transaction.', 'woocommerce-payments' ),
+ 'type' => 'string',
+ 'context' => [ 'view' ],
+ ],
+ 'created' => [
+ 'description' => __( 'Timestamp for when the payment intent was created.', 'woocommerce-payments' ),
+ 'type' => 'string',
+ 'context' => [ 'view' ],
+ ],
+ 'customer' => [
+ 'description' => __( 'The customer id of the intent', 'woocommerce-payments' ),
+ 'type' => 'string',
+ 'context' => [ 'view' ],
+ ],
+ 'status' => [
+ 'description' => __( 'The status of the payment intent.', 'woocommerce-payments' ),
+ 'type' => 'string',
+ 'context' => [ 'view' ],
+ ],
+ 'charge' => [
+ 'description' => __( 'Charge object associated with this payment intention.', 'woocommerce-payments' ),
+ 'type' => 'object',
+ 'context' => [ 'view' ],
+ 'properties' => [
+ 'id' => [
+ 'description' => 'ID for the charge.',
+ 'type' => 'string',
+ 'context' => [ 'view' ],
+ ],
+ 'amount' => [
+ 'description' => 'The amount of the charge.',
+ 'type' => 'integer',
+ 'context' => [ 'view' ],
+ ],
+ 'payment_method_details' => [
+ 'description' => 'Details for the payment method used for the charge.',
+ 'type' => 'object',
+ 'properties' => [
+ 'card' => [
+ 'description' => 'Details for a card payment method.',
+ 'type' => 'object',
+ 'properties' => [
+ 'amount_authorized' => [
+ 'description' => 'The amount authorized by the card.',
+ 'type' => 'integer',
+ ],
+ 'brand' => [
+ 'description' => 'The brand of the card.',
+ 'type' => 'string',
+ ],
+ 'capture_before' => [
+ 'description' => 'Timestamp for when the authorization must be captured.',
+ 'type' => 'string',
+ ],
+ 'country' => [
+ 'description' => 'The ISO country code.',
+ 'type' => 'string',
+ ],
+ 'exp_month' => [
+ 'description' => 'The expiration month of the card.',
+ 'type' => 'integer',
+ ],
+ 'exp_year' => [
+ 'description' => 'The expiration year of the card.',
+ 'type' => 'integer',
+ ],
+ 'last4' => [
+ 'description' => 'The last 4 digits of the card.',
+ 'type' => 'string',
+ ],
+ 'three_d_secure' => [
+ 'description' => 'Details for 3D Secure authentication.',
+ 'type' => 'object',
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'billing_details' => [
+ 'description' => __( 'Billing details for the payment method.', 'woocommerce-payments' ),
+ 'type' => 'object',
+ 'context' => [ 'view' ],
+ 'properties' => [
+ 'address' => [
+ 'description' => __( 'Address associated with the billing details.', 'woocommerce-payments' ),
+ 'type' => 'object',
+ 'context' => [ 'view' ],
+ 'properties' => [
+ 'city' => [
+ 'description' => __( 'City of the billing address.', 'woocommerce-payments' ),
+ 'type' => 'string',
+ 'context' => [ 'view' ],
+ ],
+ 'country' => [
+ 'description' => __( 'Country of the billing address.', 'woocommerce-payments' ),
+ 'type' => 'string',
+ 'context' => [ 'view' ],
+ ],
+ 'line1' => [
+ 'description' => __( 'Line 1 of the billing address.', 'woocommerce-payments' ),
+ 'type' => 'string',
+ 'context' => [ 'view' ],
+ ],
+ 'line2' => [
+ 'description' => __( 'Line 2 of the billing address.', 'woocommerce-payments' ),
+ 'type' => 'string',
+ 'context' => [ 'view' ],
+ ],
+ 'postal_code' => [
+ 'description' => __( 'Postal code of the billing address.', 'woocommerce-payments' ),
+ 'type' => 'string',
+ 'context' => [ 'view' ],
+ ],
+ 'state' => [
+ 'description' => __( 'State of the billing address.', 'woocommerce-payments' ),
+ 'type' => 'string',
+ 'context' => [ 'view' ],
+ ],
+ ],
+ ],
+ 'email' => [
+ 'description' => __( 'Email associated with the billing details.', 'woocommerce-payments' ),
+ 'type' => 'string',
+ 'format' => 'email',
+ 'context' => [ 'view' ],
+ ],
+ 'name' => [
+ 'description' => __( 'Name associated with the billing details.', 'woocommerce-payments' ),
+ 'type' => 'string',
+ 'context' => [ 'view' ],
+ ],
+ 'phone' => [
+ 'description' => __( 'Phone number associated with the billing details.', 'woocommerce-payments' ),
+ 'type' => 'string',
+ 'context' => [ 'view' ],
+ ],
+ ],
+ ],
+ 'payment_method' => [
+ 'description' => 'The payment method associated with this charge.',
+ 'type' => 'string',
+ 'context' => [ 'view' ],
+ ],
+ 'application_fee_amount' => [
+ 'description' => 'The application fee amount.',
+ 'type' => 'integer',
+ 'context' => [ 'view' ],
+ ],
+ 'status' => [
+ 'description' => 'The status of the payment intent created.',
+ 'type' => 'string',
+ 'context' => [ 'view' ],
+ ],
+ ],
+ ],
+
+ ],
+ ];
+ }
+
+ /**
+ * Prepare each item for response.
+ *
+ * @param array|mixed $item Item to prepare.
+ * @param WP_REST_Request $request Request instance.
+ *
+ * @return WP_REST_Response|WP_Error|WP_REST_Response
+ */
+ public function prepare_item_for_response( $item, $request ) {
+ $prepared_item = [];
+ $prepared_item['id'] = $item->get_id();
+ $prepared_item['amount'] = $item->get_amount();
+ $prepared_item['currency'] = $item->get_currency();
+ $prepared_item['created'] = $item->get_created()->getTimestamp();
+ $prepared_item['customer'] = $item->get_customer_id();
+ $prepared_item['payment_method'] = $item->get_payment_method_id();
+ $prepared_item['status'] = $item->get_status();
+
+ try {
+ $charge = $item->get_charge();
+ $prepared_item['charge']['id'] = $charge->get_id();
+ $prepared_item['charge']['amount'] = $charge->get_amount();
+ $prepared_item['charge']['application_fee_amount'] = $charge->get_application_fee_amount();
+ $prepared_item['charge']['status'] = $charge->get_status();
+
+ $billing_details = $charge->get_billing_details();
+ if ( isset( $billing_details['address'] ) ) {
+ $prepared_item['charge']['billing_details']['address']['city'] = $billing_details['address']['city'] ?? '';
+ $prepared_item['charge']['billing_details']['address']['country'] = $billing_details['address']['country'] ?? '';
+ $prepared_item['charge']['billing_details']['address']['line1'] = $billing_details['address']['line1'] ?? '';
+ $prepared_item['charge']['billing_details']['address']['line2'] = $billing_details['address']['line2'] ?? '';
+ $prepared_item['charge']['billing_details']['address']['postal_code'] = $billing_details['address']['postal_code'] ?? '';
+ $prepared_item['charge']['billing_details']['address']['state'] = $billing_details['address']['state'] ?? '';
+ }
+ $prepared_item['charge']['billing_details']['email'] = $billing_details['email'] ?? '';
+ $prepared_item['charge']['billing_details']['name'] = $billing_details['name'] ?? '';
+ $prepared_item['charge']['billing_details']['phone'] = $billing_details['phone'] ?? '';
+
+ $payment_method_details = $charge->get_payment_method_details();
+ if ( isset( $payment_method_details['card'] ) ) {
+ $prepared_item['charge']['payment_method_details']['card']['amount_authorized'] = $payment_method_details['card']['amount_authorized'] ?? '';
+ $prepared_item['charge']['payment_method_details']['card']['brand'] = $payment_method_details['card']['brand'] ?? '';
+ $prepared_item['charge']['payment_method_details']['card']['capture_before'] = $payment_method_details['card']['capture_before'] ?? '';
+ $prepared_item['charge']['payment_method_details']['card']['country'] = $payment_method_details['card']['country'] ?? '';
+ $prepared_item['charge']['payment_method_details']['card']['exp_month'] = $payment_method_details['card']['exp_month'] ?? '';
+ $prepared_item['charge']['payment_method_details']['card']['exp_year'] = $payment_method_details['card']['exp_year'] ?? '';
+ $prepared_item['charge']['payment_method_details']['card']['last4'] = $payment_method_details['card']['last4'] ?? '';
+ $prepared_item['charge']['payment_method_details']['card']['three_d_secure'] = $payment_method_details['card']['three_d_secure'] ?? '';
+ }
+ } catch ( \Throwable $e ) {
+ Logger::error( 'Failed to prepare payment intent for response: ' . $e );
+ }
+
+ $context = $request['context'] ?? 'view';
+ $prepared_item = $this->add_additional_fields_to_object( $prepared_item, $request );
+ $prepared_item = $this->filter_response_by_context( $prepared_item, $context );
+
+ return rest_ensure_response( $prepared_item );
+ }
+
+
+}
diff --git a/includes/class-wc-payments.php b/includes/class-wc-payments.php
index bee75837a4a..68a46ce4cfc 100644
--- a/includes/class-wc-payments.php
+++ b/includes/class-wc-payments.php
@@ -1073,13 +1073,17 @@ public static function init_rest_api() {
}
include_once WCPAY_ABSPATH . 'includes/admin/class-wc-rest-payments-payment-intents-controller.php';
- $payment_intents_controller = new WC_REST_Payments_Payment_Intents_Controller(
+ $payment_intents_controller = new WC_REST_Payments_Payment_Intents_Controller( self::$api_client );
+ $payment_intents_controller->register_routes();
+
+ include_once WCPAY_ABSPATH . 'includes/admin/class-wc-rest-payments-payment-intents-create-controller.php';
+ $payment_intents_create_controller = new WC_REST_Payments_Payment_Intents_Create_Controller(
self::$api_client,
self::get_gateway(),
wcpay_get_container()->get( OrderService::class ),
wcpay_get_container()->get( Level3Service::class )
);
- $payment_intents_controller->register_routes();
+ $payment_intents_create_controller->register_routes();
include_once WCPAY_ABSPATH . 'includes/admin/class-wc-rest-payments-authorizations-controller.php';
$authorizations_controller = new WC_REST_Payments_Authorizations_Controller( self::$api_client );
diff --git a/tests/unit/admin/test-class-wc-rest-payments-payment-intents-controller.php b/tests/unit/admin/test-class-wc-rest-payments-payment-intents-controller.php
index 731727a3daf..1bdb96fe54b 100644
--- a/tests/unit/admin/test-class-wc-rest-payments-payment-intents-controller.php
+++ b/tests/unit/admin/test-class-wc-rest-payments-payment-intents-controller.php
@@ -1,6 +1,6 @@
mock_order_service = $this->createMock( OrderService::class );
$this->mock_level3_service = $this->createMock( Level3Service::class );
- $this->controller = new WC_REST_Payments_Payment_Intents_Controller(
+ $this->controller = new WC_REST_Payments_Payment_Intents_Create_Controller(
$this->mock_api_client,
$this->mock_gateway,
$this->mock_order_service,
From 5f471fb74650775dbf30cdb2d94b03478d739520 Mon Sep 17 00:00:00 2001
From: botwoo
Date: Tue, 10 Oct 2023 18:59:36 +0000
Subject: [PATCH 98/98] Amend changelog entries for release 6.6.0
---
changelog/update-6705-temp-fix-arg-issue | 5 -----
1 file changed, 5 deletions(-)
delete mode 100644 changelog/update-6705-temp-fix-arg-issue
diff --git a/changelog/update-6705-temp-fix-arg-issue b/changelog/update-6705-temp-fix-arg-issue
deleted file mode 100644
index c94fd968d6a..00000000000
--- a/changelog/update-6705-temp-fix-arg-issue
+++ /dev/null
@@ -1,5 +0,0 @@
-Significance: patch
-Type: dev
-Comment: Temp fix by reverting controller constructor and moving code to new file
-
-