A payment screen can look perfect and still create a serious support problem. A customer double-clicks. The browser refreshes. A payment message arrives twice. Your app charges again or delivers the same order twice.
Safe payments need more than a button. Your app must create one payment for one order, confirm the result with the payment provider, and make repeated messages harmless.
What actually happens after a customer clicks Pay?
- Your app creates an order with a unique order number.
- The protected part of the app asks the payment provider to create a payment.
- The customer completes the provider's checkout flow.
- The provider sends a signed payment message to your app.
- Your app marks the order paid and delivers it once.
- The customer sees a receipt or clear next step.
The page the customer sees after checkout is useful, but it is not final proof. The customer may close the tab or lose their connection after paying.
Why should the payment start behind the app?
The browser is controlled by the customer. It must not decide the final price, mark an order paid, or hold the payment provider's private key.
Create the order and payment in the app's protected part. Look up the product and amount there. Keep the provider key in the hosting platform's protected settings. The guide Where Should API Keys Go? explains this boundary.
How do you stop two payment requests?
Give every order one unique number. When your app asks the provider to create a payment, send a matching repeat-protection key. Payment APIs often call this an idempotency key. It means, “If this request arrives again, return the first result. Do not do the work twice.”
Stripe's idempotent request guide describes this behavior. The key should represent the action, such as creating payment for one order. A random new key on every retry cannot identify a repeat.
Disabling the button after one click improves the screen, but it is not enough. Network retries and two open tabs can still create repeated requests.
How does your app learn that payment succeeded?
A payment provider sends an event to a public address in your app. This event is often called a webhook. Think of it as a signed delivery notice from the provider.
Verify the signature before trusting the message. Then retrieve or check the payment with the provider and match it to your saved order. Stripe's Checkout fulfillment guide says not to rely only on the return page and recommends a webhook for reliable confirmation.
What if the same payment event arrives twice?
Repeated events are normal on the internet. A provider may retry because your app answered too slowly or its first reply was lost.
Save the provider's event number or the payment number you already processed. If it arrives again, return success without shipping, adding credits, or sending another receipt.
Stripe's webhook guide recommends recording processed event IDs and ignoring duplicates. Your database should also prevent the same provider payment number from belonging to two orders.
Which order states should you save?
| State | Meaning | Allowed next step |
|---|---|---|
| Pending | The order exists, but payment is not confirmed | Wait, retry checkout, or expire |
| Paid | The provider confirmed payment | Deliver once |
| Delivered | The product or access was provided | Show receipt or support |
| Failed | The payment did not complete | Show a safe retry |
| Refunded | Money was returned | Apply the refund policy |
Do not use one loose “success” value for every step. Payment and delivery can finish at different times.
What should you test before taking real money?
Use the provider's testing environment. Stripe's testing guide separates test objects from live payments and provides simulated success and failure cases.
- Complete one successful payment and confirm one order is delivered.
- Double-click Pay and confirm only one payment exists.
- Refresh during checkout and reopen the order.
- Close the browser after payment but before the return page loads.
- Send the same success event twice and confirm delivery happens once.
- Test a declined and a delayed payment.
- Issue a refund and confirm the order, access, and receipt match your policy.
Use clearly marked test accounts and products. Test and live keys must never be mixed.
What should you ask your coding AI?
Copy this request: Inspect the current payment flow before editing. Trace order creation, payment creation, the return page, webhook verification, delivery, and refunds. Show where a repeated click, request, or event could create a second charge or delivery. Propose one order ID, one repeat-protection key, saved payment and event IDs, and pass-or-fail tests. Do not change live payment settings or use real money.
Ask the AI to show evidence for each test. A code summary without a simulated repeated event is not enough.
When is the payment flow ready?
- The browser cannot choose the final amount or mark an order paid.
- One order creates at most one provider payment.
- Unsigned events are rejected.
- The same event can run twice without a second delivery.
- A paid customer can recover access after closing the browser.
- You can find the order, provider payment, event, and refund in one support trail.
What should you read next?
- Where Should API Keys Go? A Plain-English Guide
- How to Add Login Without Exposing Customer Data
- AI App Launch Checklist for Solo Founders
When the payment flow passes in testing, visit JustDeploy to publish the app and verify the live return and webhook addresses.