4fa7f76bc1
* 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>
26 lines
841 B
TOML
26 lines
841 B
TOML
name = "readest-iap-reconcile"
|
|
main = "src/index.ts"
|
|
compatibility_date = "2026-04-10"
|
|
compatibility_flags = ["nodejs_compat"]
|
|
workers_dev = false
|
|
|
|
# Hourly drift sweep. Catches subscription state changes that never arrived as a
|
|
# webhook (a row still marked active while its store expiry has passed). Invokes
|
|
# the worker's scheduled() handler directly — no public HTTP endpoint.
|
|
[triggers]
|
|
crons = ["7 * * * *"]
|
|
|
|
# Low volume; capture every run (the main worker stays head-sampled at 1%).
|
|
[observability]
|
|
enabled = true
|
|
head_sampling_rate = 1
|
|
|
|
# Same dataset the main worker writes webhook events to.
|
|
[[analytics_engine_datasets]]
|
|
binding = "IAP_WEBHOOK_AE"
|
|
dataset = "iap_webhooks"
|
|
|
|
# Required secrets (do not commit). Set from this directory with:
|
|
# wrangler secret put SUPABASE_URL
|
|
# wrangler secret put SUPABASE_SERVICE_ROLE_KEY
|