* feat(payment): observability for store subscription webhooks
Add monitoring for the App Store / Google Play webhooks so store-side
subscription changes are observable on Cloudflare, where stored Workers
Logs are head-sampled at 1% and would miss almost all low-volume webhook
events.
- Add iap/telemetry.ts: every webhook invocation emits a structured log
line (streamed in full by `wrangler tail`) and a Cloudflare Analytics
Engine data point (100% capture, independent of log sampling). Writes
no-op off the Worker runtime, mirroring the getCloudflareContext guard
in deepl/translate.ts.
- Instrument both webhook routes to record outcome (handled, skipped,
rejected, error), notification type, status, reason, and latency on
every return path.
- Add GET /api/cron/iap-reconcile: a CRON_SECRET-protected sweep that
counts drift (rows still active while their store expiry has passed = a
missed webhook) in both IAP tables and records a reconcile metric.
Detection-only; never mutates state.
- Add the IAP_WEBHOOK_AE Analytics Engine binding to wrangler.toml.
New configuration: CRON_SECRET (reconcile auth) and an iap_webhooks
Analytics Engine dataset bound as IAP_WEBHOOK_AE. The reconcile route is
triggered on a schedule (a Cloudflare Cron Trigger worker that fetches
the URL, or any external scheduler) with an Authorization bearer header.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(payment): move reconciliation to a dedicated cron Worker
Replace the public CRON_SECRET-protected /api/cron/iap-reconcile route
with a dedicated Cloudflare Cron Worker. A Cron Trigger invokes the
worker's scheduled() handler directly, so there is no public HTTP surface
and no shared request secret to manage - the strongest option on
Cloudflare (OpenNext's generated worker only exports `fetch`, so the main
worker cannot host a scheduled() handler).
- Add workers/iap-reconcile: a self-contained worker (own package.json,
tsconfig, wrangler.toml) matching the existing workers/send-email
convention, registered in pnpm-workspace.yaml. Hourly Cron Trigger;
reads the IAP tables via the Supabase service role and records a drift
metric to the shared iap_webhooks Analytics Engine dataset.
- Reconcile logic lives in workers/iap-reconcile/src/reconcile.ts and is
unit-tested from the app suite.
- Remove the public route and its test; drop the now-unused
recordIapReconcile from iap/telemetry.ts (webhook telemetry unchanged).
Configuration: set SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY as secrets
on the worker and deploy it with `wrangler deploy` from its directory.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>