diff --git a/changelog.txt b/changelog.txt index 390485fb5..f92f80913 100644 --- a/changelog.txt +++ b/changelog.txt @@ -4,7 +4,8 @@ * Fix - Ensure ordering for Stripe payment methods is saved even when setting up from scratch. * Add - Add support for Stripe Express Checkout Element on the block cart and checkout page. * Add - Implemented the "Update all subscriptions payment methods" checkbox on My Account → Payment methods for UPE payment methods. -* Add - Add support for the new Stripe Checkout Element on the shortcode checkout page. +* Add - Add support for the new Stripe Checkout Element on the shortcode checkout page. +* Add - Add support for the new Stripe Checkout Element on the pay for order page. * Dev - Introduces a new class with payment methods constants. * Dev - Introduces a new class with currency codes constants. * Dev - Improves the readability of the redirect URL generation code (UPE). diff --git a/client/entrypoints/express-checkout/index.js b/client/entrypoints/express-checkout/index.js index 80064dc92..7f12cff3d 100644 --- a/client/entrypoints/express-checkout/index.js +++ b/client/entrypoints/express-checkout/index.js @@ -1,3 +1,4 @@ +/*global wcStripeExpressCheckoutPayForOrderParams */ import { __ } from '@wordpress/i18n'; import { debounce } from 'lodash'; import jQuery from 'jquery'; @@ -21,6 +22,7 @@ import { } from 'wcstripe/express-checkout/event-handler'; import { getStripeServerData } from 'wcstripe/stripe-utils'; import { getAddToCartVariationParams } from 'wcstripe/utils'; +import './styles.scss'; jQuery( function ( $ ) { // Don't load if blocks checkout is being loaded. @@ -267,7 +269,21 @@ jQuery( function ( $ ) { */ init: () => { if ( getExpressCheckoutData( 'is_pay_for_order' ) ) { - // Pay for order page specific initialization. + const { + total: { amount: total }, + displayItems, + order, + } = wcStripeExpressCheckoutPayForOrderParams; + + wcStripeECE.startExpressCheckoutElement( { + mode: 'payment', + total, + currency: getExpressCheckoutData( 'checkout' ) + .currency_code, + appearance: getExpressCheckoutButtonAppearance(), + displayItems, + order, + } ); } else if ( getExpressCheckoutData( 'is_product_page' ) ) { // Product page specific initialization. } else { diff --git a/client/entrypoints/express-checkout/styles.scss b/client/entrypoints/express-checkout/styles.scss new file mode 100644 index 000000000..9381c1395 --- /dev/null +++ b/client/entrypoints/express-checkout/styles.scss @@ -0,0 +1,3 @@ +#wc-stripe-express-checkout-element iframe { + max-width: unset; +} diff --git a/client/express-checkout/utils/normalize.js b/client/express-checkout/utils/normalize.js index 3ec0ccb48..7af50f605 100644 --- a/client/express-checkout/utils/normalize.js +++ b/client/express-checkout/utils/normalize.js @@ -86,6 +86,7 @@ export const normalizeOrderData = ( event, paymentMethodId ) => { export const normalizePayForOrderData = ( event, paymentMethodId ) => { return { payment_method: 'stripe', + 'wc-stripe-is-deferred-intent': true, // Set the deferred intent flag, so the deferred intent flow is used. 'wc-stripe-payment-method': paymentMethodId, express_payment_type: event?.expressPaymentType, }; diff --git a/includes/payment-methods/class-wc-stripe-express-checkout-ajax-handler.php b/includes/payment-methods/class-wc-stripe-express-checkout-ajax-handler.php index 5259b3a84..c2dd0598c 100644 --- a/includes/payment-methods/class-wc-stripe-express-checkout-ajax-handler.php +++ b/includes/payment-methods/class-wc-stripe-express-checkout-ajax-handler.php @@ -37,6 +37,7 @@ public function init() { add_action( 'wc_ajax_wc_stripe_get_selected_product_data', [ $this, 'ajax_get_selected_product_data' ] ); add_action( 'wc_ajax_wc_stripe_clear_cart', [ $this, 'ajax_clear_cart' ] ); add_action( 'wc_ajax_wc_stripe_log_errors', [ $this, 'ajax_log_errors' ] ); + add_action( 'wc_ajax_wc_stripe_pay_for_order', [ $this, 'ajax_pay_for_order' ] ); } /** @@ -71,7 +72,7 @@ public function ajax_get_cart_details() { /** * Adds the current product to the cart. Used on product detail page. * - * @return array $data Results of adding the product to the cart. + * @return array $data Results of adding the product to the cart. */ public function ajax_add_to_cart() { check_ajax_referer( 'wc-stripe-add-to-cart', 'security' ); @@ -309,4 +310,65 @@ public function ajax_log_errors() { exit; } + + /** + * Processes the Pay for Order AJAX request from the Express Checkout. + */ + public function ajax_pay_for_order() { + check_ajax_referer( 'wc-stripe-pay-for-order' ); + + if ( + ! isset( $_POST['payment_method'] ) || 'stripe' !== $_POST['payment_method'] + || ! isset( $_POST['order'] ) || ! intval( $_POST['order'] ) + || ! isset( $_POST['wc-stripe-payment-method'] ) || empty( $_POST['wc-stripe-payment-method'] ) + ) { + // Incomplete request. + $response = [ + 'result' => 'error', + 'messages' => __( 'Invalid request', 'woocommerce-gateway-stripe' ), + ]; + wp_send_json( $response, 400 ); + return; + } + + try { + // Set up an environment, similar to core checkout. + wc_maybe_define_constant( 'WOOCOMMERCE_CHECKOUT', true ); + wc_set_time_limit( 0 ); + + // Load the order. + $order_id = intval( $_POST['order'] ); + $order = wc_get_order( $order_id ); + + if ( ! is_a( $order, WC_Order::class ) ) { + throw new Exception( __( 'Invalid order!', 'woocommerce-gateway-stripe' ) ); + } + + if ( ! $order->needs_payment() ) { + throw new Exception( __( 'This order does not require payment!', 'woocommerce-gateway-stripe' ) ); + } + + // Process the payment. + $result = WC_Stripe::get_instance()->get_main_stripe_gateway()->process_payment( $order_id ); + + $this->express_checkout_helper->add_order_payment_method_title( $order ); + + // process_payment() should only return `success` or throw an exception. + if ( ! is_array( $result ) || ! isset( $result['result'] ) || 'success' !== $result['result'] || ! isset( $result['redirect'] ) ) { + throw new Exception( __( 'Unable to determine payment success.', 'woocommerce-gateway-stripe' ) ); + } + + // Include the order ID in the result. + $result['order_id'] = $order_id; + + $result = apply_filters( 'woocommerce_payment_successful_result', $result, $order_id ); + } catch ( Exception $e ) { + $result = [ + 'result' => 'error', + 'messages' => $e->getMessage(), + ]; + } + + wp_send_json( $result ); + } } diff --git a/includes/payment-methods/class-wc-stripe-express-checkout-element.php b/includes/payment-methods/class-wc-stripe-express-checkout-element.php index 7c11b2aa2..a280aa603 100644 --- a/includes/payment-methods/class-wc-stripe-express-checkout-element.php +++ b/includes/payment-methods/class-wc-stripe-express-checkout-element.php @@ -88,11 +88,14 @@ public function init() { add_action( 'woocommerce_after_add_to_cart_form', [ $this, 'display_express_checkout_button_html' ], 1 ); add_action( 'woocommerce_proceed_to_checkout', [ $this, 'display_express_checkout_button_html' ], 25 ); add_action( 'woocommerce_checkout_before_customer_details', [ $this, 'display_express_checkout_button_html' ], 1 ); + add_action( 'woocommerce_pay_order_before_payment', [ $this, 'display_express_checkout_button_html' ], 1 ); add_filter( 'woocommerce_gateway_title', [ $this, 'filter_gateway_title' ], 10, 2 ); add_action( 'woocommerce_checkout_order_processed', [ $this, 'add_order_meta' ], 10, 2 ); add_filter( 'woocommerce_login_redirect', [ $this, 'get_login_redirect_url' ], 10, 3 ); add_filter( 'woocommerce_registration_redirect', [ $this, 'get_login_redirect_url' ], 10, 3 ); + + add_action( 'before_woocommerce_pay_form', [ $this, 'localize_pay_for_order_page_scripts' ] ); } /** @@ -184,6 +187,7 @@ public function javascript_params() { 'get_selected_product_data' => wp_create_nonce( 'wc-stripe-get-selected-product-data' ), 'log_errors' => wp_create_nonce( 'wc-stripe-log-errors' ), 'clear_cart' => wp_create_nonce( 'wc-stripe-clear-cart' ), + 'pay_for_order' => wp_create_nonce( 'wc-stripe-pay-for-order' ), ], 'i18n' => [ 'no_prepaid_card' => __( 'Sorry, we\'re not accepting prepaid cards at this time.', 'woocommerce-gateway-stripe' ), @@ -208,6 +212,64 @@ public function javascript_params() { ]; } + /** + * Localizes additional parameters necessary for the Pay for Order page. + * + * @param WC_Order $order The order that needs payment. + */ + public function localize_pay_for_order_page_scripts( $order ) { + $currency = get_woocommerce_currency(); + $data = []; + $items = []; + + foreach ( $order->get_items() as $item ) { + if ( method_exists( $item, 'get_total' ) ) { + $items[] = [ + 'label' => $item->get_name(), + 'amount' => WC_Stripe_Helper::get_stripe_amount( $item->get_total(), $currency ), + ]; + } + } + + if ( $order->get_total_tax() ) { + $items[] = [ + 'label' => __( 'Tax', 'woocommerce-gateway-stripe' ), + 'amount' => WC_Stripe_Helper::get_stripe_amount( $order->get_total_tax(), $currency ), + ]; + } + + if ( $order->get_shipping_total() ) { + $shipping_label = sprintf( + // Translators: %s is the name of the shipping method. + __( 'Shipping (%s)', 'woocommerce-gateway-stripe' ), + $order->get_shipping_method() + ); + + $items[] = [ + 'label' => $shipping_label, + 'amount' => WC_Stripe_Helper::get_stripe_amount( $order->get_shipping_total(), $currency ), + ]; + } + + foreach ( $order->get_fees() as $fee ) { + $items[] = [ + 'label' => $fee->get_name(), + 'amount' => WC_Stripe_Helper::get_stripe_amount( $fee->get_amount(), $currency ), + ]; + } + + $data['order'] = $order->get_id(); + $data['displayItems'] = $items; + $data['needs_shipping'] = false; // This should be already entered/prepared. + $data['total'] = [ + 'label' => $this->express_checkout_helper->get_total_label(), + 'amount' => WC_Stripe_Helper::get_stripe_amount( $order->get_total(), $currency ), + 'pending' => true, + ]; + + wp_localize_script( 'wc_stripe_express_checkout', 'wcStripeExpressCheckoutPayForOrderParams', $data ); + } + /** * Load scripts and styles. */ diff --git a/includes/payment-methods/class-wc-stripe-express-checkout-helper.php b/includes/payment-methods/class-wc-stripe-express-checkout-helper.php index cd272e651..18a7d8219 100644 --- a/includes/payment-methods/class-wc-stripe-express-checkout-helper.php +++ b/includes/payment-methods/class-wc-stripe-express-checkout-helper.php @@ -1250,4 +1250,34 @@ public function maybe_restore_recurring_chosen_shipping_methods( $previous_chose WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods ); } + + /** + * Adds the express checkout payment method title to the order. + * + * @param WC_Order $order The order. + */ + public function add_order_payment_method_title( $order ) { + if ( empty( $_POST['express_payment_type'] ) || ! isset( $_POST['payment_method'] ) || 'stripe' !== $_POST['payment_method'] ) { // phpcs:ignore WordPress.Security.NonceVerification + return; + } + + $express_payment_type = wc_clean( wp_unslash( $_POST['express_payment_type'] ) ); // phpcs:ignore WordPress.Security.NonceVerification + $express_payment_titles = [ + 'apple_pay' => 'Apple Pay', + 'google_pay' => 'Google Pay', + ]; + $payment_method_title = $express_payment_titles[ $express_payment_type ] ?? false; + + if ( ! $payment_method_title ) { + return; + } + + $suffix = apply_filters( 'wc_stripe_payment_request_payment_method_title_suffix', 'Stripe' ); + if ( ! empty( $suffix ) ) { + $suffix = " ($suffix)"; + } + + $order->set_payment_method_title( $payment_method_title . $suffix ); + $order->save(); + } } diff --git a/readme.txt b/readme.txt index 81eae7994..da9f82889 100644 --- a/readme.txt +++ b/readme.txt @@ -132,7 +132,8 @@ If you get stuck, you can ask for help in the Plugin Forum. * Fix - Ensure ordering for Stripe payment methods is saved even when setting up from scratch. * Add - Add support for Stripe Express Checkout Element on the block cart and checkout page. * Add - Implemented the "Update all subscriptions payment methods" checkbox on My Account → Payment methods for UPE payment methods. -* Add - Add support for the new Stripe Checkout Element on the shortcode checkout page. +* Add - Add support for the new Stripe Checkout Element on the shortcode checkout page. +* Add - Add support for the new Stripe Checkout Element on the pay for order page. * Dev - Introduces a new class with payment methods constants. * Dev - Introduces a new class with currency codes constants. * Dev - Improves the readability of the redirect URL generation code (UPE).