Skip to content

Commit b631e25

Browse files
feat(jobs): checkout reconciler — email customers whose Razorpay checkout never completed
Adds CheckoutReconcileWorker, a periodic sweep (every 5min, batch 200) over the pending_checkouts table (api migration 034). It catches the webhook-blind failure: a Razorpay checkout that failed on the hosted page WITHOUT a payment object — Razorpay emits zero webhooks, so the api never learns of it and the customer gets no email. This sweep is the only mechanism that closes that gap. Each tick selects rows with resolved_at IS NULL AND failure_notified_at IS NULL AND created_at < now() - 15min, then per row: - best-effort Razorpay double-check via the existing subscriptionFetcher (reused from the billing reconciler); if the subscription reports active/authenticated or paid_count > 0 the webhook was merely delayed — stamp resolved_at, skip the email. Skipped entirely when no RAZORPAY_KEY_ID is wired (noop fetcher) — the 15-min heuristic stands. - otherwise write a checkout.abandoned audit_log row and stamp failure_notified_at in one FOR UPDATE SKIP LOCKED transaction so each customer is emailed at most once, even under replicas:2. Email follows the established pipeline: the audit row is the trigger, the EventEmailForwarder drains it. New checkout.abandoned email kind is Go-rendered (renderCheckoutAbandoned) and registered in all three registries — supportedAuditKinds, eventEmailBuilders, eventEmailBodyRenderers — so the registry-iterating coverage test catches a half-registration (CLAUDE rules 16/18/70). Registry-iterating + behaviour tests in checkout_reconcile_test.go cover the tunables, the three-registry wiring, builder/renderer output, and the Razorpay double-check decision table. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 1451253 commit b631e25

5 files changed

Lines changed: 696 additions & 0 deletions

File tree

0 commit comments

Comments
 (0)