Files
readest/pnpm-workspace.yaml
T
Huang Xin 4fa7f76bc1 feat(payment): observability for store subscription webhooks (#4704)
* 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>
2026-06-21 18:02:02 +02:00

59 lines
1.4 KiB
YAML

packages:
- apps/*
- apps/readest-app/workers/send-email
- apps/readest-app/workers/iap-reconcile
- apps/readest-app/extensions/*
- packages/foliate-js
allowBuilds:
core-js: true
edgedriver: true
esbuild: true
geckodriver: true
protobufjs: true
sharp: true
workerd: true
onlyBuiltDependencies:
- sharp
patchedDependencies:
'@ai-sdk/provider-utils@3.0.25': patches/@ai-sdk__provider-utils@3.0.25.patch
'@ai-sdk/provider-utils@4.0.27': patches/@ai-sdk__provider-utils@4.0.27.patch
mdast-util-gfm-autolink-literal@2.0.1: patches/mdast-util-gfm-autolink-literal@2.0.1.patch
overrides:
glob: '>=11.1.0'
jws: '>=4.0.1'
vite: '>=7.3.5 <8'
esbuild: '>=0.28.1 <0.29'
srvx: '>=0.11.13'
rollup: '>=4.59.0'
undici: '>=7.28.0 <8'
flatted: '>=3.4.2'
body-parser: '>=2.2.1'
dompurify: '>=3.4.11'
i18next-http-backend: '>=3.0.5'
picomatch: '>=4.0.4'
path-to-regexp: '>=8.4.0'
protobufjs: '>=7.6.3 <8'
serialize-javascript: 7.0.5
fast-xml-parser: '>=5.7.0'
lodash: '>=4.18.0'
lodash-es: '>=4.18.0'
postcss: 8.5.14
basic-ftp: '>=5.3.0'
qs: '>=6.15.2'
shell-quote: '>=1.8.4'
form-data: '>=4.0.6'
js-yaml: '>=4.2.0'
'@opentelemetry/core': '>=2.8.0'
'@babel/runtime': '>=7.26.10'
'@babel/helpers': '>=7.26.10'
'@babel/core': '>=7.29.6'
mdast-util-gfm-autolink-literal: 2.0.1
uuid: '>=14.0.0'
ws: '>=8.21.0'
'@emnapi/core': 1.8.1
'@emnapi/runtime': 1.8.1