Problem/Motivation

Unsupported stripe payment method types leave orders stuck in cart (while paid in Stripe).
Shop customers and owners are frustrated.

Steps to reproduce

  1. Enable iDEAL | Wero payment method (or any other unsupported payment method) in your Stripe Account without knowing
  2. Pay an order with one of these payment methods, get charged but have your order stuck in cart (without recognizing)

The order process will run into an error with the following message

Drupal\commerce_payment\Exception\PaymentGatewayException: The selected stripe payment method type(stripe_ideal) is not currently supported. in Drupal\commerce_stripe\Plugin\Commerce\PaymentGateway\StripePaymentElement->createPaymentMethodFromStripePaymentMethod() (Zeile 769 in /web/modules/contrib/commerce_stripe/src/Plugin/Commerce/PaymentGateway/StripePaymentElement.php).

and while the customer is charged, the order is stuck in cart and the shop owner doesn't know, leading to negative company ratings.

Technical details:

URL in this case looks like this:
/checkout/123/review/return?payment_intent=pi_123456789xyz&payment_intent_client_secret=pi_123456789xyz_secret_abc&redirect_status=succeeded

Backtrace:

#0 /web/modules/contrib/commerce_stripe/src/Plugin/Commerce/PaymentGateway/StripePaymentElement.php(692): Drupal\commerce_stripe\Plugin\Commerce\PaymentGateway\StripePaymentElement->createPaymentMethodFromStripePaymentMethod()
#1 /web/modules/contrib/commerce/modules/payment/src/Controller/PaymentCheckoutController.php(138): Drupal\commerce_stripe\Plugin\Commerce\PaymentGateway\StripePaymentElement->onReturn()
#2 [internal function]: Drupal\commerce_payment\Controller\PaymentCheckoutController->returnPage()
#3 /web/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(123): call_user_func_array()
#4 /web/core/lib/Drupal/Core/Render/Renderer.php(634): Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->{closure:Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber::wrapControllerExecutionInRenderContext():121}()
#5 [internal function]: Drupal\Core\Render\Renderer::{closure:Drupal\Core\Render\Renderer::executeInRenderContext():634}()
#6 /web/core/lib/Drupal/Core/Render/Renderer.php(649): Fiber->resume()
#7 /web/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(121): Drupal\Core\Render\Renderer->executeInRenderContext()
#8 /web/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php(97): Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext()
#9 /vendor/symfony/http-kernel/HttpKernel.php(183): Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->{closure:Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber::onController():96}()
#10 /vendor/symfony/http-kernel/HttpKernel.php(76): Symfony\Component\HttpKernel\HttpKernel->handleRaw()
#11 /web/core/lib/Drupal/Core/StackMiddleware/Session.php(53): Symfony\Component\HttpKernel\HttpKernel->handle()
#12 /web/core/lib/Drupal/Core/StackMiddleware/KernelPreHandle.php(48): Drupal\Core\StackMiddleware\Session->handle()
#13 /web/core/lib/Drupal/Core/StackMiddleware/ContentLength.php(28): Drupal\Core\StackMiddleware\KernelPreHandle->handle()
#14 /web/core/modules/big_pipe/src/StackMiddleware/ContentLength.php(32): Drupal\Core\StackMiddleware\ContentLength->handle()
#15 /web/core/modules/page_cache/src/StackMiddleware/PageCache.php(118): Drupal\big_pipe\StackMiddleware\ContentLength->handle()
#16 /web/core/modules/page_cache/src/StackMiddleware/PageCache.php(92): Drupal\page_cache\StackMiddleware\PageCache->pass()
#17 /web/modules/contrib/crowdsec/src/Middleware.php(62): Drupal\page_cache\StackMiddleware\PageCache->handle()
#18 /web/modules/contrib/ban/src/BanMiddleware.php(61): Drupal\crowdsec\Middleware->handle()
#19 /web/core/lib/Drupal/Core/StackMiddleware/ReverseProxyMiddleware.php(48): Drupal\ban\BanMiddleware->handle()
#20 /web/core/lib/Drupal/Core/StackMiddleware/NegotiationMiddleware.php(51): Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle()
#21 /web/core/lib/Drupal/Core/StackMiddleware/AjaxPageState.php(53): Drupal\Core\StackMiddleware\NegotiationMiddleware->handle()
#22 /web/modules/contrib/remove_http_headers/src/StackMiddleware/RemoveHttpHeadersMiddleware.php(49): Drupal\Core\StackMiddleware\AjaxPageState->handle()
#23 /web/core/lib/Drupal/Core/StackMiddleware/StackedHttpKernel.php(54): Drupal\remove_http_headers\StackMiddleware\RemoveHttpHeadersMiddleware->handle()
#24 /web/core/lib/Drupal/Core/DrupalKernel.php(745): Drupal\Core\StackMiddleware\StackedHttpKernel->handle()
#25 /web/index.php(19): Drupal\Core\DrupalKernel->handle()
#26 {main}

