-
Notifications
You must be signed in to change notification settings - Fork 682
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[10.0] Payment Intents #667
Conversation
ae72f18
to
a0dc130
Compare
aacb31d
to
f51e430
Compare
643b132
to
5a32e06
Compare
Except the "<" I really like it. Good work! Is the "<" used somewhere else already? If you not you can maybe just remove it. |
@christophrumpel thanks! I removed it. |
I wonder if Continue instead of Back will make more sense as a copy in the payment confirmation. |
@ipalaus good idea. It now looks as the following: The copy "back" is still the same for the main payment view when confirmation is still needed. |
Yeah, that looks good! Checking again, maybe it would look better if you center-aligned paragraphs "Extra confirmation..." and "This payment was already...". Both the title and button text are centered, and especially in the confirmation, it's just too short to look good left aligned. Up to you, it's a personal taste at the end. Feel free to ignore this one :) Also, awesome work. Thank you! |
@ipalaus gonna leave it as so because the text from the main confirmation page isn't centered either :) |
I can also add the amount on the page if needed with its currency sign 🤔 |
That would give more context to the view for sure 👍 |
b922c64
to
bccfedf
Compare
Great work @driesvints :) |
Hello, Thanks for the quick update for the new stripe version. Thank you in advance for your help. |
And I would be interested in hearing which version of Spark will get the new Stripe updates :-) |
} catch (IncompletePayment $exception) { | ||
$this->markAsIncomplete(); | ||
|
||
throw $exception; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is maybe not totally the expected logic I would want to see. Imagine a user has a monthly Forge plan and 15 days into their subscription they decide to swap to yearly. If the payment is incomplete, this marks the whole subscription as incomplete and the user would no longer be considered as having an "active" subscription, even though they have already paid for the whole month.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, you're correct. I hadn't considered that scenario. In that regard I think we should also update the webhooks since it will now always place a subscription in an incomplete state when its Stripe status changes to incomplete
or incomplete_expired
. I'll check into this and make some changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@taylorotwell I've checked into this and can confirm that no exception is being thrown when downgrading or when proration applies. Stripe doesn't throws an exception in this case. Stripe will mark the subscription as "past_due" in this case which means Stripe will try to re-charge the card in the intervals set in the dashboard. When a "next action" is required for the payment the invoice.payment_action_required
is fired which the email setting in this PR is for.
In the other situations during a card error or "next action" payment status when you swap to a plan and need to immediately pay for an invoice the subscription is put into an incomplete state, just like when creating one. But I believe that's wanted, right?
@@ -254,6 +259,7 @@ protected function buildPayload() | |||
return array_filter([ | |||
'billing_cycle_anchor' => $this->billingCycleAnchor, | |||
'coupon' => $this->coupon, | |||
'expand' => ['latest_invoice.payment_intent'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does this do?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It makes use of the expand
functionality from the Stripe API to retrieve data from objects in one request. By default when creating a new subscription, the latest_invoice
parameter contains a string with its ID. By using the expand
here we retrieve the actual object as well as the payment intent object so we can immediately use it below when validating the payment.
These changes bring support for the new payment intents api to charges and subscriptions. As there are quite some breaking changes here let's go over the most prominent ones below: Any payment action will now throw an exception when a payment either fails or when the payment requires a secondary action in order to be completed. This goes for single charges, invoicing customers directly, subscribing to a new plan or swapping plans. Developers can catch these exceptions and decide for themselves how to handle these by either letting Stripe handle everything for them (to be set up in the Stripe dashboard) or use the custom built-in solution which will be added in the next commit. A new status column is introduced for subscriptions as well. Whenever an attempt is made to subscribe to a plan but a secondary payment action is required, the subscription will be put into a state of "incomplete" while payment confirmation is awaited. As soon as payment has been properly processed, a webhook will update the subscription's status to active. After these changes, webhooks will be a fundamental part of how Cashier works and they're now required in order to properly handle any payment confirmations and off-session updates to subscriptions & customers. The charge method now only accepts a payment method instead of a token. Developers will need to update their JS integration to retrieve a payment method id instead of a source token. These changes were done because this is now the recommended way by Stripe to work with payment methods. More info about that can be found here: https://stripe.com/docs/payments/payment-methods#transitioning In an upcoming update all card methods as well as the create method on the subscription builder will be updated as well. Closes #636
These changes add a built-in payment page to Cashier. This offers a custom way for users to provide payment confirmations using, for example, 3D Secure. Developers can redirect to this page after catching an instance of an IncompletePayment exception. They can also pass a redirect url to which the user will be redirected after they've confirmed their payment.
These changes add a built-in way for Cashier to send reminder emails to the customer when payment confirmation is needed for off-session. For example, when a subscription renews. Not that a limitation of this addition is that emails will be sent out even when they're on-session during a payment that requires an extra action since there's no way to know for Stripe that the payment was done on or off session. But a customer will never be charged twice and will simply see a "Payment Successful" message if they visit the payment page again.
When a plan downgrade fails because of a next action requirement for the payment or a faulty card, we'll not put the subscription into an incomplete state because it could be that the person is upgrading to a yearly plan with a prorated invoice. This would mean that the customer still paid for the current period and would be allowed to continue on their subscription. These scenarios are now covered by these tests.
Not sure where it comes in with your new workflow if at all but Stripe documents refer to this quote to display users to help gain permission for off-session payment
|
Is that all or do we still have to wait for Stripe to add more? |
@mrsimonbennett heya thanks, I saw. There's a separate issue about this here: #649 |
@driesvints Thanks for your work on this 🙌. I'm building Payment Intents into our e-commerce framework and it's been great for inspiration and notes. One thing I noticed that might be an issue on your Payment class is the cancelled check - https://github.com/laravel/cashier/blob/billing-sca/src/Payment.php#L96. For some reason Stripe refer to this state as |
@davidrushton that's a very good spot. Thanks! |
@driesvints For those of us wanting to jump the gun and implement this straight away, are you able to provide very brief overview of the intended payment flow for setting up new subscriptions? For instance, where should the ID that gets passed to PaymentController be generated? |
I think @coatesap you need to be passing the StripePaymentIntent ID to it, which is generated by stripe when you attempt payment see step 4 https://stripe.com/docs/billing/subscriptions/payment |
Thanks @mrsimonbennett - that makes sense. I was thinking more in terms of whether that should take place on the back end / front end / etc. My current code seems to work ok, but wanted to check whether it followed the intended usage: try {
$user->newSubscription('default', $stripeRef)->create();
} catch (PaymentActionRequired $exception) {
return redirect()->route('cashier.payment', ['id' => $exception->payment->asStripePaymentIntent()->id]);
} |
For anyone else browsing this thread, https://github.com/laravel/cashier/blob/upgrade-guide/UPGRADE.md has some useful pointers. |
@coatesap that's the correct flow. I'm also working on new docs here: laravel/docs#5313 There have been some newer changes from last Thursday and Friday which I haven't gotten into the new upgrade guide or docs yet so beware of that. |
I don't know where to put that so let's say here : |
Yeah, man, a big thank you to @driesvints and @taylorotwell this was a massive job. |
THANKS! 😍 |
This PR aimes at updating the subscription based billing and single charges with the payment intents API to verify payments using 3D secure, amongst else.
Todo
incomplete
state for subscriptionsChanges
Payment Intents
These changes bring support for the new payment intents api to charges and subscriptions. As there are quite some breaking changes here let's go over the most prominent ones below:
Any payment action will now throw an exception when a payment either fails or when the payment requires a secondary action in order to be completed. This goes for single charges, invoicing customers directly, subscribing to a new plan or swapping plans. Developers can catch these exceptions and decide for themselves how to handle these by either letting Stripe handle everything for them (to be set up in the Stripe dashboard) or use the custom built-in solution which will be added in the next commit.
A new status column is introduced for subscriptions as well. Whenever an attempt is made to subscribe to a plan but a secondary payment action is required, the subscription will be put into a state of "incomplete" while payment confirmation is awaited. As soon as payment has been properly processed, a webhook will update the subscription's status to active.
After these changes, webhooks will be a fundamental part of how Cashier works and they're now required in order to properly handle any payment confirmations and off-session updates to subscriptions & customers.
The charge method now only accepts a payment method instead of a token. Developers will need to update their JS integration to retrieve a payment method id instead of a source token. These changes were done because this is now the recommended way by Stripe to work with payment methods. More info about that can be found here: https://stripe.com/docs/payments/payment-methods#transitioning
In an upcoming update all card methods as well as the create method on the subscription builder will be updated as well.
Dedicated Payment Page
These changes add a built-in payment page to Cashier. This offers a custom way for users to provide payment confirmations using, for example, 3D Secure.
Developers can redirect to this page after catching an instance of an IncompletePayment exception. They can also pass a redirect url to which the user will be redirected after they've confirmed their payment.
Screenshots for this page can be found here: #667 (comment)
Payment Confirmation Email
These changes add a built-in way for Cashier to send reminder emails to the customer when payment confirmation is needed for off-session. For example, when a subscription renews.
Note that a limitation of this addition is that emails will be sent out even when they're on-session during a payment that requires an extra action since there's no way to know for Stripe that the payment was done on or off session. But a customer will never be charged twice and will simply see a "Payment Successful" message if they visit the payment page again.
Resources
Concerns
Use incomplete status as well for failed payments? ✅
At the moment we immediately cancel a subscription when its initial payment fails. But maybe we should allow the customer a chance to use a different payment method first? At the moment his causes Stripe to be polluted with failed subscriptions and voided invoices. It would be better if the invoice just stayed open until paid.
Update 17/05: the new payment view can handle these now as well. A separate
PaymentFailure
exception will be thrown to inform the user about the specific failure and let them act accordingly.How to mark subscriptions as incomplete? ✅
When a subscription is incomplete we need to mark it as incomplete as well in Cashier. I first wanted to just set the
ends_at
to "now" but that won't work since it'll be marked as cancelled, which isn't really wanted. I think the only solution will be to introduce a newstatus
column on thesubscriptions
table and handle all the use cases in theSubscription
model.Update 20/05: I've now implemented a new
status
column as suggested above. It's checked in theactive
method on a subscription and properly cascades to thesubscribed
method on the billable entity.Also catch Stripe card errors when performing single charges? ✅
At the moment card errors are caught when paying a subscription invoice, swapping plans or creating a new subscription. They're wrapped in a
PaymentFailure
exception which is thrown. But Stripe card errors are still thrown as normal when performing single charges. I already implemented theActionRequired
exception for single charges but I'm not sure if we should also catch Stripe card errors in the same way we do for the other parts of the app.Update 28/06: decided to leave it as is for now. We can always decide to implement this in a future release. The most important part is that the
PaymentActionRequired
exception is thrown so developers can redirect customers to the payment page.