Skip to content

Commit 7d52400

Browse files
authored
Amazon Pay ECE: fix payment method issues for subscriptions (#4008)
* Update payment information params for confirmation tokens * Add support for payment tokens * Save payment method info in subscription * Display correct payment via info * Add readme and changelog entries * Set correct payment type when using Amazon Pay payment token * Add and update unit tests * Set up mandate when using Amazon Pay saved payment method * Refactor payment information code for readability
1 parent fb75afd commit 7d52400

13 files changed

+348
-37
lines changed

changelog.txt

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
* Add - Disable unsupported payment methods in Stripe settings
2828
* Update - Update handling of PR as a country in the terminal locations endpoint.
2929
* Fix - Hide Amazon Pay in settings when legacy checkout is enabled.
30+
* Fix - Fix subscription renewal issues for Amazon Pay.
3031

3132
= 9.3.1 - 2025-03-14 =
3233
* Fix - Temporarily disables the subscriptions detached notice feature due to long loading times on stores with many subscriptions.

includes/class-wc-stripe-customer.php

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class WC_Stripe_Customer {
2626
WC_Stripe_UPE_Payment_Method_ACH::STRIPE_ID,
2727
WC_Stripe_UPE_Payment_Method_ACSS::STRIPE_ID,
2828
WC_Stripe_UPE_Payment_Method_Bacs_Debit::STRIPE_ID,
29+
WC_Stripe_UPE_Payment_Method_Amazon_Pay::STRIPE_ID,
2930
];
3031

3132
/**

includes/class-wc-stripe-intent-controller.php

+1
Original file line numberDiff line numberDiff line change
@@ -1010,6 +1010,7 @@ public function is_mandate_data_required( $selected_payment_type, $is_using_save
10101010
$payment_methods_with_mandates = [
10111011
WC_Stripe_Payment_Methods::ACH,
10121012
WC_Stripe_Payment_Methods::ACSS_DEBIT,
1013+
WC_Stripe_Payment_Methods::AMAZON_PAY,
10131014
WC_Stripe_Payment_Methods::BACS_DEBIT,
10141015
WC_Stripe_Payment_Methods::BECS_DEBIT,
10151016
WC_Stripe_Payment_Methods::SEPA_DEBIT,

includes/compat/trait-wc-stripe-subscriptions.php

+4
Original file line numberDiff line numberDiff line change
@@ -1010,6 +1010,10 @@ public function maybe_render_subscription_payment_method( $payment_method_to_dis
10101010
/* translators: 1) the Bacs Direct Debit payment method's last 4 numbers */
10111011
$payment_method_to_display = sprintf( __( 'Via Bacs Direct Debit ending in (%1$s)', 'woocommerce-gateway-stripe' ), $source->bacs_debit->last4 );
10121012
break 3;
1013+
case WC_Stripe_Payment_Methods::AMAZON_PAY:
1014+
/* translators: 1) the Amazon Pay payment method's email */
1015+
$payment_method_to_display = sprintf( __( 'Via Amazon Pay (%1$s)', 'woocommerce-gateway-stripe' ), $source->billing_details->email ?? '' );
1016+
break 3;
10131017
}
10141018
}
10151019
}

includes/payment-methods/class-wc-stripe-upe-payment-gateway.php

+71-31
Original file line numberDiff line numberDiff line change
@@ -1071,6 +1071,14 @@ private function process_payment_with_confirmation_token( int $order_id ) {
10711071
}
10721072
}
10731073

1074+
if ( $payment_information['save_payment_method_to_store'] ) {
1075+
$this->handle_saving_payment_method(
1076+
$order,
1077+
$payment_method,
1078+
$selected_payment_type
1079+
);
1080+
}
1081+
10741082
$return_url = $this->get_return_url( $order );
10751083

