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>
50 lines
1.1 KiB
TOML
50 lines
1.1 KiB
TOML
name = "readest-web"
|
|
main = ".open-next/worker.js"
|
|
compatibility_date = "2026-04-10"
|
|
compatibility_flags = ["nodejs_compat"]
|
|
workers_dev = true
|
|
preview_urls = true
|
|
|
|
[[routes]]
|
|
pattern = "web-cf.readest.com"
|
|
zone_name = "readest.com"
|
|
custom_domain = true
|
|
|
|
[[routes]]
|
|
pattern = "api-cf.readest.com"
|
|
zone_name = "readest.com"
|
|
custom_domain = true
|
|
|
|
[[routes]]
|
|
pattern = "web.readest.com"
|
|
zone_name = "readest.com"
|
|
custom_domain = true
|
|
|
|
[vars]
|
|
PNPM_VERSION = "10.28.1"
|
|
|
|
[observability]
|
|
enabled = true
|
|
head_sampling_rate = 0.01
|
|
logs = { head_sampling_rate = 0.01 }
|
|
traces = { head_sampling_rate = 0.01 }
|
|
|
|
[assets]
|
|
directory = ".open-next/assets"
|
|
binding = "ASSETS"
|
|
|
|
[[kv_namespaces]]
|
|
binding = "TRANSLATIONS_KV"
|
|
id = "fa6e3eb1988a424bbf3c952e01650ced"
|
|
|
|
[[r2_buckets]]
|
|
binding = "NEXT_INC_CACHE_R2_BUCKET"
|
|
bucket_name = "readest-next-inc-cache"
|
|
|
|
# Per-event metrics for the App Store / Google Play subscription webhooks and the
|
|
# reconciliation sweep. 100% capture, independent of the head-sampled Workers
|
|
# Logs above. Queryable via the Analytics Engine SQL API.
|
|
[[analytics_engine_datasets]]
|
|
binding = "IAP_WEBHOOK_AE"
|
|
dataset = "iap_webhooks"
|