-
Notifications
You must be signed in to change notification settings - Fork 680
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
Payment Element integration documentation #1541
Comments
From my understanding of the Stripe documentation, they now recommend using the Stripe UI Payment Element with Setup Intents for subscriptions or Payment Intents for single charges. Setup Intents should be confirmed using Stripe JS's A Meanwhile, new webhooks need to be configured:
Other webhooks should also be set up for error handling ( Of course there are many ways to approach this. We could also fulfill the order listening to the What do you think? |
I have this all set up and working with the payment element for both subscriptions and one off payments. I had to do a few workarounds as cashier doesn't seem to have all the required functionality to handle failed payments due to 3DS, but I am creating PRs to add this functionality The payment element actually checks for validity of card before redirecting (insufficient funds, lost/stolen etc.), so certain errors are caught before it hits your server side code. 3DS also happens here. When the payment element redirects you if it is a setup_intent it means the card was registered and if it's payment_intent it means the payment was successful. Therefore you don't need to listen for the webhook events and have a pending page and you have the payment/setup intent id in the url params which you can use to retrieve the intent from the Stripe API If using setup_intent my code creates the payment method, sets it as default, then uses it to create a subscription "off_session". If this fails due to 3DS then it redirects to a form to confirm the payment Mostly the same for payment intents and single charges charging the default payment method, except it just creates an order in our db or redirects if action is required I'm happy to share examples |
Thanks all. I'll dig into this once I have time for it. |
@quantumwebco thanks for sharing. That's a good set up. I might move some of the logic out of webhooks to simplify the handling of failed payments. I followed parts of this Stripe guide to set up my form: https://stripe.com/docs/payments/accept-a-payment-deferred There's a 3rd use case I forgot to mention: updating the default payment method for subscriptions. |
To add a new payment method, in the backend I call That is also the way I handle subscriptions. It runs through that flow first and then creates a subscription like It's the same for single payments but instead of createSetupIntent you use I will put some code in to gists which shows the flows I am using (no webhooks, direct to API) and share them here |
I think these docs will be better for you https://stripe.com/docs/payments/quickstart you can basically follow that but replace the backend stuff with the cashier methods. It's the pretty much the same for setup intent as it is for payment intent. You can also use a payment intent and set an option to save the card for future use |
@quantumwebco Is this still working for you? I'm trying to do the same thing and keep getting a |
Yep, all still working fine in my apps. Are you adding it via a setup intent or a payment intent? If a payment intent make sure you are setting the off_session parameter correctly. If you dump out the exception what are the error codes? |
@quantumwebco Thanks for the reply; I'm issuing a setup intent |
Ah I see, I have replied on Laracasts. You need to get the card server side rather than client side. The redirect happens before the |
Just chipping in here. I'm using a Nuxt JS front-end here. I've tried switching card element to async mounted () {
await this.card()
if (this.$stripe) {
const clientSecret = this.subscription?.intent?.client_secret ?? ''
const elements = this.$stripe.elements({ clientSecret })
this.subscription.elements = elements.create('payment', {
layout: {
type: 'accordion',
defaultCollapsed: true,
radios: true
}
})
this.subscription.elements.mount('#card-element')
}
}, Can I just confirm, would sending the payment type back to the server via const clientSecret = this.subscription.intent.client_secret ?? ''
const { setupIntent, error } = await this.$stripe.confirmCardSetup(
clientSecret, {
payment_method: {
card: this.subscription.elements,
billing_details: {
name: this.billing.name
}
}
}
) And in Cashier in my backend I'm implementing the following: try {
$user = User::find(Auth::id());
$user->newSubscription('default', $plan->stripe_id)->withCoupon($coupon)->create($request->input('token'));
} catch (IncompletePayment $e) {
if ($e->payment->requiresAction()) {
return new ApiSuccessResponse([
'requiresAction' => true,
'redirectTo' => route('cashier.payment', [
$e->payment->id,
'redirect' => $frontendURL.'account/subscriptions/',
]),
], [
'message' => "We require some payment action from you, this usually means you need to open your banking app to approve this subscription.",
], 400);
} else if ($e->payment->requiresPaymentMethod()) {
return new ApiSuccessResponse([
'requiresAction' => true,
'redirectTo' => route('cashier.payment', [
$e->payment->id,
'redirect' => $frontendURL.'account/subscriptions/',
]),
], [
'message' => "The payment method currently set up is invalid or has sufficient funds, we're going to redirect you to complete this transaction, please wait.",
], 400);
} else if ($e->payment->requiresConfirmation()) {
return new ApiSuccessResponse([
'requiresAction' => true,
'redirectTo' => route('cashier.payment', [
$e->payment->id,
'redirect' => $frontendURL.'account/subscriptions/',
]),
], [
'message' => "We jsut need you to confirm this payment.",
], 400);
} else {
return new ApiSuccessResponse([
'requiresConfirmation' => true,
], [
'message' => "We require you to confirm this payment."
], 400);
}
}
return new ApiSuccessResponse([
'complete' => true
], [
'message' => "You've successfully upgraded your account.",
], 201); |
This looks like code for the older style integration so you might need to refactor a fair bit. Hopefully this will help you https://laracasts.com/discuss/channels/javascript/stripe-payment-element-error#reply-899320 |
The Cashier Stripe documentation is only explaining how to use the Stripe UI Card Element, which is now marked as "Legacy". While the new Payment Element is not documented.
It seems like the JS code should now call
await stripe.confirmPayment
instead ofawait stripe.confirmCardSetup
. But it requires defining areturn_url
. Apayment_intent_client_secret
value is then passed as GET query string parameter (not ideal), which I guess can be used with Cashier.I think the documentation should explain how to set this up properly. Or is this not supported officially?
Thanks!
The text was updated successfully, but these errors were encountered: