I am making an a simple app which allows users to post ads to the website for a fee, as in pay per post. No account is required, it is in essence a guest checkout by default.
The issue I am dealing with is the order and best way to implement the business logic on the server side.
My flow is as follows:
- (Front end) User fills out a form with ad details
- (Front end) User hits submit
- (Back end) Post request is processed, new database entry is created
- (Back end) User is redirected to Stripe checkout
- Payment fails or succeeds
Notice how in this flow I create an object/entry in my database before any payment has been made. This already feels wrong because I feel the entry shouldn't exist unless it has been paid for. Because spammer could in theory fill my database with endless entries by filling the form and not completing payment. So if Stripe payment fails in step 5, I have a choice: delete the entry, or return the user to the form prefilled. Clearly, the later is the better option, however if the user never finishes or fixes the payment I will still have a dangling entry in my database. What is the best way to approach this situation? And what should I do with the entry if I want to implement a pay later option (via sending a payment url to an email)?
Also, if payment succeeds everything operates as normal. However, I want to use webhooks as it seems more reliable for payment status. But Stripe checkout also uses a "success_url". So in the case that the checkout succeeds, but the payment is rejected. I will still forward the user to a success_url, which seems strange as user might get the impression that everything went smoothly. So, how would that case be handled?
I feel like a lot of the trouble stems from Stripe checkout making me leave my server environment, whereby I lack control of how to change my database for each case should it arise.
One option for the first part might be to not create the database records until after the payment. For example, you could effectively store some state in the
metadataof the CheckoutSession you create, as to what the customer picked and the information you will need in order to create the record. Then when you handle thecheckout.session.completedwebhook event for the customer having paid, you can inspect what the customer bought(https://dev.to/stripe/purchase-fulfilment-with-checkout-or-wait-what-was-i-paid-for-335d) and the metadata you previously set, and create the things in your database.https://stripe.com/docs/api/checkout/sessions/create#create_checkout_session-metadata
I'm not sure what you're referring to here as that doesn't really happen. On the Checkout page if the customer's payment fails like their card is declined, they just get an error message on the page and can try again multiple times and change their details and so on, they don't just fail and get redirected away. You can see how it works by using test mode and test cards. https://stripe.com/docs/testing#declined-payments
If you recieve the
checkout.session.completedwebhook event then the payment was successful. If thesuccess_urlis visited, you can have your backend retrieve the CheckoutSession ID and check if it has been paid(since anyone could visit that URL directly) but in general the redirect to the URL only happens once the customer has paid(and Stripe waits up to 10 seconds for you to recieve and acknowledge thecheckout.session.completedwebhook event before it redirects).https://stripe.com/docs/payments/checkout/custom-success-page