-
 ); ?>)
+
 ); ?>)
get_meta( 'last4' ) ) {
echo esc_html_e( 'Card ending in', 'woocommerce-payments' ) . ' ';
@@ -510,18 +517,18 @@ public function add_multibanco_payment_instructions_to_order_on_hold_email( $ord
if ( $plain_text ) {
echo "----------------------------------------\n";
- echo __( 'Multibanco Payment instructions', 'woocommerce-payments' ) . "\n\n";
+ echo esc_html__( 'Multibanco Payment instructions', 'woocommerce-payments' ) . "\n\n";
printf(
/* translators: %s: expiry date */
- __( 'Expires %s', 'woocommerce-payments' ) . "\n\n",
- $expiry_date
+ esc_html__( 'Expires %s', 'woocommerce-payments' ) . "\n\n",
+ esc_html( $expiry_date )
);
- echo '1. ' . __( 'In your online bank account or from an ATM, choose "Payment and other services".', 'woocommerce-payments' ) . "\n";
- echo '2. ' . __( 'Click "Payments of services/shopping".', 'woocommerce-payments' ) . "\n";
- echo '3. ' . __( 'Enter the entity number, reference number, and amount.', 'woocommerce-payments' ) . "\n\n";
- echo __( 'Entity', 'woocommerce-payments' ) . ': ' . $multibanco_info['entity'] . "\n";
- echo __( 'Reference', 'woocommerce-payments' ) . ': ' . $multibanco_info['reference'] . "\n";
- echo __( 'Amount', 'woocommerce-payments' ) . ': ' . wp_strip_all_tags( $formatted_order_total ) . "\n";
+ echo '1. ' . esc_html__( 'In your online bank account or from an ATM, choose "Payment and other services".', 'woocommerce-payments' ) . "\n";
+ echo '2. ' . esc_html__( 'Click "Payments of services/shopping".', 'woocommerce-payments' ) . "\n";
+ echo '3. ' . esc_html__( 'Enter the entity number, reference number, and amount.', 'woocommerce-payments' ) . "\n\n";
+ echo esc_html__( 'Entity', 'woocommerce-payments' ) . ': ' . esc_html( $multibanco_info['entity'] ) . "\n";
+ echo esc_html__( 'Reference', 'woocommerce-payments' ) . ': ' . esc_html( $multibanco_info['reference'] ) . "\n";
+ echo esc_html__( 'Amount', 'woocommerce-payments' ) . ': ' . esc_html( wp_strip_all_tags( $formatted_order_total ) ) . "\n";
echo "----------------------------------------\n\n";
} else {
?>
@@ -542,7 +549,7 @@ public function add_multibanco_payment_instructions_to_order_on_hold_email( $ord
get_order_number() );
+ echo esc_html( sprintf( __( 'Order #%s', 'woocommerce-payments' ), $order->get_order_number() ) );
?>
|
@@ -550,14 +557,14 @@ public function add_multibanco_payment_instructions_to_order_on_hold_email( $ord
%s', 'woocommerce-payments' ),
[
'strong' => '',
]
),
- $expiry_date
+ esc_html( $expiry_date )
);
?>
|
@@ -584,7 +591,7 @@ public function add_multibanco_payment_instructions_to_order_on_hold_email( $ord
|
- |
+ |
diff --git a/includes/class-wc-payments-payment-request-session.php b/includes/class-wc-payments-payment-request-session.php
index 79582261e6d..78590cf9676 100644
--- a/includes/class-wc-payments-payment-request-session.php
+++ b/includes/class-wc-payments-payment-request-session.php
@@ -37,7 +37,6 @@ class WC_Payments_Payment_Request_Session {
public function init() {
// adding this filter with a higher priority than the session handler of the Store API.
add_filter( 'woocommerce_session_handler', [ $this, 'add_payment_request_store_api_session_handler' ], 20 );
- add_filter( 'rest_post_dispatch', [ $this, 'store_api_headers' ], 10, 3 );
// checking to ensure we're not erasing the cart on the "order received" page.
if ( $this->is_custom_session_order_received_page() ) {
@@ -124,6 +123,19 @@ protected function get_session_token() {
);
}
+ /**
+ * Adding the session key to the Store API response, to ensure the session can be retrieved later.
+ *
+ * @param mixed $response Response to replace the requested version with.
+ *
+ * @return mixed
+ */
+ public function store_api_headers( $response ) {
+ $response->header( 'X-WooPayments-Tokenized-Cart-Session', $this->get_session_token() );
+
+ return $response;
+ }
+
/**
* Adding the session key to the Store API response, to ensure the session can be retrieved later.
*
@@ -133,18 +145,17 @@ protected function get_session_token() {
*
* @return mixed
*/
- public function store_api_headers( $response, $server, $request ) {
- if ( ! \WC_Payments_Utils::is_store_api_request() ) {
- return $response;
- }
-
- $nonce = $request->get_header( 'X-WooPayments-Tokenized-Cart-Session-Nonce' );
- if ( ! wp_verify_nonce( $nonce, 'woopayments_tokenized_cart_session_nonce' ) ) {
- return $response;
+ public function maybe_clear_cart_data( $response, $server, $request ) {
+ if ( $request->get_header( 'X-WooPayments-Tokenized-Cart-Is-Ephemeral-Cart' ) === '1' ) {
+ // the customer id value doesn't matter.
+ // in this case, we'll be using the `WC_Payments_Payment_Request_Session_Handler` session handler,
+ // which will use the correct customer ID to delete.
+ // I am specifically calling `delete_session` instead of `forget_session` or `destroy_session`,
+ // because those methods might delete the customer's cookies (which we want to keep).
+ WC()->session->delete_session( 0 );
+ WC()->cart->empty_cart();
}
- $response->header( 'X-WooPayments-Tokenized-Cart-Session', $this->get_session_token() );
-
return $response;
}
@@ -183,6 +194,10 @@ public function add_payment_request_store_api_session_handler( $default_session_
add_filter( 'woocommerce_persistent_cart_enabled', '__return_false' );
// when an order is placed via the Store API on product pages, we need to slightly modify the "order received" URL.
add_filter( 'woocommerce_get_return_url', [ $this, 'store_api_order_received_return_url' ] );
+ // ensuring that the `X-WooPayments-Tokenized-Cart-Session` response header is added to the response.
+ add_filter( 'rest_post_dispatch', [ $this, 'store_api_headers' ] );
+ // clearing the cart contents if the request is made just to fetch product attributes and prices.
+ add_filter( 'rest_post_dispatch', [ $this, 'maybe_clear_cart_data' ], 10, 3 );
require_once WCPAY_ABSPATH . '/includes/class-wc-payments-payment-request-session-handler.php';
diff --git a/includes/class-wc-payments-utils.php b/includes/class-wc-payments-utils.php
index dd38b8d2c09..c84ebdd6742 100644
--- a/includes/class-wc-payments-utils.php
+++ b/includes/class-wc-payments-utils.php
@@ -718,6 +718,9 @@ public static function get_filtered_error_status_code( Exception $e ): int {
/**
* Get the BNPL limits per currency for a specific payment method.
*
+ * FLAG: PAYMENT_METHODS_LIST
+ * This can be replaced once all BNPL methods are converted to use definitions.
+ *
* @param string $payment_method The payment method name ('affirm', 'afterpay_clearpay', or 'klarna').
* @return array The BNPL limits per currency for the specified payment method.
*/
@@ -1350,15 +1353,14 @@ public static function is_cart_page(): bool {
}
/**
- * Block based themes display the cart block even when the cart shortcode is used. has_block() isn't effective
- * in this case because it checks the page content for the block, which isn't present.
+ * Determine if the current page is a cart block.
*
- * @return bool
+ * @return bool True if the current page is a cart block, false otherwise.
*
* @psalm-suppress UndefinedFunction
*/
public static function is_cart_block(): bool {
- return has_block( 'woocommerce/cart' ) || ( wp_is_block_theme() && is_cart() );
+ return has_block( 'woocommerce/cart' );
}
/**
diff --git a/includes/class-wc-payments-webhook-processing-service.php b/includes/class-wc-payments-webhook-processing-service.php
index 4a4268b9cfc..3ce4258b21e 100644
--- a/includes/class-wc-payments-webhook-processing-service.php
+++ b/includes/class-wc-payments-webhook-processing-service.php
@@ -14,6 +14,7 @@
use WCPay\Exceptions\Order_Not_Found_Exception;
use WCPay\Exceptions\Rest_Request_Exception;
use WCPay\Logger;
+use WCPay\Constants\Refund_Status;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
@@ -248,17 +249,15 @@ private function process_webhook_refund_updated( $event_body ) {
$event_data = $this->read_webhook_property( $event_body, 'data' );
$event_object = $this->read_webhook_property( $event_data, 'object' );
- // First, check the reason for the update. We're only interested in a status of failed.
- $status = $this->read_webhook_property( $event_object, 'status' );
- if ( 'failed' !== $status ) {
- return;
- }
-
// Fetch the details of the failed refund so that we can find the associated order and write a note.
- $charge_id = $this->read_webhook_property( $event_object, 'charge' );
- $refund_id = $this->read_webhook_property( $event_object, 'id' );
- $amount = $this->read_webhook_property( $event_object, 'amount' );
- $currency = $this->read_webhook_property( $event_object, 'currency' );
+ $charge_id = $this->read_webhook_property( $event_object, 'charge' );
+ $refund_id = $this->read_webhook_property( $event_object, 'id' );
+ $amount = $this->read_webhook_property( $event_object, 'amount' );
+ $currency = $this->read_webhook_property( $event_object, 'currency' );
+ $status = $this->read_webhook_property( $event_object, 'status' );
+ $balance_transaction = $this->has_webhook_property( $event_object, 'balance_transaction' )
+ ? $this->read_webhook_property( $event_object, 'balance_transaction' )
+ : null;
// Look up the order related to this charge.
$order = $this->wcpay_db->order_from_charge_id( $charge_id );
@@ -273,29 +272,9 @@ private function process_webhook_refund_updated( $event_body ) {
);
}
- $note = sprintf(
- WC_Payments_Utils::esc_interpolated_html(
- /* translators: %1: the refund amount, %2: WooPayments, %3: ID of the refund */
- __( 'A refund of %1$s was
unsuccessful using %2$s (
%3$s).', 'woocommerce-payments' ),
- [
- 'strong' => '
',
- 'code' => '',
- ]
- ),
- WC_Payments_Explicit_Price_Formatter::get_explicit_price(
- wc_price( WC_Payments_Utils::interpret_stripe_amount( $amount, $currency ), [ 'currency' => strtoupper( $currency ) ] ),
- $order
- ),
- 'WooPayments',
- $refund_id
- );
-
- if ( $this->order_service->order_note_exists( $order, $note ) ) {
- return;
- }
-
+ $matched_wc_refund = null;
/**
- * Get refunds from order and delete refund if matches wcpay refund id.
+ * Get the WC_Refund from the WCPay refund ID.
*
* @var $wc_refunds WC_Order_Refund[]
* */
@@ -304,34 +283,33 @@ private function process_webhook_refund_updated( $event_body ) {
foreach ( $wc_refunds as $wc_refund ) {
$wcpay_refund_id = $this->order_service->get_wcpay_refund_id_for_order( $wc_refund );
if ( $refund_id === $wcpay_refund_id ) {
- // Delete WC Refund.
- $wc_refund->delete();
+ $matched_wc_refund = $wc_refund;
break;
}
}
}
- // Update order status if order is fully refunded.
- $current_order_status = $order->get_status();
- if ( Order_Status::REFUNDED === $current_order_status ) {
- $order->update_status( Order_Status::FAILED );
- }
-
- $order->add_order_note( $note );
- $this->order_service->set_wcpay_refund_status_for_order( $order, 'failed' );
- $order->save();
-
- try {
- $failure_reason = $this->read_webhook_property( $event_object, 'failure_reason' );
-
- if ( 'insufficient_funds' === $failure_reason ) {
- $this->order_service->handle_insufficient_balance_for_refund(
- $order,
- $amount
- );
- }
- } catch ( Exception $e ) {
- Logger::debug( 'Failed to handle insufficient balance for refund: ' . $e->getMessage() );
+ // Refund update webhook events can be either failed, cancelled (basically it's also a failure but triggered by the merchant), succeeded only.
+ switch ( $status ) {
+ case Refund_Status::FAILED:
+ $this->order_service->handle_failed_refund( $order, $refund_id, $amount, $currency, $matched_wc_refund );
+ if (
+ $this->has_webhook_property( $event_object, 'failure_reason' )
+ && 'insufficient_funds' === $this->read_webhook_property( $event_object, 'failure_reason' )
+ ) {
+ $this->order_service->handle_insufficient_balance_for_refund( $order, $amount );
+ }
+ break;
+ case Refund_Status::CANCELED:
+ $this->order_service->handle_failed_refund( $order, $refund_id, $amount, $currency, $matched_wc_refund, true );
+ break;
+ case Refund_Status::SUCCEEDED:
+ if ( $matched_wc_refund ) {
+ $this->order_service->add_note_and_metadata_for_created_refund( $order, $matched_wc_refund, $refund_id, $balance_transaction ?? null );
+ }
+ break;
+ default:
+ throw new Invalid_Webhook_Data_Exception( 'Invalid refund update status: ' . $status );
}
}
@@ -470,11 +448,10 @@ private function process_webhook_payment_intent_succeeded( $event_body ) {
$intent_id = $this->read_webhook_property( $event_object, 'id' );
$currency = $this->read_webhook_property( $event_object, 'currency' );
$order = $this->get_order_from_event_body( $event_body );
- $intent_status = $this->read_webhook_property( $event_object, 'status' );
$event_charges = $this->read_webhook_property( $event_object, 'charges' );
$charges_data = $this->read_webhook_property( $event_charges, 'data' );
$charge_id = $this->read_webhook_property( $charges_data[0], 'id' );
- $metadata = $this->read_webhook_property( $event_object, 'metadata' );
+ $charge_amount = $this->read_webhook_property( $event_object, 'amount' );
$payment_method_id = $charges_data[0]['payment_method'] ?? null;
if ( ! $order ) {
@@ -496,8 +473,13 @@ private function process_webhook_payment_intent_succeeded( $event_body ) {
}
$application_fee_amount = $charges_data[0]['application_fee_amount'] ?? null;
+
if ( $application_fee_amount ) {
- $meta_data_to_update['_wcpay_transaction_fee'] = WC_Payments_Utils::interpret_stripe_amount( $application_fee_amount, $currency );
+ $fee = WC_Payments_Utils::interpret_stripe_amount( $application_fee_amount, $currency );
+ $meta_data_to_update['_wcpay_transaction_fee'] = $fee;
+
+ $charge_amount = WC_Payments_Utils::interpret_stripe_amount( $charge_amount, $currency );
+ $meta_data_to_update['_wcpay_net'] = $charge_amount - $fee;
}
foreach ( $meta_data_to_update as $key => $value ) {
@@ -879,7 +861,7 @@ private function process_webhook_refund_triggered_externally( array $event_body
$wc_refund = $this->order_service->create_refund_for_order( $order, $refunded_amount, $refund_reason, ( ! $is_partial_refund ? $order->get_items() : [] ) );
// Process the refund in the order service.
- $this->order_service->add_note_and_metadata_for_refund( $order, $wc_refund, $refund_id, $refund_balance_transaction_id );
+ $this->order_service->add_note_and_metadata_for_created_refund( $order, $wc_refund, $refund_id, $refund_balance_transaction_id, Refund_Status::PENDING === $refund['status'] );
}
/**
diff --git a/includes/class-wc-payments.php b/includes/class-wc-payments.php
index 41759213ddf..e5b120f59aa 100644
--- a/includes/class-wc-payments.php
+++ b/includes/class-wc-payments.php
@@ -445,6 +445,7 @@ public static function init() {
include_once __DIR__ . '/payment-methods/class-multibanco-payment-method.php';
include_once __DIR__ . '/payment-methods/class-grabpay-payment-method.php';
include_once __DIR__ . '/payment-methods/class-wechatpay-payment-method.php';
+ include_once __DIR__ . '/inline-script-payloads/class-woo-payments-payment-methods-config.php';
include_once __DIR__ . '/express-checkout/class-wc-payments-express-checkout-button-helper.php';
include_once __DIR__ . '/class-wc-payment-token-wcpay-sepa.php';
include_once __DIR__ . '/class-wc-payments-status.php';
@@ -471,6 +472,7 @@ public static function init() {
include_once __DIR__ . '/exceptions/class-order-id-mismatch-exception.php';
include_once __DIR__ . '/exceptions/class-rate-limiter-enabled-exception.php';
include_once __DIR__ . '/exceptions/class-invalid-address-exception.php';
+ include_once __DIR__ . '/exceptions/class-subscription-mode-mismatch-exception.php';
include_once __DIR__ . '/constants/class-base-constant.php';
include_once __DIR__ . '/constants/class-country-code.php';
include_once __DIR__ . '/constants/class-country-test-cards.php';
@@ -481,6 +483,7 @@ public static function init() {
include_once __DIR__ . '/constants/class-payment-type.php';
include_once __DIR__ . '/constants/class-payment-initiated-by.php';
include_once __DIR__ . '/constants/class-intent-status.php';
+ include_once __DIR__ . '/constants/class-refund-status.php';
include_once __DIR__ . '/constants/class-payment-intent-status.php';
include_once __DIR__ . '/constants/class-payment-capture-type.php';
include_once __DIR__ . '/constants/class-payment-method.php';
@@ -568,6 +571,10 @@ public static function init() {
self::$customer_service->init_hooks();
self::$token_service->init_hooks();
+ /**
+ * FLAG: PAYMENT_METHODS_LIST
+ * As payment methods are converted to use definitions, they need to be removed from the list below.
+ */
$payment_method_classes = [
CC_Payment_Method::class,
Bancontact_Payment_Method::class,
@@ -676,7 +683,6 @@ function () {
add_filter( 'woocommerce_payment_gateways', [ __CLASS__, 'register_gateway' ] );
add_filter( 'option_woocommerce_gateway_order', [ __CLASS__, 'order_woopayments_gateways' ], 2 );
add_filter( 'default_option_woocommerce_gateway_order', [ __CLASS__, 'order_woopayments_gateways' ], 3 );
- add_filter( 'woocommerce_rest_api_option_permissions', [ __CLASS__, 'add_wcpay_options_to_woocommerce_permissions_list' ], 5 );
add_filter( 'woocommerce_admin_get_user_data_fields', [ __CLASS__, 'add_user_data_fields' ] );
// Add note query support for source.
@@ -1119,6 +1125,10 @@ public static function init_rest_api() {
$settings_controller = new WC_REST_Payments_Settings_Controller( self::$api_client, self::get_gateway(), self::$account );
$settings_controller->register_routes();
+ include_once WCPAY_ABSPATH . 'includes/admin/class-wc-rest-payments-settings-option-controller.php';
+ $settings_option_controller = new WC_REST_Payments_Settings_Option_Controller( self::$api_client );
+ $settings_option_controller->register_routes();
+
include_once WCPAY_ABSPATH . 'includes/admin/class-wc-rest-payments-reader-controller.php';
$charges_controller = new WC_REST_Payments_Reader_Controller( self::$api_client, self::get_gateway(), self::$in_person_payments_receipts_service );
$charges_controller->register_routes();
@@ -1957,45 +1967,6 @@ public static function enqueue_dev_runtime_scripts() {
}
}
- /**
- * Adds WCPay options to Woo Core option allow list.
- *
- * @param array $permissions Array containing the permissions.
- *
- * @return array An array containing the modified permissions.
- */
- public static function add_wcpay_options_to_woocommerce_permissions_list( $permissions ) {
- $wcpay_permissions_list = array_fill_keys(
- [
- 'wcpay_frt_discover_banner_settings',
- 'wcpay_multi_currency_setup_completed',
- 'woocommerce_dismissed_todo_tasks',
- 'woocommerce_remind_me_later_todo_tasks',
- 'woocommerce_deleted_todo_tasks',
- 'wcpay_fraud_protection_welcome_tour_dismissed',
- 'wcpay_capability_request_dismissed_notices',
- 'wcpay_onboarding_eligibility_modal_dismissed',
- 'wcpay_connection_success_modal_dismissed',
- 'wcpay_next_deposit_notice_dismissed',
- 'wcpay_duplicate_payment_method_notices_dismissed',
- 'wcpay_exit_survey_dismissed',
- 'wcpay_instant_deposit_notice_dismissed',
- 'wcpay_date_format_notice_dismissed',
- ],
- true
- );
-
- if ( is_array( $permissions ) ) {
- return array_merge(
- $permissions,
- $wcpay_permissions_list
- );
- }
-
- return $wcpay_permissions_list;
- }
-
-
/**
* Creates a new request object for a server call.
*
diff --git a/includes/compat/subscriptions/class-wc-payments-email-failed-authentication-retry.php b/includes/compat/subscriptions/class-wc-payments-email-failed-authentication-retry.php
index 7d4838bfceb..324c7e36119 100644
--- a/includes/compat/subscriptions/class-wc-payments-email-failed-authentication-retry.php
+++ b/includes/compat/subscriptions/class-wc-payments-email-failed-authentication-retry.php
@@ -16,7 +16,26 @@
}
/**
+ * Class WC_Payments_Email_Failed_Authentication_Retry
+ *
* An email sent to the admin when payment fails to go through due to authentication_required error.
+ *
+ * @extends WC_Email_Failed_Order
+ *
+ * @filter woocommerce_email_preview_dummy_order
+ * Filters the dummy order object used for email previews.
+ * @param WC_Order|bool $order The order object or false.
+ * @return WC_Order The filtered order object.
+ *
+ * @filter woocommerce_email_preview_dummy_retry
+ * Filters the dummy retry object used for email previews.
+ * @param WCS_Retry|bool $retry The retry object or false.
+ * @return WCS_Retry|null The filtered retry object or null if WCS_Retry class doesn't exist.
+ *
+ * @filter woocommerce_email_preview_placeholders
+ * Filters the email preview placeholders.
+ * @param array $placeholders Array of email preview placeholders.
+ * @return array Modified array of placeholders.
*/
class WC_Payments_Email_Failed_Authentication_Retry extends WC_Email_Failed_Order {
@@ -46,6 +65,69 @@ public function __construct() {
// We want all the parent's methods, with none of its properties, so call its parent's constructor, rather than my parent constructor.
WC_Email::__construct();
+
+ // Add email preview filters.
+ add_filter( 'woocommerce_email_preview_dummy_order', [ $this, 'get_preview_order' ], 10, 1 );
+ add_filter( 'woocommerce_email_preview_dummy_retry', [ $this, 'get_preview_retry' ], 10, 1 );
+ add_filter( 'woocommerce_email_preview_placeholders', [ $this, 'get_preview_placeholders' ], 10, 1 );
+ }
+
+ /**
+ * Get a dummy order for email preview.
+ *
+ * @param WC_Order|bool $order The order object or false.
+ * @return WC_Order
+ */
+ public function get_preview_order( $order ) {
+ if ( ! $order instanceof WC_Order ) {
+ $order = wc_create_order();
+ $order->set_status( 'failed' );
+ $order->set_billing_first_name( 'John' );
+ $order->set_billing_last_name( 'Doe' );
+ $order->set_billing_email( 'john.doe@example.com' );
+ $order->set_total( 99.99 );
+ $order->save();
+ }
+ return $order;
+ }
+
+ /**
+ * Get a dummy retry object for email preview.
+ *
+ * @param WCS_Retry|bool $retry The retry object or false.
+ * @return WCS_Retry|null
+ */
+ public function get_preview_retry( $retry ) {
+ if ( ! class_exists( 'WCS_Retry' ) || ! function_exists( 'wcs_get_human_time_diff' ) ) {
+ return null;
+ }
+
+ if ( ! $retry instanceof WCS_Retry ) {
+ $retry_data = [
+ 'time' => time() + DAY_IN_SECONDS,
+ 'order_id' => 0,
+ 'retry_number' => 1,
+ 'status' => 'pending',
+ ];
+ $retry = new WCS_Retry( $retry_data );
+ }
+ return $retry;
+ }
+
+ /**
+ * Get preview placeholders.
+ *
+ * @param array $placeholders The placeholders array.
+ * @return array
+ */
+ public function get_preview_placeholders( $placeholders ) {
+ $retry = $this->get_preview_retry( false );
+ $retry_time = '';
+ if ( $retry && function_exists( 'wcs_get_human_time_diff' ) ) {
+ $retry_time = wcs_get_human_time_diff( $retry->get_time() );
+ }
+ $placeholders['{retry_time}'] = $retry_time;
+ return $placeholders;
}
/**
@@ -73,23 +155,29 @@ public function get_default_heading() {
* @param WC_Order|null $order Order object.
*/
public function trigger( $order_id, $order = null ) {
+ if ( ! $this->is_enabled() || ! $this->get_recipient() ) {
+ return;
+ }
+
$this->object = $order;
- $this->find['retry-time'] = '{retry_time}';
if ( class_exists( 'WCS_Retry_Manager' ) && function_exists( 'wcs_get_human_time_diff' ) ) {
- $this->retry = WCS_Retry_Manager::store()->get_last_retry_for_order( wcs_get_objects_property( $order, 'id' ) );
- $this->replace['retry-time'] = wcs_get_human_time_diff( $this->retry->get_time() );
+ $this->retry = WCS_Retry_Manager::store()->get_last_retry_for_order( wcs_get_objects_property( $order, 'id' ) );
} else {
Logger::log( 'WCS_Retry_Manager class or does not exist. Not able to send admin email about customer notification for authentication required for renewal payment.' );
return;
}
+ // Set up order number replacement.
$this->find['order-number'] = '{order_number}';
$this->replace['order-number'] = $this->object->get_order_number();
- if ( ! $this->is_enabled() || ! $this->get_recipient() ) {
- return;
+ // Set up retry time replacement.
+ $retry_time = '';
+ if ( $this->retry && function_exists( 'wcs_get_human_time_diff' ) ) {
+ $retry_time = wcs_get_human_time_diff( $this->retry->get_time() );
}
+ $this->placeholders['{retry_time}'] = $retry_time;
$this->send( $this->get_recipient(), $this->get_subject(), $this->get_content(), $this->get_headers(), $this->get_attachments() );
}
@@ -100,6 +188,11 @@ public function trigger( $order_id, $order = null ) {
* @return string
*/
public function get_content_html() {
+ // Ensure retry object is initialized for preview.
+ if ( ! isset( $this->retry ) ) {
+ $this->retry = $this->get_preview_retry( false );
+ }
+
return wc_get_template_html(
$this->template_html,
[
@@ -121,6 +214,11 @@ public function get_content_html() {
* @return string
*/
public function get_content_plain() {
+ // Ensure retry object is initialized for preview.
+ if ( ! isset( $this->retry ) ) {
+ $this->retry = $this->get_preview_retry( false );
+ }
+
return wc_get_template_html(
$this->template_plain,
[
diff --git a/includes/constants/class-payment-method.php b/includes/constants/class-payment-method.php
index 24c5cb8b831..cd354fbdc3d 100644
--- a/includes/constants/class-payment-method.php
+++ b/includes/constants/class-payment-method.php
@@ -18,6 +18,10 @@
* @psalm-immutable
*/
class Payment_Method extends Base_Constant {
+ /**
+ * FLAG: PAYMENT_METHODS_LIST
+ * We need to see how we can use the definitions to replace these constants.
+ */
const ALIPAY = 'alipay';
const BANCONTACT = 'bancontact';
const BASC = 'bacs_debit';
diff --git a/includes/constants/class-refund-status.php b/includes/constants/class-refund-status.php
new file mode 100644
index 00000000000..a0a16d8fa3c
--- /dev/null
+++ b/includes/constants/class-refund-status.php
@@ -0,0 +1,24 @@
+set_payment_method_title( __( 'WooCommerce In-Person Payments', 'woocommerce-payments' ) );
+ return $order;
+ }
+
+ /**
+ * Get preview address data for email preview.
+ *
+ * @param array $address The address data.
+ * @return array
+ */
+ public function get_preview_address( $address ) {
+ if ( empty( $address ) ) {
+ $address = [
+ 'line1' => '123 Sample Street',
+ 'line2' => 'Suite 100',
+ 'city' => 'Sample City',
+ 'state' => 'ST',
+ 'postal_code' => '12345',
+ 'country' => 'US',
+ ];
+ }
+ return $address;
+ }
+
+ /**
+ * Get preview placeholders for email preview.
+ *
+ * @param array $placeholders The placeholders array.
+ * @return array
+ */
+ public function get_preview_placeholders( $placeholders ) {
+ $placeholders['{order_date}'] = wc_format_datetime( new DateTime() );
+ $placeholders['{order_number}'] = '42';
+ return $placeholders;
+ }
+
/**
* Get email subject.
*
@@ -171,6 +235,33 @@ public function get_content_plain(): string {
);
}
+ /**
+ * Get preview merchant settings for email preview.
+ *
+ * @param array $settings The merchant settings.
+ * @return array
+ */
+ public function get_preview_merchant_settings( $settings ) {
+ if ( empty( $settings ) ) {
+ $settings = [
+ 'business_name' => 'Sample Store',
+ 'support_info' => [
+ 'address' => [
+ 'line1' => '123 Sample Street',
+ 'line2' => 'Suite 100',
+ 'city' => 'Sample City',
+ 'state' => 'ST',
+ 'postal_code' => '12345',
+ 'country' => 'US',
+ ],
+ 'phone' => '+1 (555) 123-4567',
+ 'email' => 'support@samplestore.com',
+ ],
+ ];
+ }
+ return $settings;
+ }
+
/**
* Get store details content html
*
@@ -179,34 +270,58 @@ public function get_content_plain(): string {
* @return void
*/
public function store_details( array $settings, bool $plain_text ) {
+ // Ensure we have all required data for preview.
+ $settings = $this->get_preview_merchant_settings( $settings );
+
+ $template_data = [
+ 'business_name' => $settings['business_name'] ?? '',
+ 'support_address' => $settings['support_info']['address'] ?? [],
+ 'support_phone' => $settings['support_info']['phone'] ?? '',
+ 'support_email' => $settings['support_info']['email'] ?? '',
+ ];
+
if ( $plain_text ) {
wc_get_template(
'emails/plain/email-ipp-receipt-store-details.php',
- [
- 'business_name' => $settings['business_name'],
- 'support_address' => $settings['support_info']['address'],
- 'support_phone' => $settings['support_info']['phone'],
- 'support_email' => $settings['support_info']['email'],
- ],
+ $template_data,
'',
WCPAY_ABSPATH . 'templates/'
);
-
} else {
wc_get_template(
'emails/email-ipp-receipt-store-details.php',
- [
- 'business_name' => $settings['business_name'],
- 'support_address' => $settings['support_info']['address'],
- 'support_phone' => $settings['support_info']['phone'],
- 'support_email' => $settings['support_info']['email'],
- ],
+ $template_data,
'',
WCPAY_ABSPATH . 'templates/'
);
}
}
+ /**
+ * Get preview charge data for email preview.
+ *
+ * @param array $charge The charge data.
+ * @return array
+ */
+ public function get_preview_charge( $charge ) {
+ if ( empty( $charge ) ) {
+ $charge = [
+ 'payment_method_details' => [
+ 'card_present' => [
+ 'brand' => 'visa',
+ 'last4' => '4242',
+ 'receipt' => [
+ 'application_preferred_name' => 'Sample App',
+ 'dedicated_file_name' => 'Sample File',
+ 'account_type' => 'credit',
+ ],
+ ],
+ ],
+ ];
+ }
+ return $charge;
+ }
+
/**
* Get compliance data content html
*
@@ -215,23 +330,25 @@ public function store_details( array $settings, bool $plain_text ) {
* @return void
*/
public function compliance_details( array $charge, bool $plain_text ) {
+ // Ensure we have all required data for preview.
+ $charge = $this->get_preview_charge( $charge );
+
+ $template_data = [
+ 'payment_method_details' => $charge['payment_method_details']['card_present'] ?? [],
+ 'receipt' => $charge['payment_method_details']['card_present']['receipt'] ?? [],
+ ];
+
if ( $plain_text ) {
wc_get_template(
'emails/plain/email-ipp-receipt-compliance-details.php',
- [
- 'payment_method_details' => $charge['payment_method_details']['card_present'],
- 'receipt' => $charge['payment_method_details']['card_present']['receipt'],
- ],
+ $template_data,
'',
WCPAY_ABSPATH . 'templates/'
);
} else {
wc_get_template(
'emails/email-ipp-receipt-compliance-details.php',
- [
- 'payment_method_details' => $charge['payment_method_details']['card_present'],
- 'receipt' => $charge['payment_method_details']['card_present']['receipt'],
- ],
+ $template_data,
'',
WCPAY_ABSPATH . 'templates/'
);
diff --git a/includes/exceptions/class-subscription-mode-mismatch-exception.php b/includes/exceptions/class-subscription-mode-mismatch-exception.php
new file mode 100644
index 00000000000..5045485985d
--- /dev/null
+++ b/includes/exceptions/class-subscription-mode-mismatch-exception.php
@@ -0,0 +1,29 @@
+get_route() === '/wc/store/v1/cart/update-customer';
if ( $is_update_customer_route ) {
add_filter( 'woocommerce_validate_postcode', [ $this, 'maybe_skip_postcode_validation' ], 10, 3 );
}
- $request_data = $request->get_json_params();
- if ( isset( $request_data['shipping_address'] ) ) {
- $request->set_param( 'shipping_address', $this->transform_ece_address_state_data( $request_data['shipping_address'] ) );
- // on the "update customer" route, GooglePay/Apple pay might provide redacted postcode data.
+ if ( isset( $request['shipping_address'] ) && is_array( $request['shipping_address'] ) ) {
+ $shipping_address = $request['shipping_address'];
+ $shipping_address = $this->transform_ece_address_state_data( $shipping_address );
+ // on the "update customer" route, Google Pay/Apple Pay might provide redacted postcode data.
// we need to modify the zip code to ensure that shipping zone identification still works.
if ( $is_update_customer_route ) {
- $request->set_param( 'shipping_address', $this->transform_ece_address_postcode_data( $request_data['shipping_address'] ) );
+ $shipping_address = $this->transform_ece_address_postcode_data( $shipping_address );
}
+ $request->set_param( 'shipping_address', $shipping_address );
}
- if ( isset( $request_data['billing_address'] ) ) {
- $request->set_param( 'billing_address', $this->transform_ece_address_state_data( $request_data['billing_address'] ) );
- // on the "update customer" route, GooglePay/Apple pay might provide redacted postcode data.
+ if ( isset( $request['billing_address'] ) && is_array( $request['billing_address'] ) ) {
+ $billing_address = $request['billing_address'];
+ $billing_address = $this->transform_ece_address_state_data( $billing_address );
+ // on the "update customer" route, Google Pay/Apple Pay might provide redacted postcode data.
// we need to modify the zip code to ensure that shipping zone identification still works.
if ( $is_update_customer_route ) {
- $request->set_param( 'billing_address', $this->transform_ece_address_postcode_data( $request_data['billing_address'] ) );
+ $billing_address = $this->transform_ece_address_postcode_data( $billing_address );
}
+ $request->set_param( 'billing_address', $billing_address );
}
return $response;
@@ -561,9 +564,9 @@ public function maybe_skip_postcode_validation( $valid, $postcode, $country ) {
}
/**
- * Transform a GooglePay/ApplePay state address data fields into values that are valid for WooCommerce.
+ * Transform a Google Pay/Apple Pay state address data fields into values that are valid for WooCommerce.
*
- * @param array $address The address to normalize from the GooglePay/ApplePay request.
+ * @param array $address The address to normalize from the Google Pay/Apple Pay request.
*
* @return array
*/
@@ -583,9 +586,9 @@ private function transform_ece_address_state_data( $address ) {
}
/**
- * Transform a GooglePay/ApplePay postcode address data fields into values that are valid for WooCommerce.
+ * Transform a Google Pay/Apple Pay postcode address data fields into values that are valid for WooCommerce.
*
- * @param array $address The address to normalize from the GooglePay/ApplePay request.
+ * @param array $address The address to normalize from the Google Pay/Apple Pay request.
*
* @return array
*/
diff --git a/includes/express-checkout/class-wc-payments-express-checkout-button-display-handler.php b/includes/express-checkout/class-wc-payments-express-checkout-button-display-handler.php
index f9c978d43a1..b13244d3ae1 100644
--- a/includes/express-checkout/class-wc-payments-express-checkout-button-display-handler.php
+++ b/includes/express-checkout/class-wc-payments-express-checkout-button-display-handler.php
@@ -141,11 +141,10 @@ public function display_express_checkout_buttons() {
} else {
$this->add_order_attribution_inputs();
}
-
+ $this->display_express_checkout_separator_if_necessary( $separator_starts_hidden );
?>
display_express_checkout_separator_if_necessary( $separator_starts_hidden );
}
}
diff --git a/includes/express-checkout/class-wc-payments-express-checkout-button-handler.php b/includes/express-checkout/class-wc-payments-express-checkout-button-handler.php
index 073e09b0ad9..c224250b9cb 100644
--- a/includes/express-checkout/class-wc-payments-express-checkout-button-handler.php
+++ b/includes/express-checkout/class-wc-payments-express-checkout-button-handler.php
@@ -250,6 +250,7 @@ public function get_express_checkout_params() {
// Defaults to 'required' to match how core initializes this option.
'needs_payer_phone' => 'required' === get_option( 'woocommerce_checkout_phone_field', 'required' ),
'allowed_shipping_countries' => array_keys( WC()->countries->get_shipping_countries() ?? [] ),
+ 'display_prices_with_tax' => 'incl' === get_option( 'woocommerce_tax_display_cart' ),
],
'button' => $this->get_button_settings(),
'login_confirmation' => $this->get_login_confirmation_settings(),
@@ -329,7 +330,7 @@ public function display_express_checkout_button_html() {
return;
}
?>
-