Commit b631e25
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
0 commit comments