You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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 version column 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":
    1. Your backend attempts to mark the item as "pending purchase" (or secure the lock).
    2. If the lock succeeds, create the PaymentIntent with metadata to 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,
      )
      
    3. 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.succeeded webhook event.
  • In your webhook handler:
    1. Fetch the product using the metadata.product_id from the PaymentIntent.
    2. 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."

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 17:17:46