diff --git a/assets/images/bank-debit.svg b/assets/images/bank-debit.svg
new file mode 100644
index 0000000000..dc61f40270
--- /dev/null
+++ b/assets/images/bank-debit.svg
@@ -0,0 +1 @@
+
diff --git a/client/payment-method-icons/bank-debit/icon.svg b/client/payment-method-icons/bank-debit/icon.svg
new file mode 100644
index 0000000000..01d68b796f
--- /dev/null
+++ b/client/payment-method-icons/bank-debit/icon.svg
@@ -0,0 +1,5 @@
+
diff --git a/client/payment-method-icons/bank-debit/index.js b/client/payment-method-icons/bank-debit/index.js
new file mode 100644
index 0000000000..bee7ba6dc4
--- /dev/null
+++ b/client/payment-method-icons/bank-debit/index.js
@@ -0,0 +1,7 @@
+import React from 'react';
+import IconWithShell from '../styles/icon-with-shell';
+import icon from './icon.svg';
+
+const BankDebitIcon = ( props ) => ;
+
+export default BankDebitIcon;
diff --git a/client/payment-method-icons/index.js b/client/payment-method-icons/index.js
index c1ad9d7bac..7e032411de 100644
--- a/client/payment-method-icons/index.js
+++ b/client/payment-method-icons/index.js
@@ -16,6 +16,7 @@ import BoletoIcon from './boleto';
import OxxoIcon from './oxxo';
import WechatPayIcon from './wechat-pay';
import CashAppIcon from './cashapp';
+import BankDebitIcon from './bank-debit';
export default {
alipay: AlipayIcon,
@@ -36,5 +37,6 @@ export default {
oxxo: OxxoIcon,
wechat_pay: WechatPayIcon,
cashapp: CashAppIcon,
- bacs_debit: CreditCardIcon,
+ us_bank_account: BankDebitIcon,
+ bacs_debit: BankDebitIcon,
};
diff --git a/client/payment-methods-map.js b/client/payment-methods-map.js
index 20255dd485..ed1bf03712 100644
--- a/client/payment-methods-map.js
+++ b/client/payment-methods-map.js
@@ -3,6 +3,7 @@ import icons from './payment-method-icons';
const accountCountry =
window.wc_stripe_settings_params?.account_country || 'US';
+const isAchEnabled = window.wc_stripe_settings_params?.is_ach_enabled === '1';
const paymentMethodsMap = {
card: {
@@ -241,6 +242,19 @@ const paymentMethodsMap = {
},
};
+if ( isAchEnabled ) {
+ paymentMethodsMap.us_bank_account = {
+ id: 'us_bank_account',
+ label: __( 'ACH Direct Debit', 'woocommerce-gateway-stripe' ),
+ description: __(
+ 'ACH lets you accept payments from customers with a US bank account.',
+ 'woocommerce-gateway-stripe'
+ ),
+ Icon: icons.us_bank_account,
+ currencies: [ 'USD' ],
+ };
+}
+
// Enable Bacs according to feature flag value
if ( window.wc_stripe_settings_params?.is_bacs_enabled ) {
paymentMethodsMap.bacs_debit = {
diff --git a/client/stripe-utils/constants.js b/client/stripe-utils/constants.js
index 02f7de36b9..59fa58d9dd 100644
--- a/client/stripe-utils/constants.js
+++ b/client/stripe-utils/constants.js
@@ -26,6 +26,7 @@ export const PAYMENT_METHOD_LINK = 'link';
* Payment method names constants with the `stripe` prefix
*/
export const PAYMENT_METHOD_STRIPE_CARD = 'stripe';
+export const PAYMENT_METHOD_STRIPE_ACH = 'stripe_us_bank_account';
export const PAYMENT_METHOD_STRIPE_GIROPAY = 'stripe_giropay';
export const PAYMENT_METHOD_STRIPE_EPS = 'stripe_eps';
export const PAYMENT_METHOD_STRIPE_IDEAL = 'stripe_ideal';
@@ -48,6 +49,7 @@ export const PAYMENT_METHOD_STRIPE_BACS = 'stripe_bacs_debit';
export function getPaymentMethodsConstants() {
return {
card: PAYMENT_METHOD_STRIPE_CARD,
+ us_bank_account: PAYMENT_METHOD_STRIPE_ACH,
giropay: PAYMENT_METHOD_STRIPE_GIROPAY,
eps: PAYMENT_METHOD_STRIPE_EPS,
ideal: PAYMENT_METHOD_STRIPE_IDEAL,
diff --git a/includes/abstracts/abstract-wc-stripe-payment-gateway.php b/includes/abstracts/abstract-wc-stripe-payment-gateway.php
index 65fc00490a..bca889a540 100644
--- a/includes/abstracts/abstract-wc-stripe-payment-gateway.php
+++ b/includes/abstracts/abstract-wc-stripe-payment-gateway.php
@@ -337,6 +337,7 @@ public function payment_icons() {
return apply_filters(
'wc_stripe_payment_icons',
[
+ WC_Stripe_Payment_Methods::ACH => '',
WC_Stripe_Payment_Methods::ALIPAY => '',
WC_Stripe_Payment_Methods::WECHAT_PAY => '',
WC_Stripe_Payment_Methods::BANCONTACT => '',
diff --git a/includes/class-wc-stripe-intent-controller.php b/includes/class-wc-stripe-intent-controller.php
index 756a732b5f..5a53fab60d 100644
--- a/includes/class-wc-stripe-intent-controller.php
+++ b/includes/class-wc-stripe-intent-controller.php
@@ -967,6 +967,7 @@ private function build_base_payment_intent_request_params( $payment_information
*/
public function is_mandate_data_required( $selected_payment_type, $is_using_saved_payment_method = false ) {
$payment_methods_with_mandates = [
+ WC_Stripe_Payment_Methods::ACH,
WC_Stripe_Payment_Methods::BACS_DEBIT,
WC_Stripe_Payment_Methods::SEPA_DEBIT,
WC_Stripe_Payment_Methods::BANCONTACT,
diff --git a/includes/constants/class-wc-stripe-payment-methods.php b/includes/constants/class-wc-stripe-payment-methods.php
index 66c1a79dbc..c7617cc519 100644
--- a/includes/constants/class-wc-stripe-payment-methods.php
+++ b/includes/constants/class-wc-stripe-payment-methods.php
@@ -4,6 +4,8 @@
* Class WC_Stripe_Payment_Methods
*/
class WC_Stripe_Payment_Methods {
+
+ const ACH = 'us_bank_account';
const AFFIRM = 'affirm';
const AFTERPAY_CLEARPAY = 'afterpay_clearpay';
const ALIPAY = 'alipay';
diff --git a/includes/payment-methods/class-wc-stripe-upe-payment-gateway.php b/includes/payment-methods/class-wc-stripe-upe-payment-gateway.php
index e7ecac4060..a1783d12a0 100644
--- a/includes/payment-methods/class-wc-stripe-upe-payment-gateway.php
+++ b/includes/payment-methods/class-wc-stripe-upe-payment-gateway.php
@@ -22,6 +22,7 @@ class WC_Stripe_UPE_Payment_Gateway extends WC_Gateway_Stripe {
*/
const UPE_AVAILABLE_METHODS = [
WC_Stripe_UPE_Payment_Method_CC::class,
+ WC_Stripe_UPE_Payment_Method_ACH::class,
WC_Stripe_UPE_Payment_Method_Alipay::class,
WC_Stripe_UPE_Payment_Method_Giropay::class,
WC_Stripe_UPE_Payment_Method_Klarna::class,
@@ -155,6 +156,11 @@ public function __construct() {
foreach ( self::UPE_AVAILABLE_METHODS as $payment_method_class ) {
+ // Show ACH only if feature is enabled.
+ if ( WC_Stripe_UPE_Payment_Method_ACH::class === $payment_method_class && ! WC_Stripe_Feature_Flags::is_ach_lpm_enabled() ) {
+ continue;
+ }
+
/** Show Sofort if it's already enabled. Hide from the new merchants and keep it for the old ones who are already using this gateway, until we remove it completely.
* Stripe is deprecating Sofort https://support.stripe.com/questions/sofort-is-being-deprecated-as-a-standalone-payment-method.
*/
@@ -2156,6 +2162,10 @@ protected function prepare_payment_information_from_request( WC_Order $order ) {
'has_subscription' => $this->has_subscription( $order->get_id() ),
];
+ if ( 'us_bank_account' === $selected_payment_type ) {
+ WC_Stripe_API::attach_payment_method_to_customer( $payment_information['customer'], $payment_method_id );
+ }
+
if ( ! empty( $payment_method_id ) ) {
$payment_method_details = WC_Stripe_API::get_payment_method( $payment_method_id );
$payment_information['payment_method'] = $payment_method_id;
@@ -2167,7 +2177,7 @@ protected function prepare_payment_information_from_request( WC_Order $order ) {
$order,
$payment_method_details
);
- $payment_information['capture_method'] = $capture_method;
+ $payment_information['capture_method'] = $capture_method;
} else {
$confirmation_token_id = sanitize_text_field( wp_unslash( $_POST['wc-stripe-confirmation-token'] ?? '' ) );
$payment_information['confirmation_token'] = $confirmation_token_id;
diff --git a/includes/payment-methods/class-wc-stripe-upe-payment-method-ach.php b/includes/payment-methods/class-wc-stripe-upe-payment-method-ach.php
new file mode 100644
index 0000000000..f4079cb9a0
--- /dev/null
+++ b/includes/payment-methods/class-wc-stripe-upe-payment-method-ach.php
@@ -0,0 +1,49 @@
+stripe_id = self::STRIPE_ID;
+ $this->title = __( 'ACH Direct Debit', 'woocommerce-gateway-stripe' );
+ $this->is_reusable = false; // Usually ACH requires verification per transaction.
+ $this->supported_currencies = [ 'USD' ];
+ $this->supported_countries = [ 'US' ];
+ $this->label = __( 'ACH Direct Debit', 'woocommerce-gateway-stripe' );
+ $this->description = __( 'Pay directly from your US bank account via ACH.', 'woocommerce-gateway-stripe' );
+ }
+
+ /**
+ * Checks if ACH is available for the Stripe account's country.
+ *
+ * @return bool True if US-based account; false otherwise.
+ */
+ public function is_available_for_account_country() {
+ return in_array( WC_Stripe::get_instance()->account->get_account_country(), $this->supported_countries, true );
+ }
+
+ /**
+ * Returns string representing payment method type
+ * to query to retrieve saved payment methods from Stripe.
+ */
+ public function get_retrievable_type() {
+ return $this->get_id();
+ }
+}
diff --git a/tests/phpunit/test-class-wc-stripe-upe-payment-gateway.php b/tests/phpunit/test-class-wc-stripe-upe-payment-gateway.php
index 2165d05f8d..c0e9223cad 100644
--- a/tests/phpunit/test-class-wc-stripe-upe-payment-gateway.php
+++ b/tests/phpunit/test-class-wc-stripe-upe-payment-gateway.php
@@ -117,6 +117,8 @@ class WC_Stripe_UPE_Payment_Gateway_Test extends WP_UnitTestCase {
public function set_up() {
parent::set_up();
+ update_option( WC_Stripe_Feature_Flags::LPM_ACH_FEATURE_FLAG_NAME, 'yes' );
+
$mock_account = $this->getMockBuilder( 'WC_Stripe_Account' )
->disableOriginalConstructor()
->getMock();
@@ -181,6 +183,11 @@ public function set_up() {
);
}
+ public function tear_down() {
+ parent::tear_down();
+ delete_option( WC_Stripe_Feature_Flags::LPM_ACH_FEATURE_FLAG_NAME );
+ }
+
/**
* Helper function to set $_POST vars for saved payment method.
*/
@@ -258,6 +265,7 @@ public function get_upe_available_payment_methods_provider() {
'US',
[
WC_Stripe_UPE_Payment_Method_CC::STRIPE_ID,
+ WC_Stripe_UPE_Payment_Method_ACH::STRIPE_ID,
WC_Stripe_UPE_Payment_Method_Alipay::STRIPE_ID,
WC_Stripe_UPE_Payment_Method_Klarna::STRIPE_ID,
WC_Stripe_UPE_Payment_Method_Affirm::STRIPE_ID,
diff --git a/tests/phpunit/test-class-wc-stripe-upe-payment-method.php b/tests/phpunit/test-class-wc-stripe-upe-payment-method.php
index c5c0d30de4..a8f2818fed 100644
--- a/tests/phpunit/test-class-wc-stripe-upe-payment-method.php
+++ b/tests/phpunit/test-class-wc-stripe-upe-payment-method.php
@@ -73,49 +73,51 @@ class WC_Stripe_UPE_Payment_Method_Test extends WP_UnitTestCase {
* Mock capabilities object from Stripe response--all inactive.
*/
const MOCK_INACTIVE_CAPABILITIES_RESPONSE = [
- 'alipay_payments' => 'inactive',
- 'bancontact_payments' => 'inactive',
- 'card_payments' => 'inactive',
- 'eps_payments' => 'inactive',
- 'giropay_payments' => 'inactive',
- 'klarna_payments' => 'inactive',
- 'affirm_payments' => 'inactive',
- 'clearpay_afterpay_payments' => 'inactive',
- 'ideal_payments' => 'inactive',
- 'p24_payments' => 'inactive',
- 'sepa_debit_payments' => 'inactive',
- 'sofort_payments' => 'inactive',
- 'transfers' => 'inactive',
- 'multibanco_payments' => 'inactive',
- 'boleto_payments' => 'inactive',
- 'oxxo_payments' => 'inactive',
- 'link_payments' => 'inactive',
- 'wechat_pay_payments' => 'inactive',
+ 'alipay_payments' => 'inactive',
+ 'bancontact_payments' => 'inactive',
+ 'card_payments' => 'inactive',
+ 'eps_payments' => 'inactive',
+ 'giropay_payments' => 'inactive',
+ 'klarna_payments' => 'inactive',
+ 'affirm_payments' => 'inactive',
+ 'clearpay_afterpay_payments' => 'inactive',
+ 'ideal_payments' => 'inactive',
+ 'p24_payments' => 'inactive',
+ 'sepa_debit_payments' => 'inactive',
+ 'sofort_payments' => 'inactive',
+ 'transfers' => 'inactive',
+ 'multibanco_payments' => 'inactive',
+ 'boleto_payments' => 'inactive',
+ 'oxxo_payments' => 'inactive',
+ 'link_payments' => 'inactive',
+ 'wechat_pay_payments' => 'inactive',
+ 'us_bank_account_ach_payments' => 'inactive',
];
/**
* Mock capabilities object from Stripe response--all active.
*/
const MOCK_ACTIVE_CAPABILITIES_RESPONSE = [
- 'alipay_payments' => 'active',
- 'bancontact_payments' => 'active',
- 'card_payments' => 'active',
- 'eps_payments' => 'active',
- 'giropay_payments' => 'active',
- 'klarna_payments' => 'active',
- 'affirm_payments' => 'active',
- 'clearpay_afterpay_payments' => 'active',
- 'ideal_payments' => 'active',
- 'p24_payments' => 'active',
- 'sepa_debit_payments' => 'active',
- 'sofort_payments' => 'active',
- 'transfers' => 'active',
- 'multibanco_payments' => 'active',
- 'boleto_payments' => 'active',
- 'oxxo_payments' => 'active',
- 'link_payments' => 'active',
- 'cashapp_payments' => 'active',
- 'wechat_pay_payments' => 'active',
+ 'alipay_payments' => 'active',
+ 'bancontact_payments' => 'active',
+ 'card_payments' => 'active',
+ 'eps_payments' => 'active',
+ 'giropay_payments' => 'active',
+ 'klarna_payments' => 'active',
+ 'affirm_payments' => 'active',
+ 'clearpay_afterpay_payments' => 'active',
+ 'ideal_payments' => 'active',
+ 'p24_payments' => 'active',
+ 'sepa_debit_payments' => 'active',
+ 'sofort_payments' => 'active',
+ 'transfers' => 'active',
+ 'multibanco_payments' => 'active',
+ 'boleto_payments' => 'active',
+ 'oxxo_payments' => 'active',
+ 'link_payments' => 'active',
+ 'cashapp_payments' => 'active',
+ 'wechat_pay_payments' => 'active',
+ 'us_bank_account_ach_payments' => 'active',
];
/**
@@ -245,6 +247,7 @@ public function test_payment_methods_show_correct_default_outputs() {
$boleto_method = $this->mock_payment_methods['boleto'];
$oxxo_method = $this->mock_payment_methods['oxxo'];
$wechat_pay_method = $this->mock_payment_methods['wechat_pay'];
+ $ach_method = $this->mock_payment_methods['us_bank_account'];
$this->assertEquals( WC_Stripe_Payment_Methods::CARD, $card_method->get_id() );
$this->assertEquals( 'Credit / Debit Card', $card_method->get_label() );
@@ -345,6 +348,13 @@ public function test_payment_methods_show_correct_default_outputs() {
$this->assertFalse( $wechat_pay_method->is_reusable() );
$this->assertEquals( WC_Stripe_Payment_Methods::WECHAT_PAY, $wechat_pay_method->get_retrievable_type() );
$this->assertEquals( '', $wechat_pay_method->get_testing_instructions() );
+
+ $this->assertEquals( WC_Stripe_Payment_Methods::ACH, $ach_method->get_id() );
+ $this->assertEquals( 'ACH Direct Debit', $ach_method->get_label() );
+ $this->assertEquals( 'ACH Direct Debit', $ach_method->get_title() );
+ $this->assertFalse( $ach_method->is_reusable() ); // Currently non-reusable; future improvement may change this.
+ $this->assertEquals( WC_Stripe_Payment_Methods::ACH, $ach_method->get_retrievable_type() );
+ $this->assertEquals( '', $ach_method->get_testing_instructions() );
}
/**
@@ -375,6 +385,7 @@ public function test_card_payment_method_capability_is_always_enabled() {
$multibanco_method = $this->mock_payment_methods['multibanco'];
$oxxo_method = $this->mock_payment_methods['oxxo'];
$wechat_pay_method = $this->mock_payment_methods['wechat_pay'];
+ $ach_method = $this->mock_payment_methods['us_bank_account'];
$this->assertTrue( $card_method->is_enabled_at_checkout() );
$this->assertFalse( $klarna_method->is_enabled_at_checkout() );
@@ -390,6 +401,7 @@ public function test_card_payment_method_capability_is_always_enabled() {
$this->assertFalse( $multibanco_method->is_enabled_at_checkout() );
$this->assertFalse( $oxxo_method->is_enabled_at_checkout() );
$this->assertFalse( $wechat_pay_method->is_enabled_at_checkout() );
+ $this->assertFalse( $ach_method->is_enabled_at_checkout() );
}
/**
@@ -605,6 +617,7 @@ public function test_payment_methods_are_reusable_if_cart_contains_subscription(
public function test_payment_methods_support_custom_name_and_description() {
$payment_method_ids = [
+ WC_Stripe_Payment_Methods::ACH,
WC_Stripe_Payment_Methods::CARD,
WC_Stripe_Payment_Methods::KLARNA,
WC_Stripe_Payment_Methods::AFTERPAY_CLEARPAY,
diff --git a/woocommerce-gateway-stripe.php b/woocommerce-gateway-stripe.php
index 1d53409a88..f7ce5e299c 100644
--- a/woocommerce-gateway-stripe.php
+++ b/woocommerce-gateway-stripe.php
@@ -222,6 +222,7 @@ public function init() {
require_once __DIR__ . '/includes/payment-methods/class-wc-stripe-upe-payment-gateway.php';
require_once __DIR__ . '/includes/payment-methods/class-wc-stripe-upe-payment-method.php';
require_once __DIR__ . '/includes/payment-methods/class-wc-stripe-upe-payment-method-cc.php';
+ require_once __DIR__ . '/includes/payment-methods/class-wc-stripe-upe-payment-method-ach.php';
require_once __DIR__ . '/includes/payment-methods/class-wc-stripe-upe-payment-method-alipay.php';
require_once __DIR__ . '/includes/payment-methods/class-wc-stripe-upe-payment-method-bacs.php';
require_once __DIR__ . '/includes/payment-methods/class-wc-stripe-upe-payment-method-giropay.php';