Proposed resolution

There are some ways this could be solved or at least be improved to reduce the severity:
a) Find a way to only enable payment methods that are supported by commerce_stripe
Maybe the module could clearer point out (programmatically on the settings page?), which payment methods are supported and may be enabled at Stripe? (Through a clearly visible message?). Maybe it could even show an error or warning if it's possible to detect if other payment methods are enabled? (Via Stripe API?)

b) Find a way to hide unsupported payment methods in the checkout process

c) Support all existing (relevant) payment methods (combined with (a) or (b))

Remaining tasks

  1. Discuss
  2. Implement
  3. Test
  4. Release

User interface changes

API changes

Data model changes

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

anybody created an issue. See original summary.

anybody’s picture

Title: Unsupported stripe payment method types leave orders stuck in card (while paid in Stripe) » Unsupported stripe payment method types leave orders stuck in cart (while paid in Stripe)
Issue summary: View changes
anybody’s picture

anybody’s picture

anybody’s picture

Issue summary: View changes

grevil made their first commit to this issue’s fork.

grevil’s picture

Status: Active » Needs work

I created an initial draft of how this COULD look like. The implementation is not ideal, as currently the plugin ids don't match the external names. Furthermore I am not 100% sure, whether there is a payment method type for each payment method currently supported (e.g. I couldn't find a "google_pay" method type, even though it should be supported) or if there are payment method types that add support for multiple payment methods.

But what I AM sure about is, that we DEFINITLY need to remove the "automatic_payment_methods" key from the payment intent. The module currently misses a LOT of payment method types and to force the commerce_stripe admin user to check the code, which payment method types are actually supported, to only activate these payment method types in the stripe dashboard is really really bad...

Here is a quick description of "automatic_payment_methods":

When you enable this parameter, this PaymentIntent accepts payment methods that you enable in the Dashboard and that are compatible with this PaymentIntent’s other parameters.

