Demo data: three made-up repos and three people, recorded by the real Trail hooks.About TrailGitHub

Make payment webhook idempotent

Trail-Session: claude:68969d63-3984-4bbf-a591-a0175931b122 Trail-Link: http://localhost:8787/repos/payments-api/session/68969d63-3984-4bbf-a591-a0175931b122
Mmaya committed 9 days ago · maincommit fa76cc9d0eece4509d8d23659223290bc888f5d7
Agent sessions behind this commit1
Showing 2 changed files with 16 additions and 2 deletions.
app/webhooks.py+8 −2
@@ -3,7 +3,13 @@ from app.stripe import capture
def handle_payment_succeeded(event):
- order = db.orders.get(event["data"]["order_id"])
- capture(order.payment_intent)
+ # Stripe retries any delivery that takes over 30s; the same event can arrive twice.
+ with db.transaction():
+ if not db.processed_events.add(event["id"]):
+ return # already handled
+ order = db.orders.get(event["data"]["order_id"], for_update=True)
+ if order.status == "paid":
+ return
+ capture(order.payment_intent)
order.status = "paid"
db.orders.save(order)
tests/test_webhooks.py+8 −0
@@ -0,0 +1,8 @@
+from app.webhooks import handle_payment_succeeded
+
+
+def test_retry_does_not_capture_twice(fake_db, fake_stripe):
+ event = {"id": "evt_1", "data": {"order_id": 1}}
+ handle_payment_succeeded(event)
+ handle_payment_succeeded(event)
+ assert fake_stripe.captures == 1