Demo data: three made-up repos and three people, recorded by the real Trail hooks.About TrailGitHub
38 lines · 1.3 KBRaw
app/webhooks.py | 10 ++++++++--
tests/test_webhooks.py | 8 ++++++++
2 files changed, 16 insertions(+), 2 deletions(-)
 
diff --git a/app/webhooks.py b/app/webhooks.py
index 09c59d0..94653ee 100644
--- a/app/webhooks.py
+++ b/app/webhooks.py
@@ -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)
diff --git a/tests/test_webhooks.py b/tests/test_webhooks.py
new file mode 100644
index 0000000..fe05a16
--- /dev/null
+++ b/tests/test_webhooks.py
@@ -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