(https://docs.stripe.com/api/payment_intents/create?api-version=2024-06-20)

grevil’s picture

StatusFileSize
new6.58 KB

Here a quick screenshot of the output of "getSupportedStripePaymentMethodTypes()":
screenshot

grevil’s picture

Here is a list of all supported stripe payment method types:

$payment_method_types = [
    'acss_debit',
    'affirm',
    'afterpay_clearpay',
    'alipay',
    'amazon_pay',
    'au_becs_debit',
    'bacs_debit',
    'bancontact',
    'blik',
    'boleto',
    'card',
    'card_present',
    'cashapp',
    'customer_balance',
    'eps',
    'fpx',
    'giropay',
    'grabpay',
    'ideal',
    'interac_present',
    'klarna',
    'konbini',
    'link',
    'mb_way',
    'mobilepay',
    'multibanco',
    'nz_bank_account',
    'oxxo',
    'p24',
    'paynow',
    'paypal',
    'pix',
    'promptpay',
    'revolut_pay',
    'sepa_debit',
    'sofort',
    'sunbit',
    'swish',
    'twint',
    'us_bank_account',
    'wechat_pay',
    'zip'
];

(https://docs.stripe.com/api/payment_methods/object#payment_method_object...)

@anybody and I thought, that we could add a new "StripePaymentMethodTypeInterface" method called "getStripePaymentMethodTypeId()" or something like that, which simply returns the stripe payment method type id.

We go over that list and create an array of supported payment method types, which we then pass to the payment intent.

As a bonus, we could also show a list of the supported payment methods (and for a bonus bonus, we could also make them checkboxes to enable / disable the types on Drupal's site in addition to enabling / disabling them via the stripe UI).

anybody’s picture

@Grevil: I think we'd need it to return an array instead of just one ID, because one @CommercePaymentMethodType could potentially provide support for multiple stripe payment_method_types?
On the other hand I'd LIVE to have only one @CommercePaymentMethodType per Stripe payment_method_types as it would clarify and simplify things a lot if there's a 1:1 relation between the class and the remote ID. Then we could even just strip the stripe_ prefix from the plugin ID like in your draft.

If not, it could make sense to allow adding a description per array entry for example to explain that "Card" is also used for Google Pay?

Would be great to have some rapid feedback from @tomtech what he thinks!

tomtech’s picture

@anybody / @grevil,

Did attempt to do something similar to what you are proposing.

Have you tested the inverse, though, or only the happy path?

If you pass a list of payment methods, rather than using automatic, and you pass "PayPal" from the Drupal side, and it is not enabled on the Stripe side, you get an exception and cannot check out. (Maybe they no longer do this, which would then make this more viable?)

I do have other thoughts to resolve this, but one would work with MySQL, but not MariaDB. (And would require a dependency on another contrib module.). It could be simplified so that it would work on both, but then would have views implications.

Trade-offs abound, unfortunately.

The ideal current solution is to get more payment method types implemented, but that hasn't happened as fast as we'd like.

This is documented, though, if you read the module documentation, linked on the home page. Here is the deep link: https://docs.drupalcommerce.org/v2/user-guide/payment/integrations/strip...

tomtech’s picture

A few other notes on this:

1. If you could configure them in both the site AND stripe, you just know people will say "I turned it on in Drupal, but it isn't showing up" (because it isn't turned on in Stripe). and vice versa. (That is also not taking into account all the other combinations that can occur. e.g. it is turned on in both Drupal AND Stripe...but the dollar amount is too small for that payment method. or the customer doesn't have GooglePay/ApplePay configured on their laptop, etc...)

2. Relating to another ticket asking about being able to reorder the payment methods, Stripe states "Unless your integration requires a code-based option for offering payment methods, Stripe recommends the automated option. This is because Stripe evaluates the currency, payment method restrictions, and other parameters to determine the list of supported payment methods. Payment methods that increase conversion and that are most relevant to the currency and customer’s location are prioritized."

3. IRT the mapping of stripe payment methods to drupal ones, the 2 outliers are Google Pay and Apple Pay. Stripe lists them as separate payment methods in their customer UX, but in their API, they are part of the card payment method...except when invoking payment_method_types when creating a payment intent...you instead have to specify them in the javascript element to exclude them, if you are using the card element.

4. If you do have a specific use case for a particular site, you could subscribe the StripeEvents::PAYMENT_INTENT_CREATE event, and modify the payment intent before it is created.

anybody’s picture

@tomtech thanks for your rapid feedback! :)

Before going into detail, I think the MVP is to only allow payment methods (currently) supported by the module. Thats's the BUG part in my eyes. Several times we experienced clients enabling an unsupported payment method or even Stripe auto-enabling one, while it simply never makes sense in our eyes to offer an unsupported payment method, because it leads to the described broken result.

The only UI we might introduce (if it makes sense for you) is a checkbox "Disable unsupported payment methods: Only allows payment methods that are supported both - by this module and enabled in your Stripe settings. Currently, the following payment methods are supported by this module: X, Y, Z. If this is unchecked, the module will use automatic_payment_methods enabled, so users will be able to select yet unsupported payment methods, if enabled in your Stripe settings."

Should we have that setting to opt-in into this or simply always do it? Would you merge this then in general, after review?

We'll reply separately to the other points, which are of course absolutely valid and could be solved here or in a follow-up or never.

Would once again be great to have your final feedback on the general point above. Then we're unblocked. Thanks!!

grevil’s picture

Status: Needs work » Needs review

@tomtech

If you pass a list of payment methods, rather than using automatic, and you pass "PayPal" from the Drupal side, and it is not enabled on the Stripe side, you get an exception and cannot check out. (Maybe they no longer do this, which would then make this more viable?)

Interestingly enough I didn't get an exception, the order just went trough using "PayPal", even though it is disabled. Which is definitly not optimal either. Best would be, if both "automatic_payment_methods" is enabled and "payment_method_types" could be passed, so that we are using a subset of the payment method types enabled in stripe.

Stripe currently already implements a similiar feature, where both "automatic_payment_methods" and "exclude_payment_method_types" can be passed. But this isn't feasible in the context of this module (as we would need to maintain a list of all payment method types supported by stripe).
I will create a ticket for the stripe support in which I'll ask if they would add support for allowing both "automatic_payment_methods" and "payment_method_types" to allow support of a subset of the enabled payment method types.

For the time being, we should at least add a list of currently supported payment method types by the module. Later on, we could switch this setting to a checkboxes form element to allow a subset of the supported method types.

Setting this to "Needs Review" for the Payment Method Type list.

grevil’s picture

StatusFileSize
new46.91 KB

This is what it looks like:
screenshot2

anybody’s picture

#17 is 100% supported by me, I think that's the best we can do here to:

  1. mitigate the issue
  2. help administrators and shop owners not having to dig through code to know what may be enabled
  3. help shop owners not getting bad ratings and errors
  4. help customers not ending in a broken checkout

Let's keep the draft, is one day Stripe may allow something like a subset to the enabled / automatic payment methods.

anybody’s picture

Status: Needs review » Reviewed & tested by the community

RTBC for MR !230. I'd vote to eventually keep the issue postponed for MR!229. Did you already reach out to the Stripe Support for assistance @grevil? I think they should also get this link, maybe they have an idea how to solve this use-case best. The module is very widely used!