Stripe支付前校验:并发购买唯一商品的PaymentIntent取消方案
Solution for Handling Concurrent Purchases of Unique Items with Stripe
1. Pre-Payment Inventory Locking (Prevent Most Concurrency Issues)
The best first line of defense is to lock the item before even creating a Stripe PaymentIntent, so you avoid users reaching the payment stage unnecessarily:
- Use a database transaction with optimistic locking (e.g., add a
versioncolumn to your product table, increment it when marking the item as sold) or pessimistic locking (lock the product row during the purchase request). - When a user clicks "Buy":
- Your backend attempts to mark the item as "pending purchase" (or secure the lock).
- If the lock succeeds, create the PaymentIntent with
metadatato tie it to your internal data:stripe.PaymentIntent.create( amount=1500, # Amount in cents currency="usd", metadata={"product_id": "prod_789", "user_id": "user_101112"}, # Use manual confirmation to control when payment is finalized confirmation_method="manual", confirm=False, ) - If the lock fails, immediately return a clear error to the frontend: "Sorry, this item was just purchased by another user!"
2. Handle Edge Cases with Webhooks (Catch Leaking Concurrent Payments)
Even with pre-payment locking, rare race conditions might let two users reach the payment stage. Here’s how to clean this up smoothly:
- Listen for the
payment_intent.succeededwebhook event. - In your webhook handler:
- Fetch the product using the
metadata.product_idfrom the PaymentIntent. - Check if the product is already marked as sold:
- If not sold: Mark it as sold, create your internal order record, and notify the user of their successful purchase.
- If already sold:
- Issue a full refund via Stripe’s API (since the payment already processed):
stripe.Refund.create( payment_intent=event.data.object.id, reason="duplicate" ) - For PaymentIntents that haven’t fully succeeded yet (e.g., still in
requires_confirmation), you can cancel it directly:stripe.PaymentIntent.cancel(event.data.object.id) - Notify the user immediately (via in-app alert, email, or push notification) with your custom message: "Oops! Someone else bought this item first. Your payment has been refunded to your account, and no charges will appear on your statement."
- Issue a full refund via Stripe’s API (since the payment already processed):
- Fetch the product using the
3. Frontend: Keep Users Updated in Real-Time
To make the error feel immediate rather than delayed, add frontend logic to:
- Poll your backend’s order status endpoint every 2-3 seconds after the user initiates payment.
- If the backend returns a "sold to another user" status, display your custom error message prominently (e.g., at the top of the payment page).
- Alternatively, use WebSockets to push the status update to the user’s browser as soon as your webhook processes the duplicate payment.
Key Notes
- Prioritize pre-payment locking: Reducing post-payment refunds is better for user trust and minimizes extra work for your team.
- Leverage metadata: Attach all necessary context (product ID, user ID, order ID) to PaymentIntents so your webhook can easily tie Stripe events to your internal data.
- Test concurrency: Use tools like Postman to simulate simultaneous purchase requests, or Stripe’s test mode to trigger duplicate payment events and verify your flow works as expected.
内容的提问来源于stack exchange,提问作者Acampoh
相关产品推荐
相关产品推荐

