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.
| Comment | File | Size | Author |
|---|---|---|---|
| #72 | commerce_stripe-off-session-payments-3171408-72.patch | 8.76 KB | johnpitcairn |
Issue fork commerce_stripe-3171408
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
Comment #2
jonathanshawComment #4
JeremyFrench commentedHi 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_sessionI guess we should not set up future usage if this is set to none.
Comment #5
jonathanshawThanks 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.
Comment #6
jonathanshawComment #8
jonathanshawThe remaining test fails are due to broken tests on HEAD: #3171898: Fix broken HEAD.
Comment #9
JeremyFrench commentedI'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.
Comment #10
jonathanshawIf 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.
Comment #11
JeremyFrench commentedThere is already an issue for anonymous recuring here https://www.drupal.org/project/commerce_recurring/issues/2940084
Comment #12
jonathanshawI 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.
Comment #13
vince commented+1
Comment #14
penyaskitoRe-rolled #5 on top of #3171411-6: Optionally autosubmit the review pane to streamline UX
Comment #15
penyaskitoAnd 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.
Comment #16
penyaskitoPer 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.
Comment #17
penyaskitoFor 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.
Comment #18
jeff veit commentedHere'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.
Comment #20
jeff veit commentedI'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.
Comment #21
jeff veit commentedJust 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.
Comment #22
jonathanshawI 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.
Comment #23
liliplanet commented@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 3184get 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 🌿
Comment #24
jonathanshaw@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.
Comment #25
liliplanet commented@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:
Orders show ‘needs payment’ and subscription is still active
In Stripe (test mode), ‘payment declined by bank’.
===
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 🌷
Comment #26
jonathanshawI 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."
Comment #27
liliplanet commentedOh wow, thank you Jonathan 🌷
Comment #28
jeff veit commentedJohnathan 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?
Comment #29
liliplanet commentedOh 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 🌷
Comment #30
jonathanshaw@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
Comment #31
renrhafUsing 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 ?
Comment #32
jonathanshawWe need to fix the test failures in #18, review and RTBC.
Comment #33
johnpitcairn commentedThis needs a reroll following the changes in recent commits.
Comment #34
bachbach commentedhere is the patch #18 for last commits
Comment #35
jonathanshawTests 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.
Comment #36
jonathanshawThis should now be simplified and rerolled on top of #3259211: Provide more parameters to createPaymentIntent() and #3259349: Allow for payments without stripe review pane.
Comment #37
tunicWe 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.
Comment #38
jonathanshaw@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.
Comment #39
tunicOk, 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!
Comment #40
johnpitcairn commentedThe 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.Comment #41
johnpitcairn commentedThis patch applies on top of #3259211: Provide more parameters to createPaymentIntent() and #3259349: Allow for payments without stripe review pane.
Comment #42
johnpitcairn commentedAnd this one includes those patches.
Comment #44
johnpitcairn commentedTest failures are the same as for #3259211: Provide more parameters to createPaymentIntent(), good. Setting back to postponed as per #36.
Comment #45
johnpitcairn commentedComment #46
skyredwangreroll #34 patch against latest dev
Comment #47
skyredwang#46 file contained wrong patch
Comment #48
johnpitcairn commented@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.
Comment #49
acUpdated #3171408-41: Allow off_session payment intents for recurring payments so it applies again.
Comment #50
johnpitcairn commented@ac Thank you.
Comment #51
johnpitcairn commentedUm 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.
Comment #52
ac#41 no longer applied due to commits on #3259211: Provide more parameters to createPaymentIntent() (latest 2nd May 2022)
Comment #53
johnpitcairn commentedAh, 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.
Comment #54
acI did leave an extra line in my reroll though. Attached fixes it.
Comment #55
johnpitcairn commentedComment #56
johnpitcairn commented#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.
Comment #57
johnpitcairn commented#3259211: Provide more parameters to createPaymentIntent() is committed so this can be unpostponed.
Comment #58
jonathanshawNo, this is still postponed on #3259349: Allow for payments without stripe review pane. Please review and RTBC that.
Comment #59
bachbach commentedRerolled 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
Comment #60
bachbach commentedComment #64
anand.toshniwal93 commentedRe-rolled the patch against dev branch
Comment #66
jonathanshaw@jsacksick committed the blocker #3259349: Allow for payments without stripe review pane.
With a reroll, tests should pass and this should be RTBC.
Comment #67
johnpitcairn commentedWill attempt a reroll.
Comment #68
johnpitcairn commented@riabovol: Regarding the MRs from #61-63, I don't see @Bojan's comments anywhere in this thread.
Comment #69
johnpitcairn commentedLet's see how this goes.
Comment #71
johnpitcairn commentedHmm. 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.
Comment #72
johnpitcairn commentedThis 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 forPaymentInterface::getAmount(), to avoid aMinorUnitsConverter::toMinorUnits()error.Comment #73
johnpitcairn commentedComment #74
johnpitcairn commentedComment #75
jonathanshawThanks @John!
Comment #76
NewZeal commentedI'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.
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.
Comment #77
johnpitcairn commented@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?
Comment #78
NewZeal commentedCommerce 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.
Comment #79
NewZeal commentedComment #80
johnpitcairn commentedHm. 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.
Comment #81
johnpitcairn commentedComment #82
NewZeal commentedThanks, @John Pitcairn. I'll try running cron from crontab, and post this issue on a suitable module elsewhere.
Comment #83
jsacksick commentedTrusting you guys on this, committed, thanks!
Comment #85
jsacksick commentedComment #86
jsacksick commented