Problem/Motivation

Currently the stripe gateway cannot handle off session payments, payments when the customer is not present in the checkout. This is because design was tightly coupled to the stripe_review checkout pane when the module was refactored for SCA.

This involves 3 inter-related current problems:
1. No way is provided to mark a payment intent as being off session (FIXED: #3259211: Provide more parameters to createPaymentIntent()
2. No way is provided to 'setup future usage' when a payment method is first registered.
3. When createPayment() is invoked on the Stripe gateway plugin (which is the only method Commerce Recurring calls) no payment intent is created or available (leading to #3108793: Stripe\PaymentIntent instance has invalid ID). (FIXED: #3259349: Allow for payments without stripe review pane

There are a lot of different issues filed for this module around this area, and a lot of different possible ways to address them and rearchitect the whole payment intent process. The purpose of this issue is to provide a simple solution that:
- requires no serious alteration to the existing architecture
- is not disruptive to existing checkouts
- solves the needs of developers using Commerce Recurring
- facilitates more radical customisation or refactoring rather than blocking it

Steps to reproduce

1. Install commerce recurring and configure a recurring subscription product
2. Checkout the product, the initial order will be successful
3. Pass time and run cron, the subsequent orders will fail.

Proposed resolution

1. Allow specifying parameters for the intent when calling Stripe::createPaymentIntent() (done in #3259211: Provide more parameters to createPaymentIntent())
2. Provide a 'Setup future usage' setting on the stripe_review pane and pass the appropriate value when creating the intent on that pane
3. Call createPaymentIntent() from createPayment() if no intent has previously been created for the order (done in #3259349: Allow for payments without stripe review pane)

Remaining tasks

1. Review
2. Obtain maintainer agreement in principle
3. Add test coverage
4. Commit

User interface changes

A new 'Setup future usage' setting on the Stripe Review pane

API changes

The interface for createPaymentIntent() is altered. It is preferable to provide a new array parameter that allows for the customising of any parameter of the intent, rather than gradually adding single parameters to the method that correspond to single parameters of the intent.

Data model changes

None.

CommentFileSizeAuthor
#79 Screenshot 2023-02-25 at 1.26.25 PM.png273.02 KBNewZeal
#72 commerce_stripe-off-session-payments-3171408-72.patch8.76 KBjohnpitcairn
#69 commerce_stripe-off-session-payments-3171408-69.patch10.02 KBjohnpitcairn
#64 commerce_stripe-off-session-payments-3171408-64.patch16.51 KBanand.toshniwal93
#59 commerce_stripe-off-session-payments-3171408-59.patch15.5 KBbachbach
#54 commerce_stripe-off-session-payments-3171408-54-requires-3259211-3259349-DO-NOT-TEST.patch8.82 KBac
#49 commerce_stripe-off-session-payments-3171408-49-requires-3259211-3259349-DO-NOT-TEST.patch8.81 KBac
#46 3171408-46.patch6.9 KBskyredwang
#42 commerce_stripe-off-session-payments-3171408-42-3259211-3259349-combined.patch23.19 KBjohnpitcairn
#41 commerce_stripe-off-session-payments-3171408-41-requires-3259211-3259349-DO-NOT-TEST.patch8.9 KBjohnpitcairn
#34 commerce_stripe-allow-off_session-payment-intents-for-recurring-payments-3171408-34.patch13.61 KBbachbach
#25 commerce_stripe.tar_.gz28.71 KBliliplanet
#25 410CDAE5-25CA-496A-A5DD-E053AF72386A.jpeg150.96 KBliliplanet
#25 4A25159D-1106-452D-A53B-2A788ABAD43F.jpeg146.64 KBliliplanet
#25 998BC8B7-C1E0-477D-BD3D-13EA01AD304A.jpeg212.12 KBliliplanet
#18 commerce_stripe-allow-off_session-payment-intents-for-recurring-payments-3171408-18.patch13.58 KBjeff veit
#17 3171408-17.patch13.92 KBpenyaskito
#17 3171408.interdiff.16-17.txt7.37 KBpenyaskito
#16 3171408-16.patch9.6 KBpenyaskito
#16 3171408.interdiff.15-16.txt1.44 KBpenyaskito
#15 3171408-15.patch9.64 KBpenyaskito
#14 3171408-14.patch8.39 KBpenyaskito
#5 interdiff-3171408-2-5.txt2.92 KBjonathanshaw
#5 3171408-5.patch8.4 KBjonathanshaw
#2 3171408-2.patch8.23 KBjonathanshaw
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

jonathanshaw created an issue. See original summary.

jonathanshaw’s picture

Title: Allow off_session payment intents » Allow off_session payment intents for recurring payments
Priority: Normal » Major
Status: Active » Needs review
StatusFileSize
new8.23 KB

Status: Needs review » Needs work

The last submitted patch, 2: 3171408-2.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

JeremyFrench’s picture

Hi I'm testing this patch.

One thing to note is that it looks like if the future usage is set to none on the checkout stage this produces an error on checkout.

Invalid setup_future_usage: must be one of on_session or off_session

I guess we should not set up future usage if this is set to none.

jonathanshaw’s picture

StatusFileSize
new8.4 KB
new2.92 KB

Thanks for testing @JeremyFrench, really helps. I just found the exact same issue that you did a minute before you posted, I think it is the cause of the test failures. Attched patch fixes it, as well as improving the translatability of the settings summary.

jonathanshaw’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 5: 3171408-5.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

jonathanshaw’s picture

Status: Needs work » Needs review

The remaining test fails are due to broken tests on HEAD: #3171898: Fix broken HEAD.

JeremyFrench’s picture

Status: Needs review » Reviewed & tested by the community

I've tested the new patch and it works for me.

The code looks straightforward too.

I have issues with anonymous users but I think that's outside the scope of this patch.

jonathanshaw’s picture

I have issues with anonymous users but I think that's outside the scope of this patch.

If you open a new issue that's cleanest, we can always add it back in here if it is relevant once we've got to the bottom of it.

JeremyFrench’s picture

There is already an issue for anonymous recuring here https://www.drupal.org/project/commerce_recurring/issues/2940084

jonathanshaw’s picture

Status: Reviewed & tested by the community » Needs work

I discovered a new edge case today in #3172994: closeOrder() should update the order to match payment method. It's possible for the order entity payment method and the payment entity payment method to have diverged if a customer deleted their recurring payment method and then created a new one. In this case, we should be following the payment entity as it's more specific than the order.

The same applies - even more importantly - to the amount, because commerce supports payment for only part of the order I believe. Basically any data from the order should be overriden by data from the payment if we have a payment.

Therefore we should add the payment as a third (optional) argument to createPaymentIntent() and make use of it.

vince’s picture

+1

penyaskito’s picture

penyaskito’s picture

Status: Needs work » Needs review
StatusFileSize
new9.64 KB

And with #3108793: Stripe\PaymentIntent instance has invalid ID, an intent is always created without taking into account if it should happen offsite. So the payment creation with the offsite parameter is never happening. I think we should merge both issues on this one, as we need to ensure that we are aware when the payment is aimed to be created from recurring. The only way I found of doing that is based on the order type, 'recurring'. The attached patch is still on top of #3171411-6: Optionally autosubmit the review pane to streamline UX.

penyaskito’s picture

StatusFileSize
new1.44 KB
new9.6 KB

Per conversation with @jonathanshaw, #3108793: Stripe\PaymentIntent instance has invalid ID shouldn't be needed, an intent is always created in the stripe review pane, so we can assume that if there is none, we are in an off_session scenario.
The attached patch is still on top of #3171411-6: Optionally autosubmit the review pane to streamline UX, but might apply with HEAD.

penyaskito’s picture

StatusFileSize
new7.37 KB
new13.92 KB

For something like #3172234: Allow to retry payments after failure, including (re)authentication for offsession payments (SCA) to be worked, and already suggested in #12, we need the payment created by commerce_recurring when we are creating a payment intent if that exists. So adding that as a third argument here.

Added tests for that. As these tests are calling the API for real, we do not really know what to expect, so we need a hack to assert if it was called with the right params. I'm (ab)using metadata for that in the test.

The attached patch is still on top of #3171411-6: Optionally autosubmit the review pane to streamline UX.

jeff veit’s picture

Here's the patch in #17 re-rolled on HEAD, not on top of #3171411-6.

For those interested in anonymous recurring: this is necessary, but not yet sufficient. It does not set up a Stripe customer.

On a wider note, I think that the future payment intent should be coming from two sources - the order items, and the setting for saving payments, which comes from the admin, or the customer. This patch does not do this, instead it uses a checkout setting.

Anyway, let's see if it applies.

Status: Needs review » Needs work

The last submitted patch, 18: commerce_stripe-allow-off_session-payment-intents-for-recurring-payments-3171408-18.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

jeff veit’s picture

I've been thinking more about this...

I think although the Stripe gateway, and other gateways, are the right place to implement this behaviour, but that the decision about whether a store needs customer-not-present transactions is a sort of policy decision, and the setting belongs at Commerce or Recurring level, or any other product-type module that needs customer-not-present payment. (More on that below.)

And that hints that I think that off-session is a card-related technical term for 'ability to process transactions with the customer not present'. An invoice module has this ability. It's the ability to bill for later payment. And I think that this is best encoded as supporting an interface.

I think that there should probably be a setting at Commerce or Recurring level for 'Require ability to process future transactions with the customer not present'. If set to yes/true, then card payment modules should be setting up future intent to be off-session. And if this setting is switched on, then only payment methods that support the interface should be listed.

There's that patch coming down the Commerce runway, https://www.drupal.org/project/commerce/issues/2871483, which allows admins to set whether to store payment methods. It has the settings for always, never or let the customer choose. These settings are for customer-present/on-session transactions. Meanwhile this patch is the counterpart for off-session; imo it should be dependent on the 483 patch - you can only ask for off-session if you also have the ability to make future charges.

So I think the ideal thing would be to split this patch into two - one with the policy decisions - settings - at Commerce level, probably. And the rest in Stripe (and other gateways.)

I think where this setting sits is worth discussion: it could be attached to products - e.g. Commerce Recurring allows for postpaid subscriptions and trials, so it could be specifically on those products, without being on other products. Or you can imagine it as a setting for all Commerce Recurring products; customers won't be sold a subscription unless there is a way to bill in the future*. (Different modules would implement different mechanisms for that ability - e.g. Invoice might just require an email address.) Or you can imagine it at Store level. Or you can imagine it as an a module which is shared between Commerce Recurring and any other Commerce modules which support future customer-not-present.

One other issue - whether to support anonymous customer not present. E.g. a subscription which is invoiced monthly, but the customer has no account on Drupal. Subscription stops if the customer does not pay the invoice.

* I think a recurring product always needs at least a way to contact a customer about payment. I don't think it actually needs a payment method, though almost all stores will want one. So we could just say Commerce Recurring does not support transactions where there is no payment method provided while the customer is present. I think that would preclude postpaid transactions without taking payment details while the customer is present.

Sorry about the essay. It's been going round and round my head.

jeff veit’s picture

Just to add I forgot about the setting for "Collect payment methods on free orders" which I think is part of https://www.drupal.org/project/commerce/issues/2871483. Potential interaction.

jonathanshaw’s picture

I don't think #20 is wrong, it would be good if commerce had more abstract specification of payment methods that work with customers not present. However, it doesn't, and there's no sign it will soon. Therefore as we need this now in commerce_stripe, and can solve it perfectly adequately here, let's do so. If commerce provides more generic support and specifications for these use cases, we can adap later to harmonise with them.

liliplanet’s picture

@jonathanshaw, thank you so much for your patch at #5!

After several years of trying to get Stripe and Recurring to play together, no more invalid id and payments go through for cards with No authentication (U.S. card): 4242 4242 4242 4242.

===

But run into a new issue, test cards with Authentication required: 4000 0027 6000 3184 get declined.

1. Stripe notice: The card was declined as the transaction requires authentication
2. Website the order stays active with: Payment required

Jonathan, is there perhaps a solution to this please? So close to making this work.

Most appreciated 🌿

jonathanshaw’s picture

@Liliplanet I've no particular ideas, AFAIK this patch works ok with cards that need auth. I suspect your checkout may not be configured properly for SCA, see #3171424: [meta] SCA, the Stripe Review pane and recurring payments for some discussion. If that's not the case, it would be really helpful if you could debug further.

liliplanet’s picture

@JonathanShaw, thank you for your response.

So I have now tried a couple of hours, still getting ‘card was declined. This transaction requires authentication’ on a card that needs authentication.

Steps:

  1. Disabled ‘review pane’, kept ‘stripe review pane’
  2. Setup future storage: off session

Off session

Orders show ‘needs payment’ and subscription is still active

Order

In Stripe (test mode), ‘payment declined by bank’.

Declined

===

See in attached files the Stripe module that includes your commits from #5 😁

===

Using a card with no authentication required works perfectly.

Oh, so close! Would immensely appreciate your guidance 🌷

jonathanshaw’s picture

I think your problem is that 4000002760003184 requires auth on every payment: "This card requires authentication on all transactions, regardless of how the card is set up." So the behavior you are seeing is correct fot this card.

To test this patch with how we want it to be in real life, use 4000002500003155: "This card requires authentication for one-time payments. However, if you set up this card and use the saved card for subsequent off-session payments, no further authentication is needed."

liliplanet’s picture

Oh wow, thank you Jonathan 🌷

jeff veit’s picture

Johnathan may be right, but I think you've hit a bug; I've spent the last week and a half looking at the Stripe module. I'll post some reports and patches today or tomorrow, but I think you have almost certainly hit a bug.

Jonathan - because I've been working on a whole lot of the Stripe problems together, plus I have a stack of the patches installed, I don't have a nice clean series of patches. What's the best way to tackle this so that other people benefit from this work too?

liliplanet’s picture

Oh yes please @Jonathan and @Jeff, a copy of the patched module would be appreciated, it’s a minefield out there 😊 Most most appreciate both of you for getting Stripe to work 🌷

jonathanshaw’s picture

@jsacksick is active in committing patches. I suspect he just hasn't comitted a lot because none of them are RTBC. We've got to get some to RTBC, and keep them individually small.

I suggest:
- start a meta issue for planning how to tackle a set of inter-related problems.
- we can use that to have a conversation, and to post combined patches for convenience.
- we figure out which issues need to be major
- we figure out what the significant gaps in test coverage are
- we fix the module's existing tests (get HEAD passing)
- we create the new test coverage
- we get the majors to RTBC
- we turn to the less major once the majors are committed

renrhaf’s picture

Using patch from #18 successfully allows me to create payment intents for recurring automatic billing (and avoiding the issue "Stripe\PaymentIntent instance has invalid ID")

In my use case, I'll always ask for off session payment intents as the customer might reuse one of his card for buying subscriptions or recurrent products. Thanks @Jeff Veit for the patch and @jonathanshaw for all your amazing documentation work.

What's left to be done here ?

jonathanshaw’s picture

We need to fix the test failures in #18, review and RTBC.

johnpitcairn’s picture

This needs a reroll following the changes in recent commits.

bachbach’s picture

here is the patch #18 for last commits

jonathanshaw’s picture

Issue summary: View changes
Status: Needs work » Reviewed & tested by the community

Tests pass, reroll looks good to me, I've been using an earlier version of the patch in production for months, other people have manually tested it, and various people have looked at the code.

jonathanshaw’s picture

Status: Reviewed & tested by the community » Postponed
tunic’s picture

We would like to use this functionality and it's great to hear this patch has been working on prod per #35.

While I understand it would be good to have this functionality well integrated into Commerce Stripe it seems this needs deep changes in the code, as it can be seen following the issues in #36. This means that the functionality may need months or even years to complete.

I like this issue's approach: "requires no serious alteration to the existing architecture". I would suggest maintaining this patch alive and working until a proper solution is developed. We plan to use it and we can update it if any issues are detected.

jonathanshaw’s picture

@tunic those 2 issues mentioned in #36 are not more complicated than the solution proposed in this issue; they are parts of the solution in this issue broken out into seperate issues because they are useful beyond this issue.

That said, it's perfectly reasonable for people wanting to contribute to this issue to always post 2 patches:
- 1 that is based on top of #3259211: Provide more parameters to createPaymentIntent() and #3259349: Allow for payments without stripe review pane that is ready to commit once those are committed; and
- 1 that is combined with the patches form those issues into a single patch

I'd love help getting those issues RTBC. They both have good patches with tests, just need reviews and possibly small test fixes.

tunic’s picture

Ok, thanks for the information in #38. Because there were different issues related I thought the second approach (the good one) had progressed not so much.

We'll try to test that second approach so we can help to push it forward. Thanks!

johnpitcairn’s picture

The most recent patch here still applies but now introduces some regressions into current dev Stripe::createPaymentIntent(). I'll try to take a look at re-rolling at splitting it up as per #38 in the next day or two.

johnpitcairn’s picture

Status: Postponed » Needs review
StatusFileSize
new23.19 KB

And this one includes those patches.

Status: Needs review » Needs work

The last submitted patch, 42: commerce_stripe-off-session-payments-3171408-42-3259211-3259349-combined.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

johnpitcairn’s picture

Test failures are the same as for #3259211: Provide more parameters to createPaymentIntent(), good. Setting back to postponed as per #36.

johnpitcairn’s picture

Status: Needs work » Postponed
skyredwang’s picture

StatusFileSize
new6.9 KB

reroll #34 patch against latest dev

skyredwang’s picture

#46 file contained wrong patch

johnpitcairn’s picture

@skyredwang if you want this issue to move forward, please also provide a patch that applies on top of #3259211: Provide more parameters to createPaymentIntent().

That will be the way forward, as per comment #38.

ac’s picture

johnpitcairn’s picture

@ac Thank you.

johnpitcairn’s picture

Um hang on. The last commit was 27 Apr, and #41 applied on top of that plus the other two patches mentioned, so I'm not sure why it needed a reroll? @ac Did you not apply the two required patches first? I think you may have just created another combo patch.

Note #41 requires the patches from #3259211: Provide more parameters to createPaymentIntent() and #3259349: Allow for payments without stripe review pane, all applied against latest dev commit.

A number of issues now depend on #3259211: Provide more parameters to createPaymentIntent() getting committed, including this issue.

ac’s picture

#41 no longer applied due to commits on #3259211: Provide more parameters to createPaymentIntent() (latest 2nd May 2022)

johnpitcairn’s picture

Ah, gotcha, I was thinking you meant latest dev.

I've just marked #3259211: Provide more parameters to createPaymentIntent() RTBC and reached out to a maintainer to see if we can get it committed. If not, I guess we need to reroll on top of both patches, since #3259349: Allow for payments without stripe review pane was split out of this issue as well. But let's see if we can get a commit.

ac’s picture

I did leave an extra line in my reroll though. Attached fixes it.

johnpitcairn’s picture

#42 is still the patch to use if you want a combo patch. The failing tests are related to failing tests in an earlier version of the included #3259211 and do not prevent it applying, since dev hasn't changed.

I may be able to reroll this in the next day or so.

johnpitcairn’s picture

Status: Postponed » Needs work

#3259211: Provide more parameters to createPaymentIntent() is committed so this can be unpostponed.

jonathanshaw’s picture

Status: Needs work » Postponed

No, this is still postponed on #3259349: Allow for payments without stripe review pane. Please review and RTBC that.

bachbach’s picture

Rerolled the patch against last 8.x.1.x-@dev
i did it kind of blinded, didn't try to understand what it is doing, just removed from #42 what was already there

bachbach’s picture

Status: Postponed » Needs review

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

anand.toshniwal93’s picture

Re-rolled the patch against dev branch

Status: Needs review » Needs work

The last submitted patch, 64: commerce_stripe-off-session-payments-3171408-64.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

jonathanshaw’s picture

Issue summary: View changes
Issue tags: +Needs reroll

@jsacksick committed the blocker #3259349: Allow for payments without stripe review pane.
With a reroll, tests should pass and this should be RTBC.

johnpitcairn’s picture

Assigned: Unassigned » johnpitcairn

Will attempt a reroll.

johnpitcairn’s picture

@riabovol: Regarding the MRs from #61-63, I don't see @Bojan's comments anywhere in this thread.

johnpitcairn’s picture

Assigned: johnpitcairn » Unassigned
Status: Needs work » Needs review
StatusFileSize
new10.02 KB

Let's see how this goes.

Status: Needs review » Needs work

The last submitted patch, 69: commerce_stripe-off-session-payments-3171408-69.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

johnpitcairn’s picture

Assigned: Unassigned » johnpitcairn

Hmm. Think I rerolled based on the wrong patch, it should be #54. MR18 is unrelated, and not sure about MR17. This is a mess to follow. Revisiting.

johnpitcairn’s picture

Assigned: johnpitcairn » Unassigned
Status: Needs work » Needs review
StatusFileSize
new8.76 KB

This patch should pass, it does locally.

One addition to PaymentIntentTest::testCreatePaymentIntent() - if we are testing the dataset with a passed-in payment generated, the prophesized payment needs to return a valid Price for PaymentInterface::getAmount(), to avoid a MinorUnitsConverter::toMinorUnits() error.

johnpitcairn’s picture

johnpitcairn’s picture

Issue tags: -Needs reroll
jonathanshaw’s picture

Status: Needs review » Reviewed & tested by the community

Thanks @John!

NewZeal’s picture

I've been trying to get commerce_stripe going with recurring payments for a few weeks now while in Stripe test mode.
commerce_recurring 8.x-1.0-rc1
commerce_stripe 8.x-1.0-rc8
commerce_license 8.x-2.0-beta2

I've applied the patch at #72 and set my future usage to off session in the Stripe Review pane. I still get a fail on the first recurring attempt.

RuntimeException: Failed to start the session because headers have already been sent by "/vendor/symfony/http-foundation/Response.php" at line 1239. in Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage->start() (line 152 of /vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php).

This is different from the one I got prior to the patch, which was:

/html/core/modules/big_pipe/src/Render/BigPipe.php" at line 264. in Drupal\Core\Entity\Sql\SqlContentEntityStorage->save() (line 815 of /html/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php)

Will keep testing and see what I come up with.

johnpitcairn’s picture

@kent@passingphase.nz - If you remove commerce_license do recurring payments work? Are any other patches applied against commerce, commerce_recurring, advancedqueue or commerce_stripe?

NewZeal’s picture

Commerce license is an essential part of the recurring payment existence, in that these are subscriptions, for which is a license is assigned. If I have no luck I will try removing commerce license as a test.

The only other patch applied is to commerce, as follows: https://www.drupal.org/files/issues/2021-05-12/commerce-send_mail_after_... , which involves sending an email when purchasing and registering at the same time.

I do notice, however, that the error occurs when I am browsing the site at the same time that the recurring order attempts to process the payment. For instance, attached is a screenshot of the dblog error. At the time that this attempt was made via cron, I was looking at the admin/reports/fields page. It appears that I am interfering with the payment process while navigating the site. Weird.

NewZeal’s picture

StatusFileSize
new273.02 KB
johnpitcairn’s picture

Hm. How is cron being triggered? The built-in automatic cron, or an external cron run? So we don't junk up this RTBC issue any further, maybe this warrants a separate issue, probably in either commerce_recurring or commerce_license.

johnpitcairn’s picture

NewZeal’s picture

Thanks, @John Pitcairn. I'll try running cron from crontab, and post this issue on a suitable module elsewhere.

jsacksick’s picture

Status: Reviewed & tested by the community » Fixed

Trusting you guys on this, committed, thanks!

jsacksick’s picture

Status: Reviewed & tested by the community » Fixed
jsacksick’s picture

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.