I'm implementing subscription with Stripe Checkout, but I have some questions that I couldn't get definitive answers for even from their support.
I have following scenario:
User clicks on a Subscribe button
User gets redirected to Stripe Checkout page (along with session token)
User successfully pays (and from now on is a Customer)
and here I get 14 webhook calls (each one is different event type):
checkout.session.completed
payment_method.attached
invoice.created
customer.updated
customer.subscription.created
customer.created (even though I used the same email - test mode, but still...)
invoice.finalized
invoice.updated
invoice.payment_succeeded
customer.subscription.updated
charge.succeeded
payment_intent.succeeded
payment_intent.created
invoice.updated
edit: I get all of them at once because I use Stripe CLI's listen feature and it probably shows all calls which normally wouldn't happen because they have to be defined in dashboard event by event. For instance if I create invoice in the dashboard and pay it then if no webhook is defined explicitly then my server won't know about anything
Until here it all works good. My concern is that:
if a Customer will be charged next month which webhook should I listen to? I need to differentiate between first-time Customer (because I create new account for that user) and existing Customer that I just need to note in database that this user is still active (or listen for event of subscription cancellation? subscription_schedule.canceled maybe?). One idea is to just listen to successful payment and upon that check if customer exists in database - if he does just update, if not then create account.
when I do another subscription payment using the very same email I get exactly the same webhooks (including customer.created which I think shouldn't be there) or am I missing something?
is there any possibility to double-charge a Customer by accident like I read in other Stripe implementations (see: idempotency)?
what are other things to take into account while implementing Stripe Checkout? I feel like their instructions are not reassuring that I've done everything that is considered good practice.
Flow that I want to achieve:
User successful payment (as above in points from 1 - 3). Already done!
Register new customer in database (Firebase in this case) and log him in automatically so after payment he has instant access.
As long as customer's card is charged he has access. While he cancels or card has no insufficient funds I need to have my server notified about this to downgrade access.
Related
for my subscription based product I want to have a possibility to subscribe and enter payment details at once with stripe and struggle with that with the api.
In https://stripe.com/docs/billing/subscriptions/build-subscriptions?ui=elements I see an option to create a subscription with payment_behavior='default incomplete' and then enter the details to confirm the payment intent. So far so good. However if I create the subscription like that even before the customer confirms payment details stripe already generates an invoice which is not really what I want before final confirmation by customer.
Options I see:
create setupintent, have this filled by customer via elements and then have the customer subscribe. Technically works nicely but for a sales and customer perspective is not good as it has two steps thus probably reduces conversion.
create the subscription in the background before final subscription confirmation by customer and use the clientsecret of it to pass back to browser and then have customer enter his payment data and submit that and finish the setup of subscription and payment info. Technically works - however I realize that when I create the subscription to get the clientsecret to pass to elements before the customer enters his payment data and confirms the subscription the subscription is not only created but an invoice too - which would be really, i.e. an invoice created before customers really confirms the contract
create setupintent and submit it via elements and in metadata of it add the info of product that customers wants so that when the paymentmethod gets created and I get webhook event I do the booking of the product given in metainfo. May however mean the customer gets to success page but the webhook has not notified yet and thus the customer is not really subscrubed at the point in time but gets a success message he is
same as 3 except do not pass info via metadata but via successUrl parameters which refers to and endpoint at my backend which upon being called after setupintent was setup will do the subscription and then redirect to my frontend which shows success page. That seems like a error prone workaround however.
Create a workflow which is a 2 step sign up and asks for paymentinfo, sets that up and then brings customer to a final confirmation page where the submit triggers subscription creation. Seems a bit complicated from a user flow, but so far probably the best option?
Any better options?
Cheers
Tom
ps: Interestingly enough on discord stripe support told me #2 is the way to go - find it hard to believe ...
As far as I know, there isn't a workaround for this unfortunately. It is just how Subscriptions API is designed by Stripe. You can learn more about that here where they talk about "how subscriptions work".
We are currently using Stripe to offer a subscription service with 30 days free trial. Since we don't want the customer to be able to start the free trial without authorizing one payment method we use the SetupIntent of the created Subscription to present a card input to our client. Now the issue is that even before the customer is shown the card input the subscription is already created and "paid" for because it is a free trial.
This means that we cannot listen to the initial invoice.paid Webhook to activate the account, but instead need to listen to setup_intent.succeeded. This seems a bit odd and requires us to link the SetupIntent to a Subscription. It also means that when a customer cancels the subscription process before entering their card details, that Stripe still has created an active Subscription in trial.
Is there something we can do differently, or should we just accept that the subscriptions where the SetupIntent was aborted will be inactive on the Stripe side once it tries to pay for the next (non-trial) invoices?
Stripe's docs use the Setup Intent that's created with a trialing Subscription to collect a customer's Payment Method, but for your use case it may make more sense to create your own Setup Intent up front and not create the Subscription unless the Setup Intent it successful. It'd go something like this:
Create the Setup Intent
Confirm the Setup Intent after collecting details
If successful, create the trialing Subscription
Alternatively, you could try using Checkout which does require users to submit a Payment Method even from trialing Subscriptions
We are working on a service that can start a subscription later in the future: users say today they want the service, but it actually starts some days later.
We are now collecting the payment method through a SetupIntent, which allows the user to verify they own card, but it actually doesn't verify the credit availability. When we collected the payment method, we create a scheduled subscription with the verified payment method; then, when the subscription starts, Stripe uses that payment method to collect money.
It happens, sometimes, that users do not have enough credit to pay for the service when the subscription starts. Otherwise, it also happens that, when Stripe tries to get money, the customer's bank requires 3D-secure verification.
Since our subscriptions start at midnight, we would like to avoid having to involve users again in the payment process.
So, we thought: would it be possible to immediately collect the payment method through an hold on a PaymentIntent and confirm that hold only when the subscription starts? I can't find a way to do this with Stripe (don't know if it exists). It seems impossible, with Stripe, to generate a PaymentIntent (with capture_method set to manual) for a scheduled subscription.
Do you have some ideas on how we can avoid payment problems when the subscription starts?
Otherwise, it also happens that, when Stripe tries to get money, the
customer's bank requires 3D-secure verification.
This shouldn't be the case if you complete any required 3DS authentication as a part of the SetupIntent confirmation flow. Call confirmCardSetup whilst the user is present and that way the payment method is successfully verified and can be used to process off-session payments for your subscription as you need.
You can use Stripe to place a hold on a card, but this generally doesn't apply to the use case you've described.
I found a workaround for this by first creating a paymentIntent with setup_future_usage="off_session" and capture_method="manual" to first place a hold and save the paymentMethod, and then, only after capturing this paymentIntent, creating a subscription using the newly saved paymentMethod with billing_cycle_anchor that equals your subscription's interval from now.
This way it's like your customer has paid for the first interval using the paymentIntent, but will be charged from the second interval using the subscriptions API, which allows you to cancel the hold on the first payment and not create a subscription if something goes wrong.
Consider the following course of events:
A user selects one of multiple subscription options on my website and clicks the "pay" button.
They're redirected to the Stripe Checkout page but don't complete the payment yet.
They somehow manage to get back to the page where they select the subscription while keeping the Stripe Checkout page open. (I know this is somewhat contrived but technically possible.)
They choose a different subscription option and click on "pay" again.
A second checkout session is created and another Stripe Checkout page opens.
Now they complete payments on both checkout pages.
How can I prevent this? Is there a way to cancel a checkout session? When I create a checkout session for a subscription, I don't receive a payment intent that I could cancel. There also doesn't seem to be a way to cancel checkout sessions directly.
I don't know of a way to prevent the scenario you described as the customer in question is explicitly deciding to pay you twice for two different subscriptions.
That said, if your use case requires a customer to have only a single Subscription you could add logic on your end that would do the following:
Set up a webhook endpoint to listen for customer.subscription.created events.
Whenever a new Subscription is created list the Subscriptions belonging to the Customer.
If the Customer has more than one active Subscription cancel the newest one(s) and refund the associated payments. You may also want to send the customer an email letting them know what happened.
Our application has different plans (Basic, Advanced, Pro). We use Stripe.
Each Plan gives you X amount of credits per month.
We credit each
Customer with the credits when he has made a successful payment
either by subscription creation, subscription update, or subscription
cycle.
The Customer is only credited on successful payment; we listen for the invoice.payment_succeeded webhook on our server and credit the user in our database.
The problem we face is this:
Customer attempts to use some credits. We detect that he has no
credits nor a Plan.
Customer is prompted to sign up for plan.
Customer provides payment details.
Customer authorises 3DS on the client via stripe.confirmCardPayment(). He's informed that payment went through.
Customer attempts to use his credits. At this point the webhook invoice.payment_succeeded
event did not arrive on our server to update our internal Customer and credit him his
credits (or mark his subscription as active). So the user is still blocked from using our application. He's notified that he
doesn't have any credits.
We eventually receive the webhook that payment has gone through and we update the
customer subscription and credit his credits.
As you can see step 5 is problematic. The customer has paid however when he tries to use his credits he is informed he doesn't have any because the webhook that will credit his credits has not arrived yet on our server.
The webhook will arrive within the next minute or so but in the meantime the Customer is baffled that he paid for credits yet when trying to use them he's informed he doesn't have any.
How would you handle this?
Possible solutions
Client sends HTTP call to credit his account
When stripe.confirmCardPayment() succeeds, send an HTTP call from client -> server to credit the user.
This solution is both insecure and error prone.
It shifts the responsibility of crediting the user account to the client. Nothing would stop the client from sending forged HTTP calls to credit his own account.
If the user closes down his browser immediately after stripe.confirmCardPayment() succeeds, the call to credit his account would never reach our servers.
Client polls server to query successful completion
Every invoice.payment_succeeded event gets processed on our server and saved in a table successful_invoices.
When stripe.confirmCardPayment() succeeds we poll our server to find out
if a successful invoice with the invoice_id exists in that table.
If it does, we hide the loading screen and tell the client that his payment was processed and his credits have been credited successfully.
This is our preferred solution for the time being.
This is something we have faced already in various payment gateway integrations. You have to wait for the webhook to confirm the payment.
Then there needs to be a backup event that requests the gateway to confirm if the payment status is successful and so you can update the same in your records.
Normally, the webhooks do the needful else your payment_create event from your app can request that provider to check the status. But there is a mild chance of this not getting update unless you put a delay to check this post payment_create.
We tried adding a CRON that checks the status of the payment made in last 1 min. All payment having pending status from last run are picked status is updated from gateway provider. BUt in this case you need to be sure that no record should have a duplicate run in form of event from payment create and added to cron as well at same time.