diff --git a/assets/js/gateways/stripe.js b/assets/js/gateways/stripe.js index 806b62a7..e695c042 100644 --- a/assets/js/gateways/stripe.js +++ b/assets/js/gateways/stripe.js @@ -265,6 +265,7 @@ const stripeElements = function (publicKey) { // Build elements options based on mode const elementsOptions = { currency: wu_stripe.currency || 'usd', + excludedPaymentMethodTypes: wu_stripe.excluded_payment_method_types || ['klarna'], appearance: { theme: 'stripe', }, diff --git a/composer.json b/composer.json index aab71fd3..465a9afd 100644 --- a/composer.json +++ b/composer.json @@ -114,7 +114,7 @@ "berlindb/core": "^2.0.1", "mexitek/phpcolors": "^1.0.4", "phpdocumentor/reflection-docblock": "^5.3.0", - "stripe/stripe-php": "^17.4.0", + "stripe/stripe-php": "^21.0.0", "rakit/validation": "dev-master#ff003a35cdf5030a5f2482299f4c93f344a35b29", "ifsnop/mysqldump-php": "^2.12", "mpdf/mpdf": "^8.2.0", diff --git a/composer.lock b/composer.lock index 5db7f6ee..bb3d2c5e 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": "a7c66a4c175ca451efc114556a00cf44", + "content-hash": "806b6fa04ebbe4b7402822d0d95d9948", "packages": [ { "name": "amphp/amp", @@ -3372,28 +3372,28 @@ }, { "name": "stripe/stripe-php", - "version": "v17.6.0", + "version": "v21.0.0", "source": { "type": "git", "url": "https://github.com/stripe/stripe-php.git", - "reference": "a6219df5df1324a0d3f1da25fb5e4b8a3307ea16" + "reference": "0498d20d0c33490c38df956dd7cef49b3a2ad6f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/stripe/stripe-php/zipball/a6219df5df1324a0d3f1da25fb5e4b8a3307ea16", - "reference": "a6219df5df1324a0d3f1da25fb5e4b8a3307ea16", + "url": "https://api.github.com/repos/stripe/stripe-php/zipball/0498d20d0c33490c38df956dd7cef49b3a2ad6f0", + "reference": "0498d20d0c33490c38df956dd7cef49b3a2ad6f0", "shasum": "" }, "require": { "ext-curl": "*", "ext-json": "*", "ext-mbstring": "*", - "php": ">=5.6.0" + "php": ">=7.2.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "3.72.0", + "friendsofphp/php-cs-fixer": "3.94.0", "phpstan/phpstan": "^1.2", - "phpunit/phpunit": "^5.7 || ^9.0" + "phpunit/phpunit": "^8.0 || ^9.0" }, "type": "library", "extra": { @@ -3402,6 +3402,9 @@ } }, "autoload": { + "files": [ + "lib/version_check.php" + ], "psr-4": { "Stripe\\": "lib/" } @@ -3425,9 +3428,9 @@ ], "support": { "issues": "https://github.com/stripe/stripe-php/issues", - "source": "https://github.com/stripe/stripe-php/tree/v17.6.0" + "source": "https://github.com/stripe/stripe-php/tree/v21.0.0" }, - "time": "2025-08-27T19:32:42+00:00" + "time": "2026-07-16T00:34:23+00:00" }, { "name": "symfony/deprecation-contracts", diff --git a/inc/gateways/class-base-stripe-gateway.php b/inc/gateways/class-base-stripe-gateway.php index 4beddc1d..8aa6df6b 100644 --- a/inc/gateways/class-base-stripe-gateway.php +++ b/inc/gateways/class-base-stripe-gateway.php @@ -919,13 +919,14 @@ public function maybe_redirect_to_portal(): void { $subscription_data = apply_filters( 'wu_stripe_checkout_subscription_data', [ - 'payment_method_types' => $allowed_payment_method_types, - 'mode' => 'setup', - 'success_url' => $return_url, - 'cancel_url' => wu_get_current_url(), - 'billing_address_collection' => 'required', - 'client_reference_id' => $customer_id, - 'customer' => $s_customer_id, + 'payment_method_types' => $allowed_payment_method_types, + 'excluded_payment_method_types' => $this->get_excluded_payment_method_types(), + 'mode' => 'setup', + 'success_url' => $return_url, + 'cancel_url' => wu_get_current_url(), + 'billing_address_collection' => 'required', + 'client_reference_id' => $customer_id, + 'customer' => $s_customer_id, ], $gateway ); @@ -3448,6 +3449,20 @@ public function update_card_fields(): void { // phpcs:disable ?> get_id()}", $obj_name, [ - 'pk_key' => $this->publishable_key, - 'request_billing_address' => $this->request_billing_address, - 'add_new_card' => empty($saved_cards), - 'payment_method' => empty($saved_cards) ? 'add-new' : current(array_keys($saved_cards)), - 'currency' => strtolower((string) wu_get_setting('currency_symbol', 'USD')), + 'pk_key' => $this->publishable_key, + 'request_billing_address' => $this->request_billing_address, + 'add_new_card' => empty($saved_cards), + 'payment_method' => empty($saved_cards) ? 'add-new' : current(array_keys($saved_cards)), + 'excluded_payment_method_types' => $this->get_excluded_payment_method_types(), + 'currency' => strtolower((string) wu_get_setting('currency_symbol', 'USD')), ] ); diff --git a/inc/gateways/class-stripe-checkout-gateway.php b/inc/gateways/class-stripe-checkout-gateway.php index f7321e86..013e5f60 100644 --- a/inc/gateways/class-stripe-checkout-gateway.php +++ b/inc/gateways/class-stripe-checkout-gateway.php @@ -306,13 +306,14 @@ public function run_preflight() { } $subscription_data = [ - 'payment_method_types' => $allowed_payment_method_types, - 'success_url' => $redirect_url, - 'cancel_url' => $this->get_cancel_url(), - 'billing_address_collection' => 'required', - 'client_reference_id' => $this->customer->get_id(), - 'customer' => $s_customer->id, - 'metadata' => $metadata, + 'payment_method_types' => $allowed_payment_method_types, + 'excluded_payment_method_types' => $this->get_excluded_payment_method_types(), + 'success_url' => $redirect_url, + 'cancel_url' => $this->get_cancel_url(), + 'billing_address_collection' => 'required', + 'client_reference_id' => $this->customer->get_id(), + 'customer' => $s_customer->id, + 'metadata' => $metadata, ]; if ($this->order->should_auto_renew() && $this->order->has_recurring()) { diff --git a/inc/gateways/class-stripe-gateway.php b/inc/gateways/class-stripe-gateway.php index 2850eee7..61c378c8 100644 --- a/inc/gateways/class-stripe-gateway.php +++ b/inc/gateways/class-stripe-gateway.php @@ -452,11 +452,12 @@ public function run_preflight() { $intent_args = wp_parse_args( $intent_args, [ - 'amount' => $this->order->get_total() * wu_stripe_get_currency_multiplier(), - 'confirmation_method' => 'automatic', - 'setup_future_usage' => 'off_session', - 'currency' => strtolower((string) wu_get_setting('currency_symbol', 'USD')), - 'confirm' => false, + 'amount' => $this->order->get_total() * wu_stripe_get_currency_multiplier(), + 'confirmation_method' => 'automatic', + 'setup_future_usage' => 'off_session', + 'excluded_payment_method_types' => $this->get_excluded_payment_method_types(), + 'currency' => strtolower((string) wu_get_setting('currency_symbol', 'USD')), + 'confirm' => false, ] ); @@ -518,7 +519,8 @@ public function run_preflight() { $intent_args = wp_parse_args( $intent_args, [ - 'usage' => 'off_session', + 'usage' => 'off_session', + 'excluded_payment_method_types' => $this->get_excluded_payment_method_types(), ] ); diff --git a/tests/WP_Ultimo/Gateways/Stripe_Checkout_Gateway_Run_Preflight_Test.php b/tests/WP_Ultimo/Gateways/Stripe_Checkout_Gateway_Run_Preflight_Test.php index fe375d2e..5ef382ce 100644 --- a/tests/WP_Ultimo/Gateways/Stripe_Checkout_Gateway_Run_Preflight_Test.php +++ b/tests/WP_Ultimo/Gateways/Stripe_Checkout_Gateway_Run_Preflight_Test.php @@ -439,6 +439,16 @@ public function test_run_preflight_subscription_mode_uses_price_ids(): void { $this->captured_session_args['mode'], 'Recurring checkout must use subscription mode' ); + $this->assertSame( + ['card'], + $this->captured_session_args['payment_method_types'], + 'Stripe Checkout must preserve its card-only default' + ); + $this->assertSame( + ['klarna'], + $this->captured_session_args['excluded_payment_method_types'], + 'Stripe Checkout must exclude Klarna' + ); // Verify line_items exist and use price IDs (not deprecated format) $this->assertArrayHasKey( diff --git a/tests/WP_Ultimo/Gateways/Stripe_Gateway_Test.php b/tests/WP_Ultimo/Gateways/Stripe_Gateway_Test.php index bead8c66..c33f0e21 100644 --- a/tests/WP_Ultimo/Gateways/Stripe_Gateway_Test.php +++ b/tests/WP_Ultimo/Gateways/Stripe_Gateway_Test.php @@ -82,6 +82,26 @@ public function setUp(): void { $this->gateway->set_stripe_client($this->stripe_client_mock); } + public function test_excluded_payment_method_types_include_klarna(): void { + $this->assertSame(['klarna'], $this->gateway->get_excluded_payment_method_types()); + } + + public function test_excluded_payment_method_types_can_be_customized(): void { + $filter = static function ($payment_method_types) { + $payment_method_types[] = 'paypal'; + + return $payment_method_types; + }; + + add_filter('wu_stripe_excluded_payment_method_types', $filter); + + try { + $this->assertSame(['klarna', 'paypal'], $this->gateway->get_excluded_payment_method_types()); + } finally { + remove_filter('wu_stripe_excluded_payment_method_types', $filter); + } + } + // ------------------------------------------------------------------------- // Helper: build a fully-wired Stripe client mock // ------------------------------------------------------------------------- @@ -432,6 +452,7 @@ function ($fields) use (&$registered_field_ids) { */ public function test_run_preflight_creates_payment_intent_for_paid_order(): void { // Build a payment intent mock that expects create() to be called. + $created_args = null; $payment_intent = \Stripe\PaymentIntent::constructFrom([ 'id' => 'pi_new123', 'object' => 'payment_intent', @@ -444,7 +465,13 @@ public function test_run_preflight_creates_payment_intent_for_paid_order(): void ->getMock(); $payment_intents_mock->expects($this->once()) ->method('create') - ->willReturn($payment_intent); + ->willReturnCallback( + static function ($args) use (&$created_args, $payment_intent) { + $created_args = $args; + + return $payment_intent; + } + ); $payment_intents_mock->method('retrieve')->willReturn($payment_intent); $client = $this->build_stripe_client_mock(['paymentIntents' => $payment_intents_mock]); @@ -462,6 +489,8 @@ public function test_run_preflight_creates_payment_intent_for_paid_order(): void $this->assertArrayHasKey('stripe_client_secret', $result, 'Result must include stripe_client_secret'); $this->assertArrayHasKey('stripe_intent_type', $result, 'Result must include stripe_intent_type'); $this->assertSame('payment_intent', $result['stripe_intent_type']); + $this->assertSame(['klarna'], $created_args['excluded_payment_method_types']); + $this->assertArrayNotHasKey('payment_method_types', $created_args); // Cleanup. $context['payment']->delete(); @@ -475,6 +504,7 @@ public function test_run_preflight_creates_payment_intent_for_paid_order(): void * @return void */ public function test_run_preflight_creates_setup_intent_for_trial_order(): void { + $created_args = null; $setup_intent = \Stripe\SetupIntent::constructFrom([ 'id' => 'seti_new123', 'object' => 'setup_intent', @@ -487,7 +517,13 @@ public function test_run_preflight_creates_setup_intent_for_trial_order(): void ->getMock(); $setup_intents_mock->expects($this->once()) ->method('create') - ->willReturn($setup_intent); + ->willReturnCallback( + static function ($args) use (&$created_args, $setup_intent) { + $created_args = $args; + + return $setup_intent; + } + ); $setup_intents_mock->method('retrieve')->willReturn($setup_intent); $client = $this->build_stripe_client_mock(['setupIntents' => $setup_intents_mock]); @@ -505,6 +541,8 @@ public function test_run_preflight_creates_setup_intent_for_trial_order(): void $this->assertArrayHasKey('stripe_client_secret', $result); $this->assertArrayHasKey('stripe_intent_type', $result); $this->assertSame('setup_intent', $result['stripe_intent_type']); + $this->assertSame(['klarna'], $created_args['excluded_payment_method_types']); + $this->assertArrayNotHasKey('payment_method_types', $created_args); // Cleanup. $context['payment']->delete(); @@ -1015,7 +1053,7 @@ public function test_run_preflight_handles_exception_with_empty_error_code(): vo public function test_run_preflight_applies_payment_intent_args_filter(): void { $captured_args = null; - add_filter('wu_stripe_create_payment_intent_args', function ($args, $gateway) use (&$captured_args) { + add_filter('wu_stripe_create_payment_intent_args', function ($args) use (&$captured_args) { $captured_args = $args; return $args; }, 10, 2); @@ -1049,6 +1087,8 @@ public function test_run_preflight_applies_payment_intent_args_filter(): void { $this->assertNotNull($captured_args, 'wu_stripe_create_payment_intent_args filter must be applied'); $this->assertArrayHasKey('amount', $captured_args, 'Filter args must include amount'); $this->assertArrayHasKey('currency', $captured_args, 'Filter args must include currency'); + $this->assertSame(['klarna'], $captured_args['excluded_payment_method_types']); + $this->assertArrayNotHasKey('payment_method_types', $captured_args); // Cleanup. $context['payment']->delete();