10761084
return [
@@ -2223,6 +2231,8 @@ protected function prepare_payment_information_from_request( WC_Order $order ) {
22232231

22242232
if ( is_a( $token, 'WC_Payment_Token_SEPA' ) ) {
22252233
$selected_payment_type = WC_Stripe_UPE_Payment_Method_Sepa::STRIPE_ID;
2234+
} elseif ( is_a( $token, 'WC_Payment_Token_Amazon_Pay' ) ) {
2235+
$selected_payment_type = WC_Stripe_UPE_Payment_Method_Amazon_Pay::STRIPE_ID;
22262236
}
22272237
} else {
22282238
$payment_method_id = sanitize_text_field( wp_unslash( $_POST['wc-stripe-payment-method'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
@@ -2253,56 +2263,86 @@ protected function prepare_payment_information_from_request( WC_Order $order ) {
22532263
'return_url' => $this->get_return_url_for_redirect( $order, $save_payment_method_to_store ),
22542264
'use_stripe_sdk' => 'true', // We want to use the SDK to handle next actions via the client payment elements. See https://docs.stripe.com/api/setup_intents/create#create_setup_intent-use_stripe_sdk
22552265
'has_subscription' => $this->has_subscription( $order->get_id() ),
2256-
'payment_method' => '',
2266+
'payment_method' => $payment_method_id,
22572267
'payment_method_details' => $payment_method_details,
22582268
'payment_type' => 'single', // single | recurring.
2269+
'save_payment_method_to_store' => $save_payment_method_to_store,
2270+
'capture_method' => $capture_method,
22592271
];
22602272

22612273
if ( WC_Stripe_Payment_Methods::ACH === $selected_payment_type ) {
22622274
WC_Stripe_API::attach_payment_method_to_customer( $payment_information['customer'], $payment_method_id );
22632275
}
22642276

2265-
if ( ! empty( $payment_method_id ) ) {
2266-
$payment_information['payment_method'] = $payment_method_id;
2267-
$payment_information['save_payment_method_to_store'] = $save_payment_method_to_store;
2268-
$payment_information['payment_method_options'] = $this->get_payment_method_options(
2277+
// Use the dynamic + short statement descriptor if enabled and it's a card payment.
2278+
$is_short_statement_descriptor_enabled = 'yes' === $this->get_option( 'is_short_statement_descriptor_enabled', 'no' );
2279+
if ( WC_Stripe_Payment_Methods::CARD === $selected_payment_type && $is_short_statement_descriptor_enabled ) {
2280+
$payment_information['statement_descriptor_suffix'] = WC_Stripe_Helper::get_dynamic_statement_descriptor_suffix( $order );
2281+
}
2282+
2283+
if ( empty( $payment_method_id ) && ! empty( $_POST['wc-stripe-confirmation-token'] ) ) {
2284+
// Add fields that are only set when using the confirmation token flow.
2285+
$payment_information = $this->prepare_payment_information_for_confirmation_token(
2286+
$payment_information,
22692287
$selected_payment_type,
2270-
$order,
2271-
$payment_method_details
2288+
$capture_method,
22722289
);
2273-
$payment_information['capture_method'] = $capture_method;
22742290
} else {
2275-
$confirmation_token_id = sanitize_text_field( wp_unslash( $_POST['wc-stripe-confirmation-token'] ?? '' ) );
2276-
$payment_information['confirmation_token'] = $confirmation_token_id;
2277-
$payment_information['save_payment_method_to_store'] = false;
2278-
2279-
// When using confirmation tokens with manual capture, we need to
2280-
// set the capture_method parameter under payment method options.
2281-
if ( 'manual' === $capture_method ) {
2282-
$payment_information['payment_method_options'] = [
2283-
$selected_payment_type => [
2284-
'capture_method' => 'manual',
2285-
],
2286-
];
2287-
} else {
2288-
$payment_information['capture_method'] = $capture_method;
2289-
}
2291+
// Add fields that are only set when using the payment method flow.
2292+
$payment_information = $this->prepare_payment_information_for_payment_method( $payment_information, $selected_payment_type, $order );
2293+
}
22902294

2291-
// When using confirmation tokens for subscriptions, we need to set the setup_future_usage parameter under payment method options.
2292-
if ( $payment_information['has_subscription'] ) {
2293-
$payment_information['payment_method_options'][ $selected_payment_type ]['setup_future_usage'] = 'off_session';
2294-
}
2295+
return $payment_information;
2296+
}
2297+
2298+
/**
2299+
* Add or remove payment information fields for the confirmation token flow.
2300+
*
2301+
* @param array $payment_information The base payment information.
2302+
* @param string $selected_payment_type The selected payment type.
2303+
* @param string $capture_method The capture method to be used.
2304+
* @return array The customized payment information for the confirmation token flow.
2305+
*/
2306+
private function prepare_payment_information_for_confirmation_token( $payment_information, $selected_payment_type, $capture_method ) {
2307+
// These fields should not be set when using confirmation tokens to create a payment intent.
2308+
unset( $payment_information['payment_method'] );
2309+
unset( $payment_information['payment_method_details'] );
2310+
2311+
$confirmation_token_id = sanitize_text_field( wp_unslash( $_POST['wc-stripe-confirmation-token'] ?? '' ) );
2312+
$payment_information['confirmation_token'] = $confirmation_token_id;
2313+
2314+
// Some payment methods such as Amazon Pay will only accept a capture_method of 'manual'
2315+
// under payment_method_options instead of at the top level.
2316+
if ( 'manual' === $capture_method ) {
2317+
unset( $payment_information['capture_method'] );
2318+
$payment_information['payment_method_options'][ $selected_payment_type ]['capture_method'] = 'manual';
22952319
}
22962320

2297-
// Use the dynamic + short statement descriptor if enabled and it's a card payment.
2298-
$is_short_statement_descriptor_enabled = 'yes' === $this->get_option( 'is_short_statement_descriptor_enabled', 'no' );
2299-
if ( WC_Stripe_Payment_Methods::CARD === $selected_payment_type && $is_short_statement_descriptor_enabled ) {
2300-
$payment_information['statement_descriptor_suffix'] = WC_Stripe_Helper::get_dynamic_statement_descriptor_suffix( $order );
2321+
if ( $payment_information['has_subscription'] ) {
2322+
$payment_information['payment_method_options'][ $selected_payment_type ]['setup_future_usage'] = 'off_session';
23012323
}
23022324

23032325
return $payment_information;
23042326
}
23052327

2328+
/**
2329+
* Add or remove payment information fields for the payment method flow.
2330+
*
2331+
* @param array $payment_information The base payment information.
2332+
* @param string $selected_payment_type The selected payment type.
2333+
* @param WC_Order $order The WC Order being processed.
2334+
* @return array The customized payment information for the payment method flow.
2335+
*/
2336+
private function prepare_payment_information_for_payment_method( $payment_information, $selected_payment_type, $order ) {
2337+
$payment_information['payment_method_options'] = $this->get_payment_method_options(
2338+
$selected_payment_type,
2339+
$order,
2340+
$payment_information['payment_method_details']
2341+
);
2342+
2343+
return $payment_information;
2344+
}
2345+
23062346
/**
23072347
* Returns the payment method options for the selected payment type.
23082348
*

includes/payment-methods/class-wc-stripe-upe-payment-method-amazon-pay.php

+31
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* Amazon Pay Payment Method class extending UPE base class
88
*/
99
class WC_Stripe_UPE_Payment_Method_Amazon_Pay extends WC_Stripe_UPE_Payment_Method {
10+
use WC_Stripe_Subscriptions_Trait;
1011

1112
const STRIPE_ID = WC_Stripe_Payment_Methods::AMAZON_PAY;
1213

@@ -24,6 +25,36 @@ public function __construct() {
2425
'Amazon Pay is a payment method that allows customers to pay with their Amazon account.',
2526
'woocommerce-gateway-stripe'
2627
);
28+
$this->supports[] = 'tokenization';
29+
30+
// Check if subscriptions are enabled and add support for them.
31+
$this->maybe_init_subscriptions();
32+
}
33+
34+
/**
35+
* Returns string representing payment method type
36+
* to query to retrieve saved payment methods from Stripe.
37+
*/
38+
public function get_retrievable_type() {
39+
return $this->get_id();
40+
}
41+
42+
/**
43+
* Create new WC payment token and add to user.
44+
*
45+
* @param int $user_id WP_User ID
46+
* @param object $payment_method Stripe payment method object
47+
*
48+
* @return WC_Payment_Token_Amazon_Pay
49+
*/
50+
public function create_payment_token_for_user( $user_id, $payment_method ) {
51+
$token = new WC_Payment_Token_Amazon_Pay();
52+
$token->set_email( $payment_method->billing_details->email ?? '' );
53+
$token->set_gateway_id( WC_Stripe_Payment_Tokens::UPE_REUSABLE_GATEWAYS_BY_PAYMENT_METHOD[ self::STRIPE_ID ] );
54+
$token->set_token( $payment_method->id );
55+
$token->set_user_id( $user_id );
56+
$token->save();
57+
return $token;
2758
}
2859

2960
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
if ( ! defined( 'ABSPATH' ) ) {
4+
exit; // Exit if accessed directly
5+
}
6+
7+
// phpcs:disable WordPress.Files.FileName
8+
9+
/**
10+
* WooCommerce Stripe Amazon Pay Payment Token.
11+
*
12+
* Representation of a payment token for Amazon Pay.
13+
*
14+
* @class WC_Payment_Token_Amazon_Pay
15+
*/
16+
class WC_Payment_Token_Amazon_Pay extends WC_Payment_Token implements WC_Stripe_Payment_Method_Comparison_Interface {
17+
/**
18+
* Stores payment type.
19+
*
20+
* @var string
21+
*/
22+
protected $type = WC_Stripe_Payment_Methods::AMAZON_PAY;
23+
24+
/**
25+
* Stores Amazon Pay payment token data.
26+
*
27+
* @var array
28+
*/
29+
protected $extra_data = [
30+
'email' => '',
31+
];
32+
33+
/**
34+
* Get type to display to user.
35+
*
36+
* @param string $deprecated Deprecated since WooCommerce 3.0
37+
* @return string
38+
*/
39+
public function get_display_name( $deprecated = '' ) {
40+
$display = sprintf(
41+
/* translators: customer email */
42+
__( 'Amazon Pay (%s)', 'woocommerce-gateway-stripe' ),
43+
$this->get_email()
44+
);
45+
46+
return $display;
47+
}
48+
49+
/**
50+
* Hook prefix
51+
*/
52+
protected function get_hook_prefix() {
53+
return 'woocommerce_payment_token_amazon_pay_get_';
54+
}
55+
56+
/**
57+
* Returns the customer email.
58+
*
59+
* @param string $context What the value is for. Valid values are view and edit.
60+
*
61+
* @return string Customer email.
62+
*/
63+
public function get_email( $context = 'view' ) {
64+
return $this->get_prop( 'email', $context );
65+
}
66+
67+
/**
68+
* Set the customer email.
69+
*
70+
* @param string $email Customer email.
71+
*/
72+
public function set_email( $email ) {
73+
$this->set_prop( 'email', $email );
74+
}
75+
76+
/**
77+
* Checks if the payment method token is equal a provided payment method.
78+
*
79+
* @inheritDoc
80+
*/
81+
public function is_equal_payment_method( $payment_method ): bool {
82+
if ( WC_Stripe_Payment_Methods::AMAZON_PAY === $payment_method->type
83+
&& ( $payment_method->billing_details->email ?? null ) === $this->get_email() ) {
84+
return true;
85+
}
86+
87+
return false;
88+
}
89+
}

includes/payment-tokens/class-wc-stripe-payment-tokens.php

+13
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class WC_Stripe_Payment_Tokens {
2323
const UPE_REUSABLE_GATEWAYS_BY_PAYMENT_METHOD = [
2424
WC_Stripe_UPE_Payment_Method_CC::STRIPE_ID => WC_Stripe_UPE_Payment_Gateway::ID,
2525
WC_Stripe_UPE_Payment_Method_Link::STRIPE_ID => WC_Stripe_UPE_Payment_Gateway::ID,
26+
WC_Stripe_UPE_Payment_Method_Amazon_Pay::STRIPE_ID => WC_Stripe_UPE_Payment_Gateway::ID,
2627
WC_Stripe_UPE_Payment_Method_ACH::STRIPE_ID => WC_Stripe_UPE_Payment_Gateway::ID . '_' . WC_Stripe_UPE_Payment_Method_ACH::STRIPE_ID,
2728
WC_Stripe_UPE_Payment_Method_Bancontact::STRIPE_ID => WC_Stripe_UPE_Payment_Gateway::ID . '_' . WC_Stripe_UPE_Payment_Method_Bancontact::STRIPE_ID,
2829
WC_Stripe_UPE_Payment_Method_Ideal::STRIPE_ID => WC_Stripe_UPE_Payment_Gateway::ID . '_' . WC_Stripe_UPE_Payment_Method_Ideal::STRIPE_ID,
@@ -431,6 +432,13 @@ public function get_account_saved_payment_methods_list_item( $item, $payment_tok
431432
esc_html( $payment_token->get_email() )
432433
);
433434
break;
435+
case WC_Stripe_Payment_Methods::AMAZON_PAY:
436+
$item['method']['brand'] = sprintf(
437+
/* translators: customer email */
438+
esc_html__( 'Amazon Pay (%s)', 'woocommerce-gateway-stripe' ),
439+
esc_html( $payment_token->get_email() )
440+
);
441+
break;
434442
}
435443

436444
return $item;
@@ -555,6 +563,10 @@ private function add_token_to_user( $payment_method, WC_Stripe_Customer $custome
555563
$token->set_email( $payment_method->link->email );
556564
$token->set_payment_method_type( $payment_method_type );
557565
break;
566+
case WC_Stripe_UPE_Payment_Method_Amazon_Pay::STRIPE_ID:
567+
$token = new WC_Payment_Token_Amazon_Pay();
568+
$token->set_email( $payment_method->billing_details->email ?? '' );
569+
break;
558570
case WC_Stripe_UPE_Payment_Method_ACH::STRIPE_ID:
559571
$token = new WC_Payment_Token_ACH();
560572
if ( isset( $payment_method->us_bank_account->last4 ) ) {
@@ -643,6 +655,7 @@ public static function get_token_label_overrides_for_checkout() {
643655
WC_Stripe_UPE_Payment_Method_Cash_App_Pay::STRIPE_ID,
644656
WC_Stripe_UPE_Payment_Method_Link::STRIPE_ID,
645657
WC_Stripe_UPE_Payment_Method_Bacs_Debit::STRIPE_ID,
658+
WC_Stripe_UPE_Payment_Method_Amazon_Pay::STRIPE_ID,
646659
];
647660

648661
foreach ( $payment_method_types as $stripe_id ) {

readme.txt

+1
Original file line numberDiff line numberDiff line change
@@ -137,5 +137,6 @@ If you get stuck, you can ask for help in the [Plugin Forum](https://wordpress.o
137137
* Add - Disable unsupported payment methods in Stripe settings
138138
* Update - Update handling of PR as a country in the terminal locations endpoint.
139139
* Fix - Hide Amazon Pay in settings when legacy checkout is enabled.
140+
* Fix - Fix subscription renewal issues for Amazon Pay.
140141

141142
[See changelog for all versions](https://raw.githubusercontent.com/woocommerce/woocommerce-gateway-stripe/trunk/changelog.txt).

tests/phpunit/payment-methods/test-class-wc-stripe-upe-payment-method.php

+17
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,17 @@ class WC_Stripe_UPE_Payment_Method_Test extends WP_UnitTestCase {
3838
],
3939
];
4040

41+
/**
42+
* Base template for Stripe Amazon Pay payment method.
43+
*/
44+
const MOCK_AMAZON_PAY_PAYMENT_METHOD_TEMPLATE = [
45+
'id' => 'pm_mock_payment_method_id',
46+
'type' => 'amazon_pay',
47+
'billing_details' => [
48+
'email' => '[email protected]',
49+
],
50+
];
51+
4152
/**
4253
* Base template for Stripe ACH payment method.
4354
*/
@@ -787,6 +798,12 @@ public function test_create_payment_token_for_user() {
787798
$this->assertTrue( WC_Payment_Token_Link::class === get_class( $token ) );
788799
$this->assertSame( $token->get_email(), $link_payment_method_mock->link->email );
789800
break;
801+
case WC_Stripe_UPE_Payment_Method_Amazon_Pay::STRIPE_ID:
802+
$amazon_payment_method_mock = $this->array_to_object( self::MOCK_AMAZON_PAY_PAYMENT_METHOD_TEMPLATE );
803+
$token = $payment_method->create_payment_token_for_user( $user_id, $amazon_payment_method_mock );
804+
$this->assertTrue( WC_Payment_Token_Amazon_Pay::class === get_class( $token ) );
805+
$this->assertSame( $token->get_email(), $amazon_payment_method_mock->billing_details->email );
806+
break;
790807
case WC_Stripe_UPE_Payment_Method_Cash_App_Pay::STRIPE_ID:
791808
$cash_app_payment_method_mock = $this->array_to_object( self::MOCK_CASH_APP_PAYMENT_METHOD_TEMPLATE );
792809
$token = $payment_method->create_payment_token_for_user( $user_id, $cash_app_payment_method_mock );

0 commit comments

Comments
 (0)