payment-kit 1.29.1 → 1.29.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/api/dev.ts +41 -2
- package/api/hono.d.ts +42 -0
- package/api/node-sqlite.d.ts +12 -0
- package/api/src/bootstrap.ts +47 -0
- package/api/src/crons/base.ts +3 -3
- package/api/src/crons/currency.ts +1 -1
- package/api/src/crons/index.ts +41 -37
- package/api/src/crons/metering-subscription-detection.ts +1 -1
- package/api/src/crons/overdue-detection.ts +2 -2
- package/api/src/crons/retry-pending-events.ts +6 -0
- package/api/src/crons/tenant-fanout.ts +82 -0
- package/api/src/host-node/did-connect-runtime-node.ts +33 -0
- package/api/src/host-node/serve-static-arc.ts +68 -0
- package/api/src/host-node/serve-static.ts +41 -0
- package/api/src/index.ts +22 -161
- package/api/src/integrations/app-store/client.ts +3 -4
- package/api/src/integrations/app-store/handlers/subscription.ts +7 -7
- package/api/src/integrations/app-store/signed-data-verifier.ts +3 -2
- package/api/src/integrations/arcblock/token.ts +21 -7
- package/api/src/integrations/google-play/handlers/subscription.ts +6 -6
- package/api/src/integrations/google-play/handlers/voided.ts +2 -2
- package/api/src/integrations/google-play/verify.ts +3 -2
- package/api/src/integrations/iap-reconcile.ts +3 -5
- package/api/src/integrations/stripe/handlers/invoice.ts +2 -2
- package/api/src/integrations/stripe/handlers/subscription.ts +3 -3
- package/api/src/libs/archive/query.ts +19 -0
- package/api/src/libs/audit.ts +61 -4
- package/api/src/libs/auth.ts +247 -47
- package/api/src/libs/context.ts +89 -1
- package/api/src/libs/currency.ts +2 -2
- package/api/src/libs/dayjs.ts +8 -2
- package/api/src/libs/did-connect/runtime-did-connect-js.ts +88 -0
- package/api/src/libs/did-connect/tenant-identity.ts +221 -0
- package/api/src/libs/drivers/auth-storage.ts +118 -0
- package/api/src/libs/drivers/cron.ts +264 -0
- package/api/src/libs/drivers/db.ts +170 -0
- package/api/src/libs/drivers/identity.ts +142 -0
- package/api/src/libs/drivers/index.ts +40 -0
- package/api/src/libs/drivers/locks.ts +226 -0
- package/api/src/libs/drivers/migrate-runner.ts +70 -0
- package/api/src/libs/drivers/queue.ts +104 -0
- package/api/src/libs/drivers/secrets.ts +194 -0
- package/api/src/libs/env.ts +170 -54
- package/api/src/libs/exchange-rate/service.ts +7 -6
- package/api/src/libs/http-fetch-adapter.ts +60 -0
- package/api/src/libs/invoice.ts +1 -1
- package/api/src/libs/lock.ts +51 -47
- package/api/src/libs/logger.ts +48 -8
- package/api/src/libs/notification/index.ts +1 -1
- package/api/src/libs/notification/template/customer-credit-low-balance.ts +2 -1
- package/api/src/libs/notification/template/customer-revenue-succeeded.ts +1 -1
- package/api/src/libs/notification/template/customer-reward-succeeded.ts +1 -1
- package/api/src/libs/overdraft-protection.ts +1 -1
- package/api/src/libs/payout.ts +1 -1
- package/api/src/libs/queue/index.ts +271 -52
- package/api/src/libs/queue/runtime.ts +175 -0
- package/api/src/libs/resource.ts +3 -3
- package/api/src/libs/secrets.ts +38 -0
- package/api/src/libs/session.ts +3 -2
- package/api/src/libs/subscription.ts +5 -5
- package/api/src/libs/tenant.ts +92 -0
- package/api/src/libs/url.ts +3 -3
- package/api/src/libs/util.ts +21 -13
- package/api/src/middlewares/hono/cdn.ts +63 -0
- package/api/src/middlewares/hono/context.ts +80 -0
- package/api/src/middlewares/hono/csrf.ts +83 -0
- package/api/src/middlewares/hono/fallback.ts +194 -0
- package/api/src/middlewares/hono/pipeline.ts +73 -0
- package/api/src/middlewares/hono/resource-mount.ts +42 -0
- package/api/src/middlewares/hono/resource.ts +63 -0
- package/api/src/middlewares/hono/security.ts +209 -0
- package/api/src/middlewares/hono/session.ts +114 -0
- package/api/src/middlewares/hono/xss.ts +61 -0
- package/api/src/queues/auto-recharge.ts +12 -10
- package/api/src/queues/checkout-session.ts +38 -21
- package/api/src/queues/credit-consume.ts +40 -36
- package/api/src/queues/credit-grant.ts +25 -18
- package/api/src/queues/credit-reconciliation.ts +7 -5
- package/api/src/queues/discount-status.ts +9 -6
- package/api/src/queues/event.ts +41 -11
- package/api/src/queues/exchange-rate-health.ts +49 -30
- package/api/src/queues/invoice.ts +18 -15
- package/api/src/queues/notification.ts +14 -7
- package/api/src/queues/payment.ts +64 -37
- package/api/src/queues/payout.ts +37 -21
- package/api/src/queues/refund.ts +36 -18
- package/api/src/queues/subscription.ts +83 -53
- package/api/src/queues/token-transfer.ts +15 -10
- package/api/src/queues/usage-record.ts +8 -5
- package/api/src/queues/vendors/commission.ts +7 -5
- package/api/src/queues/vendors/fulfillment-coordinator.ts +17 -13
- package/api/src/queues/vendors/fulfillment.ts +4 -2
- package/api/src/queues/vendors/return-processor.ts +5 -3
- package/api/src/queues/vendors/return-scanner.ts +5 -4
- package/api/src/queues/vendors/status-check.ts +10 -7
- package/api/src/queues/webhook.ts +60 -32
- package/api/src/routes/connect/shared.ts +1 -2
- package/api/src/routes/connect/subscribe.ts +3 -3
- package/api/src/routes/{archive.ts → hono/archive.ts} +69 -64
- package/api/src/routes/{auto-recharge-configs.ts → hono/auto-recharge-configs.ts} +39 -28
- package/api/src/routes/{checkout-sessions.ts → hono/checkout-sessions.ts} +790 -923
- package/api/src/routes/{coupons.ts → hono/coupons.ts} +93 -76
- package/api/src/routes/{credit-grants.ts → hono/credit-grants.ts} +140 -126
- package/api/src/routes/hono/credit-tokens.ts +43 -0
- package/api/src/routes/{credit-transactions.ts → hono/credit-transactions.ts} +37 -29
- package/api/src/routes/{customers.ts → hono/customers.ts} +199 -224
- package/api/src/routes/{donations.ts → hono/donations.ts} +41 -32
- package/api/src/routes/{entitlements.ts → hono/entitlements.ts} +28 -25
- package/api/src/routes/{events.ts → hono/events.ts} +107 -71
- package/api/src/routes/{exchange-rate-providers.ts → hono/exchange-rate-providers.ts} +138 -126
- package/api/src/routes/hono/exchange-rates.ts +77 -0
- package/api/src/routes/hono/index.ts +115 -0
- package/api/src/routes/{integrations → hono/integrations}/app-store.ts +68 -48
- package/api/src/routes/{integrations → hono/integrations}/google-play.ts +78 -58
- package/api/src/routes/hono/integrations/stripe.ts +74 -0
- package/api/src/routes/{invoices.ts → hono/invoices.ts} +253 -244
- package/api/src/routes/{meter-events.ts → hono/meter-events.ts} +120 -110
- package/api/src/routes/hono/meters.ts +288 -0
- package/api/src/routes/hono/passports.ts +73 -0
- package/api/src/routes/{payment-currencies.ts → hono/payment-currencies.ts} +219 -197
- package/api/src/routes/{payment-intents.ts → hono/payment-intents.ts} +136 -132
- package/api/src/routes/{payment-links.ts → hono/payment-links.ts} +145 -128
- package/api/src/routes/{payment-methods.ts → hono/payment-methods.ts} +125 -93
- package/api/src/routes/{payment-stats.ts → hono/payment-stats.ts} +30 -25
- package/api/src/routes/{payouts.ts → hono/payouts.ts} +55 -47
- package/api/src/routes/{prices.ts → hono/prices.ts} +265 -242
- package/api/src/routes/{pricing-table.ts → hono/pricing-table.ts} +94 -87
- package/api/src/routes/{products.ts → hono/products.ts} +172 -159
- package/api/src/routes/{promotion-codes.ts → hono/promotion-codes.ts} +207 -185
- package/api/src/routes/hono/redirect.ts +24 -0
- package/api/src/routes/{refunds.ts → hono/refunds.ts} +98 -83
- package/api/src/routes/{settings.ts → hono/settings.ts} +64 -55
- package/api/src/routes/{subscription-items.ts → hono/subscription-items.ts} +64 -57
- package/api/src/routes/{subscriptions.ts → hono/subscriptions.ts} +475 -528
- package/api/src/routes/{tax-rates.ts → hono/tax-rates.ts} +71 -70
- package/api/src/routes/hono/tool.ts +69 -0
- package/api/src/routes/{usage-records.ts → hono/usage-records.ts} +47 -42
- package/api/src/routes/{vendor.ts → hono/vendor.ts} +315 -167
- package/api/src/routes/{webhook-attempts.ts → hono/webhook-attempts.ts} +17 -13
- package/api/src/routes/hono/webhook-endpoints.ts +126 -0
- package/api/src/service.ts +814 -0
- package/api/src/store/migrations/20230911-seeding.ts +2 -1
- package/api/src/store/migrations/20260609-remove-did-space-jobs.ts +23 -0
- package/api/src/store/migrations/20260610-tenant-columns.ts +40 -0
- package/api/src/store/migrations/20260611-tenant-backfill.ts +33 -0
- package/api/src/store/models/auto-recharge-config.ts +22 -10
- package/api/src/store/models/checkout-session.ts +15 -14
- package/api/src/store/models/coupon.ts +29 -20
- package/api/src/store/models/credit-grant.ts +38 -29
- package/api/src/store/models/credit-transaction.ts +32 -21
- package/api/src/store/models/customer.ts +19 -17
- package/api/src/store/models/discount.ts +11 -2
- package/api/src/store/models/entitlement-grant.ts +21 -9
- package/api/src/store/models/entitlement-product.ts +21 -9
- package/api/src/store/models/entitlement.ts +19 -10
- package/api/src/store/models/event.ts +18 -9
- package/api/src/store/models/exchange-rate-provider.ts +17 -4
- package/api/src/store/models/invoice-item.ts +18 -9
- package/api/src/store/models/invoice.ts +16 -8
- package/api/src/store/models/meter-event.ts +27 -9
- package/api/src/store/models/meter.ts +31 -22
- package/api/src/store/models/payment-currency.ts +25 -8
- package/api/src/store/models/payment-intent.ts +15 -6
- package/api/src/store/models/payment-link.ts +15 -6
- package/api/src/store/models/payment-method.ts +38 -22
- package/api/src/store/models/payment-stat.ts +18 -9
- package/api/src/store/models/payout.ts +15 -6
- package/api/src/store/models/price-quote.ts +17 -8
- package/api/src/store/models/price.ts +24 -12
- package/api/src/store/models/pricing-table.ts +29 -20
- package/api/src/store/models/product-vendor.ts +20 -10
- package/api/src/store/models/product.ts +15 -6
- package/api/src/store/models/promotion-code.ts +14 -6
- package/api/src/store/models/refund.ts +15 -6
- package/api/src/store/models/revenue-snapshot.ts +21 -9
- package/api/src/store/models/setting.ts +18 -9
- package/api/src/store/models/setup-intent.ts +36 -27
- package/api/src/store/models/subscription-item.ts +21 -9
- package/api/src/store/models/subscription-schedule.ts +21 -9
- package/api/src/store/models/subscription.ts +21 -10
- package/api/src/store/models/tax-rate.ts +29 -21
- package/api/src/store/models/usage-record.ts +11 -2
- package/api/src/store/models/webhook-attempt.ts +18 -9
- package/api/src/store/models/webhook-endpoint.ts +18 -9
- package/api/src/store/scoped-core.ts +55 -0
- package/api/src/store/scoped.ts +247 -0
- package/api/src/store/sequelize.ts +82 -23
- package/api/src/store/sql-migrations.ts +20 -0
- package/api/src/store/tenant-backfill.ts +260 -0
- package/api/src/store/tenant-model.ts +124 -0
- package/api/src/store/tenant-tables.ts +50 -0
- package/api/tests/bootstrap/bootstrap.spec.ts +162 -0
- package/api/tests/crons/tenant-fanout.spec.ts +158 -0
- package/api/tests/embedded/embedded-multi-mode-d3.spec.ts +257 -0
- package/api/tests/fixtures/bare-query-violation.ts +13 -0
- package/api/tests/fixtures/core-env-violation.ts +10 -0
- package/api/tests/fixtures/host-read-violation.ts +19 -0
- package/api/tests/fixtures/tenants.ts +4 -0
- package/api/tests/integrations/iap-tenant.spec.ts +284 -0
- package/api/tests/libs/archive-query.spec.ts +26 -0
- package/api/tests/libs/audit-tenant.spec.ts +153 -0
- package/api/tests/libs/context.spec.ts +204 -0
- package/api/tests/libs/core-config.spec.ts +115 -0
- package/api/tests/libs/cron-driver-d2.spec.ts +237 -0
- package/api/tests/libs/crons-conservation-d2.spec.ts +52 -0
- package/api/tests/libs/did-connect-runtime-js.spec.ts +98 -0
- package/api/tests/libs/did-connect-tenant-identity.spec.ts +159 -0
- package/api/tests/libs/lock-tenant.spec.ts +66 -0
- package/api/tests/libs/scoped.spec.ts +222 -0
- package/api/tests/libs/secrets-facade.spec.ts +52 -0
- package/api/tests/libs/service-host.spec.ts +37 -0
- package/api/tests/libs/tenancy-slot-authority.spec.ts +209 -0
- package/api/tests/libs/tenant-middleware.spec.ts +42 -0
- package/api/tests/libs/tenant-scanner.spec.ts +120 -0
- package/api/tests/middlewares/hono/cdn.spec.ts +70 -0
- package/api/tests/middlewares/hono/context.spec.ts +113 -0
- package/api/tests/middlewares/hono/csrf.spec.ts +136 -0
- package/api/tests/middlewares/hono/fallback.spec.ts +67 -0
- package/api/tests/middlewares/hono/pipeline.spec.ts +47 -0
- package/api/tests/middlewares/hono/security.spec.ts +181 -0
- package/api/tests/middlewares/hono/session.spec.ts +42 -0
- package/api/tests/middlewares/hono/xss.spec.ts +81 -0
- package/api/tests/models/tenant-backfill.spec.ts +287 -0
- package/api/tests/models/tenant-columns-model.spec.ts +46 -0
- package/api/tests/models/tenant-columns.spec.ts +161 -0
- package/api/tests/queues/credit-consume-batch.spec.ts +8 -1
- package/api/tests/queues/credit-consume.spec.ts +8 -1
- package/api/tests/queues/event-tenant.spec.ts +292 -0
- package/api/tests/queues/exchange-rate-health-tenant-d6.spec.ts +62 -0
- package/api/tests/queues/queue-parity.spec.ts +249 -0
- package/api/tests/queues/queue-runtime-surface.spec.ts +277 -0
- package/api/tests/queues/queue-teardown-d2.spec.ts +127 -0
- package/api/tests/queues/tenant-matrix-a.spec.ts +245 -0
- package/api/tests/queues/tenant-matrix-b.spec.ts +168 -0
- package/api/tests/routes/connect/hono-attach.spec.ts +107 -0
- package/api/tests/service/collapse.spec.ts +96 -0
- package/api/tests/service/didconnect-storage-slot.spec.ts +60 -0
- package/api/tests/service/fail-closed-http.spec.ts +79 -0
- package/api/tests/service/static-arc-handler.spec.ts +101 -0
- package/api/tests/service/static-externalized.spec.ts +48 -0
- package/api/tests/store/tenant-crosscut.spec.ts +202 -0
- package/api/tests/store/tenant-model-spike.spec.ts +177 -0
- package/api/tests/store/tenant-model.spec.ts +162 -0
- package/api/tests/store/tenant-residual.spec.ts +196 -0
- package/api/third.d.ts +4 -0
- package/blocklet.yml +1 -1
- package/cloudflare/MIGRATION-RUNBOOK.md +3 -8
- package/cloudflare/README.md +34 -27
- package/cloudflare/STAGING-MIGRATION-GUIDE.md +3 -15
- package/cloudflare/build.ts +33 -13
- package/cloudflare/cf-adapter.ts +419 -0
- package/cloudflare/did-connect-runtime.ts +96 -0
- package/cloudflare/did-connect-token-storage.ts +151 -0
- package/cloudflare/esbuild-cf-config.cjs +407 -0
- package/cloudflare/migrations/0006_tenant_columns.sql +46 -0
- package/cloudflare/migrations/0007_tenant_backfill_indexes.sql +65 -0
- package/cloudflare/migrations/0008_schema_parity.sql +16 -0
- package/cloudflare/migrations/0009_remove_did_space_jobs.sql +5 -0
- package/cloudflare/queue-runtime-mode.ts +13 -0
- package/cloudflare/run-build.js +33 -403
- package/cloudflare/scripts/cf-package-import-probe.mjs +90 -0
- package/cloudflare/scripts/didconnect-mock-smoke.mjs +140 -0
- package/cloudflare/shims/blocklet-sdk/asset-host-transformer.ts +20 -0
- package/cloudflare/shims/blocklet-sdk/config.ts +8 -1
- package/cloudflare/shims/blocklet-sdk/login.ts +12 -0
- package/cloudflare/shims/blocklet-sdk/service-api.ts +14 -0
- package/cloudflare/shims/blocklet-sdk/session.ts +4 -2
- package/cloudflare/shims/blocklet-sdk/util-constants.ts +8 -0
- package/cloudflare/shims/blocklet-sdk/wallet-authenticator.ts +16 -1
- package/cloudflare/shims/blocklet-sdk/wallet-handler.ts +18 -3
- package/cloudflare/shims/cron.ts +38 -158
- package/cloudflare/shims/events.ts +124 -0
- package/cloudflare/shims/fastq.ts +15 -1
- package/cloudflare/shims/nedb-storage.ts +16 -8
- package/cloudflare/shims/xss.ts +8 -0
- package/cloudflare/tenant-middleware.ts +36 -0
- package/cloudflare/tests/cf-adapter.spec.ts +244 -0
- package/cloudflare/tests/did-connect-token-storage.spec.ts +105 -0
- package/cloudflare/tests/tenant-middleware.spec.ts +160 -0
- package/cloudflare/tests/worker-handler-gate.spec.ts +69 -0
- package/cloudflare/vite.config.ts +53 -45
- package/cloudflare/worker.ts +261 -448
- package/cloudflare/wrangler.json +0 -6
- package/cloudflare/wrangler.jsonc +0 -6
- package/cloudflare/wrangler.local-e2e.jsonc +25 -0
- package/cloudflare/wrangler.staging.json +0 -6
- package/jest.config.js +3 -1
- package/package.json +33 -38
- package/scripts/bootstrap-inject.ts +166 -0
- package/scripts/core-env-whitelist.json +1 -0
- package/scripts/e2e-12b-runtime.ts +149 -0
- package/scripts/e2e-core-config.ts +125 -0
- package/scripts/e2e-d1-tenancy.ts +116 -0
- package/scripts/e2e-d2-cron-queue.ts +139 -0
- package/scripts/e2e-d3-embedded-multi.ts +171 -0
- package/scripts/e2e-hono-s2.ts +125 -0
- package/scripts/e2e-hono-s3e.ts +135 -0
- package/scripts/e2e-hono-s4.ts +114 -0
- package/scripts/e2e-migration-contract.ts +100 -0
- package/scripts/e2e-s0.ts +61 -0
- package/scripts/e2e-s1.ts +107 -0
- package/scripts/e2e-s2.ts +178 -0
- package/scripts/e2e-s3.ts +110 -0
- package/scripts/e2e-s4.ts +191 -0
- package/scripts/e2e-s5.ts +139 -0
- package/scripts/e2e-s6.ts +127 -0
- package/scripts/e2e-tenant-model.ts +119 -0
- package/scripts/e2e-tenant-worker.ts +199 -0
- package/scripts/gen-sql-migrations.js +46 -0
- package/scripts/phase8-codemod.js +219 -0
- package/scripts/phase9a-env-getters-codemod.js +82 -0
- package/scripts/scan-core-env.js +109 -0
- package/scripts/scan-tenant-queries.js +235 -0
- package/scripts/schema-drift-guard.ts +210 -0
- package/scripts/tenant-scan-whitelist.json +1 -0
- package/src/app.tsx +2 -1
- package/src/env.d.ts +13 -1
- package/src/libs/service-host.ts +13 -0
- package/tsconfig.json +1 -1
- package/vite.arc.config.ts +159 -0
- package/api/src/libs/did-space.ts +0 -235
- package/api/src/libs/middleware.ts +0 -50
- package/api/src/libs/security.ts +0 -192
- package/api/src/queues/space.ts +0 -662
- package/api/src/routes/credit-tokens.ts +0 -38
- package/api/src/routes/exchange-rates.ts +0 -87
- package/api/src/routes/index.ts +0 -142
- package/api/src/routes/integrations/stripe.ts +0 -61
- package/api/src/routes/meters.ts +0 -274
- package/api/src/routes/passports.ts +0 -68
- package/api/src/routes/redirect.ts +0 -20
- package/api/src/routes/tool.ts +0 -65
- package/api/src/routes/webhook-endpoints.ts +0 -126
- package/api/tests/routes/credit-grants.spec.ts +0 -1261
- package/cloudflare/did-connect-auth.ts +0 -527
- package/cloudflare/shims/did-space-js.ts +0 -17
- package/cloudflare/shims/did-space.ts +0 -11
- package/cloudflare/shims/express-compat/index.ts +0 -80
- package/cloudflare/shims/express-compat/types.ts +0 -41
- package/cloudflare/shims/lock.ts +0 -115
- package/cloudflare/shims/queue.ts +0 -611
- package/cloudflare/tests/shims/queue-delayed-persist.spec.ts +0 -87
- package/cloudflare/tests/shims/queue-scheduled.spec.ts +0 -186
package/cloudflare/worker.ts
CHANGED
|
@@ -14,15 +14,30 @@ import { setDB } from './shims/sequelize-d1/model';
|
|
|
14
14
|
import { initialize } from '../api/src/store/models';
|
|
15
15
|
import { Sequelize } from './shims/sequelize-d1/sequelize-class';
|
|
16
16
|
|
|
17
|
-
//
|
|
18
|
-
import
|
|
19
|
-
import
|
|
17
|
+
// Phase 12b: pin the core queue engine to 'workerd' mode BEFORE any business
|
|
18
|
+
// queue module loads (createQueue reads it at import to disable the node poll
|
|
19
|
+
// loop). Side-effect import — keep it ahead of service/crons/queues below.
|
|
20
|
+
import './queue-runtime-mode';
|
|
21
|
+
|
|
22
|
+
// Phase 12a (Option 3 seam) + S3-CF Phase 1B: the worker's /api surface comes from
|
|
23
|
+
// the embedded payment service factory. The factory performs assembly (config slot
|
|
24
|
+
// authoritative via setCoreConfig + model initialize). Phase 1B: the worker forwards
|
|
25
|
+
// every payment /api/* (business resource routes + DID payment actions) through the
|
|
26
|
+
// runtime-neutral `http.fetch` (the core full app + full pipeline) — a SINGLE
|
|
27
|
+
// surface. The factory's lazy `handler` getter is never touched here (hard-gated),
|
|
28
|
+
// so the worker only ever drives the core via `http.fetch`.
|
|
29
|
+
import { createEmbeddedPaymentService } from '../api/src/service';
|
|
30
|
+
import type { PaymentCoreService } from '../api/src/service';
|
|
20
31
|
|
|
21
32
|
// Import cron instance for scheduled handler
|
|
22
33
|
import { cronInstance } from './shims/cron';
|
|
23
34
|
|
|
24
|
-
//
|
|
25
|
-
|
|
35
|
+
// Phase 12b: drive the SAME core queue engine (api/src/libs/queue) through its
|
|
36
|
+
// host-facing runtime surface instead of the legacy cloudflare/shims/queue.ts
|
|
37
|
+
// duplicate engine (dead under the canonical build — its registry is never
|
|
38
|
+
// populated). scheduled() → dispatchDueJobs(); queue() → getQueueHandler();
|
|
39
|
+
// HTTP/scheduled/queue flush → flushQueueWork().
|
|
40
|
+
import { dispatchDueJobs, getQueueHandler, getAllQueueNames, flushQueueWork } from '../api/src/libs/queue/runtime';
|
|
26
41
|
|
|
27
42
|
// Import crons init to register all cron jobs
|
|
28
43
|
import crons from '../api/src/crons/index';
|
|
@@ -51,7 +66,10 @@ import { resetD1Timing, getD1Timing } from './shims/sequelize-d1/timing';
|
|
|
51
66
|
import { withD1Retry } from './shims/sequelize-d1/retry';
|
|
52
67
|
|
|
53
68
|
// DID Connect: login routes proxied to blocklet-service, business actions (pay/subscribe) handled locally
|
|
54
|
-
import {
|
|
69
|
+
import { createCloudflareDidConnectRuntime, createCloudflareIdentityDriver } from './did-connect-runtime';
|
|
70
|
+
|
|
71
|
+
// Phase 7: tenant-context middleware — resolves Host -> tenant (single point)
|
|
72
|
+
// and wraps the request chain in context.withTenant (multi-mode fail-closed).
|
|
55
73
|
|
|
56
74
|
FetchRequest.registerGetUrl(async (req: FetchRequest) => {
|
|
57
75
|
const resp = await fetch(req.url, {
|
|
@@ -81,7 +99,6 @@ interface CallerIdentityDTO {
|
|
|
81
99
|
|
|
82
100
|
interface Env {
|
|
83
101
|
DB: D1Database;
|
|
84
|
-
DID_CONNECT_KV: KVNamespace;
|
|
85
102
|
JOB_QUEUE: Queue;
|
|
86
103
|
ASSETS: { fetch: (request: Request | string) => Promise<Response> };
|
|
87
104
|
APP_SK: string;
|
|
@@ -109,6 +126,17 @@ interface Env {
|
|
|
109
126
|
role?: string;
|
|
110
127
|
approved?: number;
|
|
111
128
|
} | null>;
|
|
129
|
+
// S3-CF (DID convergence): did-connect-service@4.0.3 — the per-instance app
|
|
130
|
+
// signing identity for the DID-Connect authenticator. The RPC resolves the arc
|
|
131
|
+
// run-mode internally (instance app:sk → instance identity; else auth-service
|
|
132
|
+
// root APP_SK/APP_PSK; else fail-closed), so payment-core never reimplements it.
|
|
133
|
+
getInstanceAppIdentity: (instanceDid: string) => Promise<{
|
|
134
|
+
appSk: string;
|
|
135
|
+
appPsk?: string;
|
|
136
|
+
appInfo?: { name?: string; description?: string; icon?: string; link?: string };
|
|
137
|
+
}>;
|
|
138
|
+
getAppEk?: (instanceDid: string) => Promise<string | null>;
|
|
139
|
+
resolveInstanceDidForHost?: (host: string) => Promise<string | null>;
|
|
112
140
|
};
|
|
113
141
|
HYPERDRIVE: { connectionString: string };
|
|
114
142
|
[key: string]: any;
|
|
@@ -204,6 +232,101 @@ function ensureModelsInit() {
|
|
|
204
232
|
}
|
|
205
233
|
}
|
|
206
234
|
|
|
235
|
+
// Phase 12a/12c: build the explicit PaymentCoreConfig from CF env. These keys are
|
|
236
|
+
// read only by the core (via the libs/env.ts readConfig boundary), never by a
|
|
237
|
+
// worker shim, so the factory config slot carries them. Phase 12c removed the
|
|
238
|
+
// last `process.env` mirror: the HTTP path (buildApp) and the scheduled()/queue()
|
|
239
|
+
// path (setupEnv) both call ensurePaymentService(env) -> setCoreConfig, so the
|
|
240
|
+
// config slot is authoritative on every path and no process.env fallback remains.
|
|
241
|
+
function envToPaymentCoreConfig(env: Env): Record<string, any> {
|
|
242
|
+
const config: Record<string, any> = { BLOCKLET_MODE: 'production' };
|
|
243
|
+
if (env.APP_PID) {
|
|
244
|
+
config.BLOCKLET_APP_PID = env.APP_PID;
|
|
245
|
+
config.BLOCKLET_APP_ID = env.APP_PID;
|
|
246
|
+
}
|
|
247
|
+
if (env.APP_URL) {
|
|
248
|
+
config.APP_URL = env.APP_URL;
|
|
249
|
+
config.BLOCKLET_APP_URL = env.APP_URL;
|
|
250
|
+
}
|
|
251
|
+
if (env.APP_NAME) config.BLOCKLET_APP_NAME = env.APP_NAME;
|
|
252
|
+
// Stripe webhook secret: env override for signature verification (DB value may
|
|
253
|
+
// be empty if not configured in the original Blocklet Server).
|
|
254
|
+
if (env.STRIPE_WEBHOOK_SECRET) config.STRIPE_WEBHOOK_SECRET = env.STRIPE_WEBHOOK_SECRET;
|
|
255
|
+
if (env.PAYMENT_CHANGE_LOCKED_PRICE) config.PAYMENT_CHANGE_LOCKED_PRICE = env.PAYMENT_CHANGE_LOCKED_PRICE;
|
|
256
|
+
if (env.SHORT_URL_DOMAIN) config.SHORT_URL_DOMAIN = env.SHORT_URL_DOMAIN;
|
|
257
|
+
// Audience for the Pub/Sub OIDC JWT wrapping Google Play RTDN webhooks; without
|
|
258
|
+
// it googlePlayEndpoint() falls back to the workers.dev origin -> audience mismatch.
|
|
259
|
+
if (env.GOOGLE_PLAY_WEBHOOK_URL) config.GOOGLE_PLAY_WEBHOOK_URL = env.GOOGLE_PLAY_WEBHOOK_URL;
|
|
260
|
+
// Pub/Sub sender binding — without it the google_play webhook can't enforce the
|
|
261
|
+
// push service account on CF and would fail open (PR #1381 P1).
|
|
262
|
+
if (env.GOOGLE_PUBSUB_PUSH_SERVICE_ACCOUNT)
|
|
263
|
+
config.GOOGLE_PUBSUB_PUSH_SERVICE_ACCOUNT = env.GOOGLE_PUBSUB_PUSH_SERVICE_ACCOUNT;
|
|
264
|
+
if (env.GOOGLE_PUBSUB_ALLOW_UNVERIFIED_SENDER)
|
|
265
|
+
config.GOOGLE_PUBSUB_ALLOW_UNVERIFIED_SENDER = env.GOOGLE_PUBSUB_ALLOW_UNVERIFIED_SENDER;
|
|
266
|
+
return config;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Phase 12a: assemble the embedded payment service once per isolate. The factory
|
|
270
|
+
// performs config/db assembly (setCoreConfig + initialize), so models are bound
|
|
271
|
+
// here and ensureModelsInit() becomes a no-op on the HTTP path. Slots beyond
|
|
272
|
+
// config/db are intentionally omitted in 12a (defaults preserve current worker
|
|
273
|
+
// behavior: single-mode default tenant resolved from BLOCKLET_APP_PID); richer
|
|
274
|
+
// slot bridging is 12b/12c.
|
|
275
|
+
let paymentService: PaymentCoreService | null = null;
|
|
276
|
+
function ensurePaymentService(env: Env): PaymentCoreService {
|
|
277
|
+
if (paymentService) return paymentService;
|
|
278
|
+
const sequelize = new Sequelize();
|
|
279
|
+
const svc = createEmbeddedPaymentService({
|
|
280
|
+
config: envToPaymentCoreConfig(env),
|
|
281
|
+
db: { sequelize },
|
|
282
|
+
// S3-CF (DID convergence): inject the CF DID-Connect runtime (real
|
|
283
|
+
// @arcblock/did-connect-js + CF chain/txEncoder/timeout + tenant-aware D1 token
|
|
284
|
+
// store) and the AUTH_SERVICE-backed identity driver (getInstanceAppIdentity).
|
|
285
|
+
// The core buildConnectRoutesHono then registers the 14 payment DID actions with
|
|
286
|
+
// a per-tenant signing identity — the worker no longer owns a private DID surface.
|
|
287
|
+
identity: createCloudflareIdentityDriver(),
|
|
288
|
+
didConnectRuntime: createCloudflareDidConnectRuntime(),
|
|
289
|
+
});
|
|
290
|
+
// Phase 12c HARD GATE (updated S3-CF Phase 1B): the CF worker is a host adapter
|
|
291
|
+
// that drives the core via the runtime-neutral `svc.http.fetch`. It must NEVER
|
|
292
|
+
// read `svc.handler` directly — that lazy getter is the node-host convenience
|
|
293
|
+
// entry (it would wire the node staticHandler if one were injected). The worker
|
|
294
|
+
// forwards through `svc.http.fetch`, which builds the same full hono app but is
|
|
295
|
+
// the sanctioned runtime-neutral seam. Trap `handler` so a future regression
|
|
296
|
+
// fails loud instead of silently taking the node-convenience path.
|
|
297
|
+
paymentService = new Proxy(svc, {
|
|
298
|
+
get(target, prop, receiver) {
|
|
299
|
+
if (prop === 'handler') {
|
|
300
|
+
throw new Error(
|
|
301
|
+
'[worker hard-gate] svc.handler is forbidden in the CF worker — use svc.http.fetch instead'
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
return Reflect.get(target, prop, receiver);
|
|
305
|
+
},
|
|
306
|
+
});
|
|
307
|
+
modelsInitialized = true; // the factory called initialize(sequelize)
|
|
308
|
+
return paymentService;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Phase 2 (W1-1b): static D1 migration SQL cannot know the deployment app
|
|
312
|
+
// DID, so the instance_did backfill + unique-key rebuilds run through the
|
|
313
|
+
// shared runtime routine. Idempotent (NULL-only updates, DDL checks), so a
|
|
314
|
+
// once-per-isolate guard just avoids 38 no-op UPDATEs on every cron tick.
|
|
315
|
+
let tenantBackfillDone = false;
|
|
316
|
+
async function ensureTenantBackfill() {
|
|
317
|
+
if (tenantBackfillDone) return;
|
|
318
|
+
try {
|
|
319
|
+
const { runTenantBackfill } = await import('../api/src/store/tenant-backfill');
|
|
320
|
+
const result = await runTenantBackfill(new Sequelize() as any);
|
|
321
|
+
const touched = Object.entries(result.backfilled).filter(([, n]) => n > 0);
|
|
322
|
+
if (touched.length) console.log('[tenant-backfill] backfilled:', JSON.stringify(Object.fromEntries(touched)));
|
|
323
|
+
tenantBackfillDone = true;
|
|
324
|
+
} catch (e: any) {
|
|
325
|
+
// fail-loud but non-fatal: crons still run; next tick retries
|
|
326
|
+
console.error('[tenant-backfill] failed:', e?.message || e);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
207
330
|
function ensureCronsInit() {
|
|
208
331
|
if (!cronsInitialized) {
|
|
209
332
|
try {
|
|
@@ -234,10 +357,19 @@ function buildApp(env: Env): Hono<HonoEnv> {
|
|
|
234
357
|
return cachedApp;
|
|
235
358
|
}
|
|
236
359
|
|
|
360
|
+
// Phase 12a + S3-CF Phase 1B: assemble the embedded payment service
|
|
361
|
+
// (config/db/slots incl. the injected CF DID-Connect runtime). The worker drives
|
|
362
|
+
// it through `service.http.fetch` (the single runtime-neutral surface); the
|
|
363
|
+
// node-only `handler` getter is hard-gated and never touched.
|
|
364
|
+
const service = ensurePaymentService(env);
|
|
365
|
+
|
|
237
366
|
const app = new Hono<HonoEnv>();
|
|
238
367
|
|
|
239
368
|
// CORS
|
|
240
|
-
|
|
369
|
+
// S3-CF Phase 1B: payment `/api/*` CORS is owned by the CORE full pipeline
|
|
370
|
+
// (cors/xss/csrf/i18n/cdn/context), reached via service.http.fetch. The worker no
|
|
371
|
+
// longer pre-applies cors() to /api/* — that would double the CORS headers on
|
|
372
|
+
// every payment route. Worker-owned cross-origin surfaces keep their own cors:
|
|
241
373
|
app.use('/.well-known/*', cors());
|
|
242
374
|
// /__blocklet__.js is fetched by external wallets (e.g. abtwallet.io, localhost
|
|
243
375
|
// dev wallets) to resolve app metadata + chain info before starting DID Connect.
|
|
@@ -255,43 +387,17 @@ function buildApp(env: Env): Hono<HonoEnv> {
|
|
|
255
387
|
// In queue consumer/cron, this flag is absent — createEvent uses __cfPendingJobs__ (blocking).
|
|
256
388
|
(globalThis as any).__cfHttpContext__ = true;
|
|
257
389
|
setDB(withD1Retry(c.env.DB.withSession('first-primary')));
|
|
258
|
-
if (c.env.JOB_QUEUE) setCFQueue(c.env.JOB_QUEUE);
|
|
259
390
|
ensureModelsInit();
|
|
260
391
|
|
|
261
|
-
//
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
if (c.env.APP_PID) {
|
|
271
|
-
process.env.BLOCKLET_APP_PID = c.env.APP_PID;
|
|
272
|
-
process.env.BLOCKLET_APP_ID = c.env.APP_PID;
|
|
273
|
-
}
|
|
274
|
-
if (c.env.APP_NAME) process.env.BLOCKLET_APP_NAME = c.env.APP_NAME;
|
|
275
|
-
if (c.env.PAYMENT_CHANGE_LOCKED_PRICE) process.env.PAYMENT_CHANGE_LOCKED_PRICE = c.env.PAYMENT_CHANGE_LOCKED_PRICE;
|
|
276
|
-
if (c.env.SHORT_URL_DOMAIN) process.env.SHORT_URL_DOMAIN = c.env.SHORT_URL_DOMAIN;
|
|
277
|
-
// Audience for the Pub/Sub OIDC JWT that wraps Google Play RTDN webhooks.
|
|
278
|
-
// Without this mirror, libs/util.ts googlePlayEndpoint() falls back to
|
|
279
|
-
// getUrl('/api/integrations/google-play/webhook') — which resolves to
|
|
280
|
-
// the *.workers.dev origin, not the custom domain Pub/Sub actually
|
|
281
|
-
// POSTs to. Every webhook then dies with "audience mismatch".
|
|
282
|
-
if (c.env.GOOGLE_PLAY_WEBHOOK_URL) {
|
|
283
|
-
process.env.GOOGLE_PLAY_WEBHOOK_URL = c.env.GOOGLE_PLAY_WEBHOOK_URL;
|
|
284
|
-
}
|
|
285
|
-
// Pub/Sub sender binding — without mirroring these, the google_play webhook
|
|
286
|
-
// can't enforce the push service account on CF and would fail open (PR #1381 P1).
|
|
287
|
-
if (c.env.GOOGLE_PUBSUB_PUSH_SERVICE_ACCOUNT) {
|
|
288
|
-
process.env.GOOGLE_PUBSUB_PUSH_SERVICE_ACCOUNT = c.env.GOOGLE_PUBSUB_PUSH_SERVICE_ACCOUNT;
|
|
289
|
-
}
|
|
290
|
-
if (c.env.GOOGLE_PUBSUB_ALLOW_UNVERIFIED_SENDER) {
|
|
291
|
-
process.env.GOOGLE_PUBSUB_ALLOW_UNVERIFIED_SENDER = c.env.GOOGLE_PUBSUB_ALLOW_UNVERIFIED_SENDER;
|
|
292
|
-
}
|
|
293
|
-
process.env.BLOCKLET_MODE = 'production';
|
|
294
|
-
}
|
|
392
|
+
// Phase 12a: CF env now flows through the factory config slot (assembled in
|
|
393
|
+
// ensurePaymentService -> setCoreConfig, made authoritative via the
|
|
394
|
+
// libs/env.ts readConfig boundary). The per-request process.env mirror that
|
|
395
|
+
// lived here is gone. This defensive idempotent call guarantees the config is
|
|
396
|
+
// wired for this isolate regardless of Hono app-cache state; it is a no-op
|
|
397
|
+
// after the first call. (Phase 12c: scheduled()/queue() call the same
|
|
398
|
+
// ensurePaymentService via setupEnv, so the config slot is authoritative
|
|
399
|
+
// there too — no process.env mirror on any path.)
|
|
400
|
+
ensurePaymentService(c.env);
|
|
295
401
|
|
|
296
402
|
// Register Stripe key decrypt overrides from env vars
|
|
297
403
|
// Fetch EK from AUTH_SERVICE and initialize decrypt capability (first request only)
|
|
@@ -327,7 +433,23 @@ function buildApp(env: Env): Hono<HonoEnv> {
|
|
|
327
433
|
if (caller) {
|
|
328
434
|
authSource = 'cache';
|
|
329
435
|
} else {
|
|
330
|
-
caller
|
|
436
|
+
// S3-CF Phase 1B: parameterize the caller RPC with the request's actual
|
|
437
|
+
// tenant via the SAME Host→tenant resolver the core uses — not a fixed
|
|
438
|
+
// env.APP_PID — so a multi-tenant embed verifies the caller against the
|
|
439
|
+
// right instance. Non-throwing: an unresolved host (multi) keeps the
|
|
440
|
+
// APP_PID fallback. Lazily required (NOT a top-level import) to avoid
|
|
441
|
+
// pulling the identity module into the worker's eager startup graph. This
|
|
442
|
+
// does NOT wrap the business handler's ALS context (the core owns that).
|
|
443
|
+
let callerInstanceDid: string | undefined = c.env.APP_PID;
|
|
444
|
+
try {
|
|
445
|
+
// eslint-disable-next-line global-require
|
|
446
|
+
const { resolveTenantForHost } = require('../api/src/libs/drivers/identity');
|
|
447
|
+
const resolved = await resolveTenantForHost(c.req.header('host'));
|
|
448
|
+
if (resolved) callerInstanceDid = resolved;
|
|
449
|
+
} catch {
|
|
450
|
+
/* unresolved host → keep the APP_PID fallback for the caller RPC param */
|
|
451
|
+
}
|
|
452
|
+
caller = await authService.resolveIdentity(jwt, authHeader, callerInstanceDid);
|
|
331
453
|
authSource = 'rpc';
|
|
332
454
|
if (caller && cacheKey) {
|
|
333
455
|
cacheIdentity(cacheKey, caller);
|
|
@@ -351,10 +473,7 @@ function buildApp(env: Env): Hono<HonoEnv> {
|
|
|
351
473
|
// --- Append Server-Timing header ---
|
|
352
474
|
const totalDur = Math.round(performance.now() - t0);
|
|
353
475
|
const d1 = getD1Timing();
|
|
354
|
-
const timings = [
|
|
355
|
-
`total;dur=${totalDur}`,
|
|
356
|
-
`auth;dur=${authDur};desc="${authSource}"`,
|
|
357
|
-
];
|
|
476
|
+
const timings = [`total;dur=${totalDur}`, `auth;dur=${authDur};desc="${authSource}"`];
|
|
358
477
|
if (d1.queries > 0) {
|
|
359
478
|
timings.push(`db;dur=${Math.round(d1.wallMs)};desc="${d1.queries}q ${d1.rowsRead}r"`);
|
|
360
479
|
if (d1.sqlMs > 0) timings.push(`db_sql;dur=${Math.round(d1.sqlMs)}`);
|
|
@@ -366,6 +485,14 @@ function buildApp(env: Env): Hono<HonoEnv> {
|
|
|
366
485
|
c.res.headers.append('Server-Timing', timings.join(', '));
|
|
367
486
|
});
|
|
368
487
|
|
|
488
|
+
// S3-CF Phase 1B: the ALS tenant context for payment `/api/*` business requests
|
|
489
|
+
// is owned by the CORE full pipeline (contextMiddleware), reached via
|
|
490
|
+
// service.http.fetch. The worker no longer wraps /api/* in tenantMiddleware() —
|
|
491
|
+
// that would double-resolve + double-wrap the tenant. The few worker-owned /api/*
|
|
492
|
+
// endpoints (dev/debug/proxies) run in single-mode default and never query under
|
|
493
|
+
// a per-request tenant. (DID payment actions get their context from the core
|
|
494
|
+
// buildConnectRoutesHono tenant middleware, also via http.fetch.)
|
|
495
|
+
|
|
369
496
|
// Health check
|
|
370
497
|
app.get('/health', (c) => c.json({ status: 'ok' }));
|
|
371
498
|
|
|
@@ -567,8 +694,9 @@ function buildApp(env: Env): Hono<HonoEnv> {
|
|
|
567
694
|
|
|
568
695
|
// === DID Auth Login routes ===
|
|
569
696
|
// Only proxy login-related /api/did/* paths to blocklet-service.
|
|
570
|
-
// Other /api/did/* paths (subscription, pay, collect, etc.) are Payment Kit's
|
|
571
|
-
//
|
|
697
|
+
// Other /api/did/* paths (subscription, pay, collect, etc.) are Payment Kit's own
|
|
698
|
+
// DID Connect payment actions — they fall through to the /api/* → service.http.fetch
|
|
699
|
+
// dispatcher (the core full app's DID payment actions).
|
|
572
700
|
const DID_AUTH_PROXY_PATHS = [
|
|
573
701
|
'/api/did/login/',
|
|
574
702
|
'/api/did/session',
|
|
@@ -579,7 +707,10 @@ function buildApp(env: Env): Hono<HonoEnv> {
|
|
|
579
707
|
app.all('/api/did/*', async (c, next) => {
|
|
580
708
|
const path = new URL(c.req.url).pathname;
|
|
581
709
|
const shouldProxy = DID_AUTH_PROXY_PATHS.some((p) => path.startsWith(p) || path === p);
|
|
582
|
-
|
|
710
|
+
// S3-CF Phase 1B: non-proxy payment DID actions fall through to the /api/*
|
|
711
|
+
// dispatcher below → service.http.fetch (the core full app includes the DID
|
|
712
|
+
// payment actions via buildConnectRoutesHono).
|
|
713
|
+
if (!shouldProxy) return next();
|
|
583
714
|
|
|
584
715
|
if (!c.env.AUTH_SERVICE) {
|
|
585
716
|
return c.json({ error: 'AUTH_SERVICE not configured' }, 503);
|
|
@@ -596,22 +727,19 @@ function buildApp(env: Env): Hono<HonoEnv> {
|
|
|
596
727
|
});
|
|
597
728
|
|
|
598
729
|
// === DID Connect business actions (subscription, pay, collect, etc.) ===
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
} catch (e: any) {
|
|
604
|
-
console.error('DID Connect init error:', e?.message || e);
|
|
605
|
-
}
|
|
606
|
-
}
|
|
730
|
+
// S3-CF Phase 1B: the 14 payment DID actions are part of the CORE full app and
|
|
731
|
+
// are reached through the /api/* → service.http.fetch dispatcher below (they were
|
|
732
|
+
// converged onto buildConnectRoutesHono in Phase 1A, backed by the CF DID-Connect
|
|
733
|
+
// runtime injected into the service above). No separate worker mount — one surface.
|
|
607
734
|
|
|
608
735
|
// Notification unread count
|
|
609
736
|
app.get('/api/notifications/unread-count', (c) => c.json({ unReadCount: 0 }));
|
|
610
737
|
|
|
611
|
-
// Manually trigger job dispatch (same as cron's
|
|
738
|
+
// Manually trigger job dispatch (same as cron's scheduled() due-dispatch)
|
|
612
739
|
app.post('/api/__dev__/dispatch-jobs', async (c) => {
|
|
613
|
-
const names =
|
|
614
|
-
const result = await
|
|
740
|
+
const names = getAllQueueNames();
|
|
741
|
+
const result = await dispatchDueJobs();
|
|
742
|
+
await flushQueueWork();
|
|
615
743
|
return c.json({ handlers: names, ...result });
|
|
616
744
|
});
|
|
617
745
|
|
|
@@ -640,8 +768,9 @@ function buildApp(env: Env): Hono<HonoEnv> {
|
|
|
640
768
|
// for creating PaymentMethods is owner-gated, and staging has no owner yet
|
|
641
769
|
// (Pengfei's blocklet-service role is `member`). Gated by PAYMENT_LIVEMODE
|
|
642
770
|
// === 'false' so this only ever touches testmode data.
|
|
643
|
-
//
|
|
644
|
-
|
|
771
|
+
// S3-CF Phase 1B: payment business routes (and DID payment actions) are served by
|
|
772
|
+
// the core full app via the /api/* → service.http.fetch dispatcher, registered
|
|
773
|
+
// AFTER the worker's own /api/__dev__ routes so those match first.
|
|
645
774
|
|
|
646
775
|
// Dev endpoint: D1 admin operations
|
|
647
776
|
// Test CF Queue send directly
|
|
@@ -986,9 +1115,39 @@ function buildApp(env: Env): Hono<HonoEnv> {
|
|
|
986
1115
|
}
|
|
987
1116
|
});
|
|
988
1117
|
|
|
989
|
-
//
|
|
990
|
-
|
|
991
|
-
|
|
1118
|
+
// S3-CF Phase 1B: the single payment HTTP surface. Every payment `/api/*` request
|
|
1119
|
+
// (business resource routes + non-proxy DID payment actions) that wasn't handled
|
|
1120
|
+
// by a worker-owned route above is forwarded to the CORE full app via
|
|
1121
|
+
// service.http.fetch. The core full pipeline owns cors/xss/csrf/i18n/cdn/context
|
|
1122
|
+
// + the resource routes + the DID payment actions — there is no more LITE
|
|
1123
|
+
// resourceRoutes dispatcher and no second surface.
|
|
1124
|
+
//
|
|
1125
|
+
// The worker stays the HOST GLUE: it injects the caller identity it resolved
|
|
1126
|
+
// (caller RPC) as canonical x-user-* request headers — the core authenticate()
|
|
1127
|
+
// reads those — and STRIPS any client-supplied x-user-* first so it can never be
|
|
1128
|
+
// forged (component auth via x-component-sig is verified cryptographically
|
|
1129
|
+
// downstream and left intact). Raw body bytes are forwarded unconsumed (the
|
|
1130
|
+
// worker never reads the body here), preserving Stripe webhook signature fidelity
|
|
1131
|
+
// through to the core webhook route. flushQueueWork() drains the workerd deferred
|
|
1132
|
+
// queue work before responding.
|
|
1133
|
+
const USER_HEADERS = ['x-user-did', 'x-user-role', 'x-user-provider', 'x-user-fullname', 'x-user-wallet-os'];
|
|
1134
|
+
app.all('/api/*', async (c) => {
|
|
1135
|
+
const headers = new Headers(c.req.raw.headers);
|
|
1136
|
+
for (const h of USER_HEADERS) headers.delete(h); // never trust a client-supplied identity header
|
|
1137
|
+
const caller: CallerIdentityDTO | null = c.get('caller');
|
|
1138
|
+
if (caller) {
|
|
1139
|
+
const canonicalDid = caller.did?.startsWith('did:abt:') ? caller.did : `did:abt:${caller.did}`;
|
|
1140
|
+
headers.set('x-user-did', canonicalDid);
|
|
1141
|
+
headers.set('x-user-role', `blocklet-${caller.role || 'guest'}`);
|
|
1142
|
+
headers.set('x-user-provider', caller.authMethod === 'access-key' ? 'access-key' : caller.authMethod || 'wallet');
|
|
1143
|
+
headers.set('x-user-fullname', encodeURIComponent(caller.displayName || ''));
|
|
1144
|
+
headers.set('x-user-wallet-os', '');
|
|
1145
|
+
}
|
|
1146
|
+
// No basePath: the standalone worker serves payment routes at the root /api/*,
|
|
1147
|
+
// so the core full app matches them directly (raw bytes/headers carried verbatim).
|
|
1148
|
+
const res = await service.http.fetch(new Request(c.req.raw, { headers }));
|
|
1149
|
+
await flushQueueWork(); // drain workerd deferred queue work before responding
|
|
1150
|
+
return res;
|
|
992
1151
|
});
|
|
993
1152
|
|
|
994
1153
|
// === Media Kit Proxy ===
|
|
@@ -1158,347 +1317,6 @@ function buildApp(env: Env): Hono<HonoEnv> {
|
|
|
1158
1317
|
return app;
|
|
1159
1318
|
}
|
|
1160
1319
|
|
|
1161
|
-
// === Express-to-Hono Route Adapter ===
|
|
1162
|
-
|
|
1163
|
-
function normalizeRoutePath(prefix: string, routePath: string): string {
|
|
1164
|
-
let full = (prefix + routePath).replace(/\/+/g, '/');
|
|
1165
|
-
if (!full.startsWith('/')) full = `/${full}`;
|
|
1166
|
-
if (full.length > 1 && full.endsWith('/')) full = full.slice(0, -1);
|
|
1167
|
-
return full;
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
function createExpressReq(c: any, routeParams: Record<string, string>): any {
|
|
1171
|
-
const url = new URL(c.req.url);
|
|
1172
|
-
const query: Record<string, any> = {};
|
|
1173
|
-
url.searchParams.forEach((v, k) => {
|
|
1174
|
-
query[k] = v;
|
|
1175
|
-
});
|
|
1176
|
-
|
|
1177
|
-
const headers: Record<string, string> = {};
|
|
1178
|
-
c.req.raw.headers.forEach((v: string, k: string) => {
|
|
1179
|
-
headers[k.toLowerCase()] = v;
|
|
1180
|
-
});
|
|
1181
|
-
|
|
1182
|
-
const req: any = {
|
|
1183
|
-
method: c.req.method,
|
|
1184
|
-
url: url.pathname + url.search,
|
|
1185
|
-
path: url.pathname,
|
|
1186
|
-
originalUrl: url.pathname + url.search,
|
|
1187
|
-
query,
|
|
1188
|
-
params: { ...routeParams },
|
|
1189
|
-
body: null,
|
|
1190
|
-
headers,
|
|
1191
|
-
user: null,
|
|
1192
|
-
// Worker-wide livemode is driven by the PAYMENT_LIVEMODE env var (set on
|
|
1193
|
-
// each deployment). Routes that filter PaymentMethod by livemode rely on
|
|
1194
|
-
// this value matching what we stored when bootstrapping the methods.
|
|
1195
|
-
livemode: c.env?.PAYMENT_LIVEMODE !== 'false',
|
|
1196
|
-
baseCurrency: null,
|
|
1197
|
-
ip: headers['cf-connecting-ip'] || headers['x-forwarded-for'] || '127.0.0.1',
|
|
1198
|
-
get(name: string) {
|
|
1199
|
-
return headers[name.toLowerCase()];
|
|
1200
|
-
},
|
|
1201
|
-
header(name: string) {
|
|
1202
|
-
return headers[name.toLowerCase()];
|
|
1203
|
-
},
|
|
1204
|
-
};
|
|
1205
|
-
|
|
1206
|
-
return req;
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
function createExpressRes(): any {
|
|
1210
|
-
const res: any = {
|
|
1211
|
-
_statusCode: 200,
|
|
1212
|
-
_headers: {} as Record<string, string>,
|
|
1213
|
-
_body: null as any,
|
|
1214
|
-
_sent: false,
|
|
1215
|
-
_redirectUrl: null as string | null,
|
|
1216
|
-
headersSent: false,
|
|
1217
|
-
|
|
1218
|
-
status(code: number) {
|
|
1219
|
-
res._statusCode = code;
|
|
1220
|
-
return res;
|
|
1221
|
-
},
|
|
1222
|
-
json(data: any) {
|
|
1223
|
-
if (res._sent) return res;
|
|
1224
|
-
res._sent = true;
|
|
1225
|
-
res.headersSent = true;
|
|
1226
|
-
res._body = data;
|
|
1227
|
-
res._headers['content-type'] = 'application/json';
|
|
1228
|
-
return res;
|
|
1229
|
-
},
|
|
1230
|
-
send(data: any) {
|
|
1231
|
-
if (res._sent) return res;
|
|
1232
|
-
res._sent = true;
|
|
1233
|
-
res.headersSent = true;
|
|
1234
|
-
res._body = data;
|
|
1235
|
-
return res;
|
|
1236
|
-
},
|
|
1237
|
-
redirect(urlOrStatus: any, url?: string) {
|
|
1238
|
-
res._sent = true;
|
|
1239
|
-
res.headersSent = true;
|
|
1240
|
-
if (typeof urlOrStatus === 'number') {
|
|
1241
|
-
res._statusCode = urlOrStatus;
|
|
1242
|
-
res._redirectUrl = url;
|
|
1243
|
-
} else {
|
|
1244
|
-
res._statusCode = 302;
|
|
1245
|
-
res._redirectUrl = urlOrStatus;
|
|
1246
|
-
}
|
|
1247
|
-
return res;
|
|
1248
|
-
},
|
|
1249
|
-
set(key: string, value: string) {
|
|
1250
|
-
res._headers[key.toLowerCase()] = value;
|
|
1251
|
-
return res;
|
|
1252
|
-
},
|
|
1253
|
-
setHeader(key: string, value: string) {
|
|
1254
|
-
res._headers[key.toLowerCase()] = value;
|
|
1255
|
-
return res;
|
|
1256
|
-
},
|
|
1257
|
-
cookie(_name: string, _value: string, _options?: any) {
|
|
1258
|
-
return res;
|
|
1259
|
-
},
|
|
1260
|
-
end() {
|
|
1261
|
-
if (!res._sent) {
|
|
1262
|
-
res._sent = true;
|
|
1263
|
-
res.headersSent = true;
|
|
1264
|
-
}
|
|
1265
|
-
},
|
|
1266
|
-
type(t: string) {
|
|
1267
|
-
res._headers['content-type'] = t;
|
|
1268
|
-
return res;
|
|
1269
|
-
},
|
|
1270
|
-
};
|
|
1271
|
-
|
|
1272
|
-
Object.defineProperty(res, 'statusCode', {
|
|
1273
|
-
get() {
|
|
1274
|
-
return res._statusCode;
|
|
1275
|
-
},
|
|
1276
|
-
set(v: number) {
|
|
1277
|
-
res._statusCode = v;
|
|
1278
|
-
},
|
|
1279
|
-
});
|
|
1280
|
-
|
|
1281
|
-
return res;
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
function expressResToResponse(res: any): Response {
|
|
1285
|
-
if (res._redirectUrl) {
|
|
1286
|
-
return Response.redirect(res._redirectUrl, res._statusCode || 302);
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
const headers = new Headers(res._headers);
|
|
1290
|
-
|
|
1291
|
-
if (res._body === null || res._body === undefined) {
|
|
1292
|
-
return new Response(null, { status: res._statusCode, headers });
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
|
-
if (typeof res._body === 'string') {
|
|
1296
|
-
return new Response(res._body, { status: res._statusCode, headers });
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
if (res._body instanceof ArrayBuffer || res._body instanceof Uint8Array) {
|
|
1300
|
-
return new Response(res._body, { status: res._statusCode, headers });
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
if (!headers.has('content-type')) {
|
|
1304
|
-
headers.set('content-type', 'application/json');
|
|
1305
|
-
}
|
|
1306
|
-
let jsonStr = JSON.stringify(res._body);
|
|
1307
|
-
// Rewrite legacy blocklet server URLs to CF Workers domain
|
|
1308
|
-
// Old format: https://old-domain/payment/methods/x.png -> https://cf-domain/methods/x.png
|
|
1309
|
-
const cfAppUrl = ((globalThis as any).__CF_ENV__?.APP_URL || '').replace(/\/$/, '');
|
|
1310
|
-
if (cfAppUrl) {
|
|
1311
|
-
jsonStr = jsonStr
|
|
1312
|
-
.split('https://bbqa7swuuaze4l2y5salvngyjyohlhq5fs5j42eokni.did.abtnet.io/payment/')
|
|
1313
|
-
.join(`${cfAppUrl}/`);
|
|
1314
|
-
jsonStr = jsonStr.split('https://bbqa7swuuaze4l2y5salvngyjyohlhq5fs5j42eokni.did.abtnet.io').join(cfAppUrl);
|
|
1315
|
-
}
|
|
1316
|
-
return new Response(jsonStr, { status: res._statusCode, headers });
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
async function runExpressHandlers(handlers: Function[], req: any, res: any): Promise<void> {
|
|
1320
|
-
let idx = 0;
|
|
1321
|
-
|
|
1322
|
-
async function runNext(err?: any): Promise<void> {
|
|
1323
|
-
if (err) {
|
|
1324
|
-
console.error('[CF Worker] Express middleware error:', err?.message || err);
|
|
1325
|
-
if (!res._sent) {
|
|
1326
|
-
res.status(500).json({ error: err?.message || 'Internal Server Error' });
|
|
1327
|
-
}
|
|
1328
|
-
return;
|
|
1329
|
-
}
|
|
1330
|
-
if (idx >= handlers.length || res._sent) return;
|
|
1331
|
-
|
|
1332
|
-
const handler = handlers[idx++];
|
|
1333
|
-
if (!handler) return runNext();
|
|
1334
|
-
|
|
1335
|
-
if (handler.length === 4) {
|
|
1336
|
-
return runNext();
|
|
1337
|
-
}
|
|
1338
|
-
|
|
1339
|
-
return new Promise<void>((resolve) => {
|
|
1340
|
-
let nextCalled = false;
|
|
1341
|
-
|
|
1342
|
-
try {
|
|
1343
|
-
const result = handler(req, res, (nextErr?: any) => {
|
|
1344
|
-
nextCalled = true;
|
|
1345
|
-
runNext(nextErr)
|
|
1346
|
-
.then(resolve)
|
|
1347
|
-
.catch((e: any) => {
|
|
1348
|
-
if (!res._sent) res.status(500).json({ error: e?.message || 'Internal Server Error' });
|
|
1349
|
-
resolve();
|
|
1350
|
-
});
|
|
1351
|
-
});
|
|
1352
|
-
|
|
1353
|
-
if (result && typeof result.then === 'function') {
|
|
1354
|
-
result
|
|
1355
|
-
.then(() => {
|
|
1356
|
-
if (!nextCalled) {
|
|
1357
|
-
resolve();
|
|
1358
|
-
}
|
|
1359
|
-
})
|
|
1360
|
-
.catch((e: any) => {
|
|
1361
|
-
console.error('[CF Worker] Async handler error:', e?.message || e);
|
|
1362
|
-
if (!res._sent) res.status(500).json({ error: e?.message || 'Internal Server Error' });
|
|
1363
|
-
resolve();
|
|
1364
|
-
});
|
|
1365
|
-
} else if (!nextCalled) {
|
|
1366
|
-
resolve();
|
|
1367
|
-
}
|
|
1368
|
-
} catch (e: any) {
|
|
1369
|
-
console.error('[CF Worker] Sync handler error:', e?.message || e);
|
|
1370
|
-
if (!res._sent) res.status(500).json({ error: e?.message || 'Internal Server Error' });
|
|
1371
|
-
resolve();
|
|
1372
|
-
}
|
|
1373
|
-
});
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
await runNext();
|
|
1377
|
-
}
|
|
1378
|
-
|
|
1379
|
-
function mountExpressRoutes(honoApp: Hono<HonoEnv>, prefix: string, expressRouter: any) {
|
|
1380
|
-
const routes: RouteEntry[] = expressRouter._routes || [];
|
|
1381
|
-
|
|
1382
|
-
console.log(`[CF Worker] Mounting ${routes.length} Express routes under ${prefix}`);
|
|
1383
|
-
|
|
1384
|
-
for (const route of routes) {
|
|
1385
|
-
const fullPath = normalizeRoutePath(prefix, route.path);
|
|
1386
|
-
const method = route.method.toLowerCase() as 'get' | 'post' | 'put' | 'patch' | 'delete';
|
|
1387
|
-
|
|
1388
|
-
if (!['get', 'post', 'put', 'patch', 'delete'].includes(method)) {
|
|
1389
|
-
console.warn(`[CF Worker] Skipping unsupported method: ${route.method} ${fullPath}`);
|
|
1390
|
-
continue;
|
|
1391
|
-
}
|
|
1392
|
-
|
|
1393
|
-
honoApp[method](fullPath, async (c) => {
|
|
1394
|
-
const req = createExpressReq(c, c.req.param());
|
|
1395
|
-
const res = createExpressRes();
|
|
1396
|
-
|
|
1397
|
-
if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(c.req.method)) {
|
|
1398
|
-
try {
|
|
1399
|
-
const contentType = c.req.header('content-type') || '';
|
|
1400
|
-
const isStripeWebhook = fullPath.includes('/integrations/stripe/webhook');
|
|
1401
|
-
|
|
1402
|
-
if (isStripeWebhook) {
|
|
1403
|
-
const rawBody = await c.req.arrayBuffer();
|
|
1404
|
-
req.body = Buffer.from(rawBody);
|
|
1405
|
-
req.rawBody = req.body;
|
|
1406
|
-
} else if (contentType.includes('application/json')) {
|
|
1407
|
-
req.body = await c.req.json();
|
|
1408
|
-
} else if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
1409
|
-
const text = await c.req.text();
|
|
1410
|
-
req.body = Object.fromEntries(new URLSearchParams(text));
|
|
1411
|
-
} else if (contentType.includes('text/')) {
|
|
1412
|
-
req.body = await c.req.text();
|
|
1413
|
-
} else {
|
|
1414
|
-
try {
|
|
1415
|
-
req.body = await c.req.json();
|
|
1416
|
-
} catch {
|
|
1417
|
-
try {
|
|
1418
|
-
req.body = await c.req.text();
|
|
1419
|
-
} catch {
|
|
1420
|
-
req.body = null;
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
|
-
}
|
|
1424
|
-
} catch {
|
|
1425
|
-
req.body = {};
|
|
1426
|
-
}
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1429
|
-
// Debug logging for webhook
|
|
1430
|
-
if (fullPath.includes('stripe/webhook')) {
|
|
1431
|
-
console.log('[CF Worker] Stripe webhook request received:', {
|
|
1432
|
-
method: c.req.method,
|
|
1433
|
-
path: fullPath,
|
|
1434
|
-
hasSignature: !!req.headers['stripe-signature'],
|
|
1435
|
-
bodyType: typeof req.body,
|
|
1436
|
-
bodyLength: req.body?.length || 0,
|
|
1437
|
-
isBuffer: Buffer.isBuffer(req.body),
|
|
1438
|
-
handlersCount: route.handlers.length,
|
|
1439
|
-
});
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
|
-
// Inject caller identity resolved by AUTH_SERVICE RPC (or mock fallback).
|
|
1443
|
-
// AUTH_SERVICE returns the bare base58 address; Customer/Subscription queries
|
|
1444
|
-
// and entitlement lookups expect the canonical `did:abt:…` form, so we
|
|
1445
|
-
// normalize once here at the boundary instead of in every downstream call site.
|
|
1446
|
-
const caller: CallerIdentityDTO | null = c.get('caller');
|
|
1447
|
-
if (caller) {
|
|
1448
|
-
const canonicalDid = caller.did?.startsWith('did:abt:') ? caller.did : `did:abt:${caller.did}`;
|
|
1449
|
-
req.user = {
|
|
1450
|
-
did: canonicalDid,
|
|
1451
|
-
role: caller.role || 'guest',
|
|
1452
|
-
provider: caller.authMethod === 'access-key' ? 'access-key' : 'wallet',
|
|
1453
|
-
fullName: caller.displayName || '',
|
|
1454
|
-
walletOS: '',
|
|
1455
|
-
via: 'dashboard',
|
|
1456
|
-
};
|
|
1457
|
-
req.headers['x-user-did'] = canonicalDid;
|
|
1458
|
-
req.headers['x-user-role'] = `blocklet-${caller.role || 'guest'}`;
|
|
1459
|
-
req.headers['x-user-provider'] = caller.authMethod || 'wallet';
|
|
1460
|
-
req.headers['x-user-fullname'] = encodeURIComponent(caller.displayName || '');
|
|
1461
|
-
req.headers['x-user-wallet-os'] = '';
|
|
1462
|
-
} else {
|
|
1463
|
-
req.user = { did: '', role: 'guest', provider: '', fullName: '', walletOS: '', via: '' };
|
|
1464
|
-
}
|
|
1465
|
-
|
|
1466
|
-
try {
|
|
1467
|
-
await runExpressHandlers(route.handlers, req, res);
|
|
1468
|
-
} catch (e: any) {
|
|
1469
|
-
console.error(
|
|
1470
|
-
`[CF Worker] Unhandled error in ${route.method} ${fullPath}:`,
|
|
1471
|
-
e?.message || e,
|
|
1472
|
-
'\n',
|
|
1473
|
-
e?.stack?.split('\n').slice(0, 8).join('\n')
|
|
1474
|
-
);
|
|
1475
|
-
if (!res._sent) {
|
|
1476
|
-
res.status(500).json({ error: e?.message || 'Internal Server Error' });
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
|
|
1480
|
-
// Debug logging for webhook response
|
|
1481
|
-
if (fullPath.includes('stripe/webhook')) {
|
|
1482
|
-
console.log('[CF Worker] Stripe webhook handler result:', {
|
|
1483
|
-
sent: res._sent,
|
|
1484
|
-
statusCode: res._statusCode,
|
|
1485
|
-
body:
|
|
1486
|
-
typeof res._body === 'string' ? res._body.substring(0, 200) : JSON.stringify(res._body)?.substring(0, 200),
|
|
1487
|
-
});
|
|
1488
|
-
}
|
|
1489
|
-
|
|
1490
|
-
// Ensure all async push() jobs complete before returning
|
|
1491
|
-
await flushPendingJobs();
|
|
1492
|
-
|
|
1493
|
-
if (!res._sent) {
|
|
1494
|
-
console.warn(`[CF Worker] No response sent for ${route.method} ${fullPath}`);
|
|
1495
|
-
return c.json({ error: 'No response from handler' }, 500);
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
|
-
return expressResToResponse(res);
|
|
1499
|
-
});
|
|
1500
|
-
}
|
|
1501
|
-
}
|
|
1502
1320
|
|
|
1503
1321
|
// === Shared env setup for scheduled/queue handlers ===
|
|
1504
1322
|
function setupEnv(env: Env) {
|
|
@@ -1509,21 +1327,13 @@ function setupEnv(env: Env) {
|
|
|
1509
1327
|
// Queue consumer and cron: NOT HTTP context — createEvent must block (listeners complete before ack/return)
|
|
1510
1328
|
(globalThis as any).__cfHttpContext__ = false;
|
|
1511
1329
|
setDB(env.DB.withSession('first-primary'));
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
}
|
|
1520
|
-
if (env.APP_PID) {
|
|
1521
|
-
process.env.BLOCKLET_APP_PID = env.APP_PID;
|
|
1522
|
-
process.env.BLOCKLET_APP_ID = env.APP_PID;
|
|
1523
|
-
}
|
|
1524
|
-
if (env.APP_NAME) process.env.BLOCKLET_APP_NAME = env.APP_NAME;
|
|
1525
|
-
process.env.BLOCKLET_MODE = 'production';
|
|
1526
|
-
}
|
|
1330
|
+
// Phase 12c: assemble the service so the CONFIG SLOT (setCoreConfig) is
|
|
1331
|
+
// authoritative on the scheduled()/queue() paths too. envToPaymentCoreConfig
|
|
1332
|
+
// carries BLOCKLET_MODE + every key the old setupEnv process.env mirror set,
|
|
1333
|
+
// and the core reads them only through libs/env.ts readConfig — so the worker
|
|
1334
|
+
// env mirror is deleted (no more process.env fallback). Idempotent + cached;
|
|
1335
|
+
// also binds models, so ensureModelsInit() is subsumed.
|
|
1336
|
+
ensurePaymentService(env);
|
|
1527
1337
|
|
|
1528
1338
|
// Security init is handled in the per-request middleware (first request only)
|
|
1529
1339
|
}
|
|
@@ -1531,7 +1341,6 @@ function setupEnv(env: Env) {
|
|
|
1531
1341
|
// === Export ===
|
|
1532
1342
|
export default {
|
|
1533
1343
|
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
|
1534
|
-
setWaitUntil((p) => ctx.waitUntil(p));
|
|
1535
1344
|
// Expose waitUntil globally for createEvent to use in HTTP context
|
|
1536
1345
|
(globalThis as any).__cfWaitUntil__ = (p: Promise<any>) => ctx.waitUntil(p);
|
|
1537
1346
|
|
|
@@ -1542,7 +1351,8 @@ export default {
|
|
|
1542
1351
|
async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext) {
|
|
1543
1352
|
setupEnv(env);
|
|
1544
1353
|
ensureCronsInit();
|
|
1545
|
-
|
|
1354
|
+
await ensureTenantBackfill();
|
|
1355
|
+
(globalThis as any).__cfWaitUntil__ = (p: Promise<any>) => ctx.waitUntil(p);
|
|
1546
1356
|
|
|
1547
1357
|
console.log('Scheduled event:', event.cron, 'scheduledTime:', event.scheduledTime);
|
|
1548
1358
|
|
|
@@ -1555,54 +1365,57 @@ export default {
|
|
|
1555
1365
|
// execution crosses a minute boundary, matching on wall-clock would miss
|
|
1556
1366
|
// exact-minute crons like "0 1 * * * *".
|
|
1557
1367
|
await cronInstance.runAll(new Date(event.scheduledTime));
|
|
1558
|
-
|
|
1559
|
-
|
|
1368
|
+
// Phase 12b: the workerd trigger for the node queue engine's due-job
|
|
1369
|
+
// re-dispatch. dispatchDueJobs() runs each scheduled queue's redispatchDue()
|
|
1370
|
+
// (the same cancel-then-replay-from-store body the node loop() runs on a
|
|
1371
|
+
// timer), then flushQueueWork() drains the re-pushed executions before the
|
|
1372
|
+
// isolate freezes.
|
|
1373
|
+
await dispatchDueJobs();
|
|
1374
|
+
await flushQueueWork();
|
|
1560
1375
|
},
|
|
1561
1376
|
|
|
1562
|
-
// CF Queue consumer —
|
|
1377
|
+
// CF Queue consumer — resolves the SAME core queue handle by name and runs
|
|
1378
|
+
// the job through the engine's runJobWithTenant/onJob/retry/cancel path. Under
|
|
1379
|
+
// the canonical node-engine model immediate jobs execute in-isolate via fastq
|
|
1380
|
+
// (nothing is sent to CF Queue), so this consumer is the back-compat path for
|
|
1381
|
+
// any message still on JOB_QUEUE — it must never fork the engine.
|
|
1563
1382
|
async queue(
|
|
1564
1383
|
batch: MessageBatch<{ queueName: string; jobId: string; job: any; persist?: boolean }>,
|
|
1565
1384
|
env: Env,
|
|
1566
|
-
ctx: ExecutionContext
|
|
1385
|
+
ctx: ExecutionContext
|
|
1567
1386
|
) {
|
|
1568
1387
|
setupEnv(env);
|
|
1569
|
-
|
|
1388
|
+
(globalThis as any).__cfWaitUntil__ = (p: Promise<any>) => ctx.waitUntil(p);
|
|
1570
1389
|
|
|
1571
1390
|
console.log(`[queue:consumer] Received batch of ${batch.messages.length} messages`);
|
|
1572
1391
|
|
|
1573
1392
|
for (const msg of batch.messages) {
|
|
1574
|
-
const { queueName, jobId, job
|
|
1393
|
+
const { queueName, jobId, job } = msg.body;
|
|
1575
1394
|
|
|
1576
|
-
const
|
|
1395
|
+
const handle = getQueueHandler(queueName);
|
|
1577
1396
|
|
|
1578
|
-
if (!
|
|
1579
|
-
console.error(`[queue:consumer] No
|
|
1397
|
+
if (!handle) {
|
|
1398
|
+
console.error(`[queue:consumer] No core queue registered for "${queueName}", acking message`);
|
|
1580
1399
|
msg.ack();
|
|
1581
1400
|
continue;
|
|
1582
1401
|
}
|
|
1583
1402
|
|
|
1584
|
-
// persist defaults to true for backward compatibility and for direct
|
|
1585
|
-
// push() immediate jobs (where addJob wrote the row, so we must delete
|
|
1586
|
-
// it after onJob succeeds).
|
|
1587
|
-
//
|
|
1588
|
-
// Scheduled dispatches from runAllScheduledJobs set persist=false —
|
|
1589
|
-
// the dispatcher already deleted the D1 row before sending, and
|
|
1590
|
-
// onJob may have re-pushed a fresh row with the same id; deleting
|
|
1591
|
-
// again would wipe out that new row.
|
|
1592
|
-
const shouldPersist = persist !== false;
|
|
1593
|
-
|
|
1594
1403
|
try {
|
|
1595
|
-
console.log(`[queue:consumer] Processing ${queueName}:${jobId}
|
|
1596
|
-
|
|
1404
|
+
console.log(`[queue:consumer] Processing ${queueName}:${jobId}`);
|
|
1405
|
+
// persist:false — the row already exists in D1 (the original push wrote
|
|
1406
|
+
// it); re-running inline through pushAndWait executes onJob and clears
|
|
1407
|
+
// the row on success without a duplicate-addJob that would hang.
|
|
1408
|
+
// eslint-disable-next-line no-await-in-loop
|
|
1409
|
+
await handle.pushAndWait({ job, id: jobId, persist: false });
|
|
1597
1410
|
console.log(`[queue:consumer] Completed ${queueName}:${jobId}`);
|
|
1598
1411
|
msg.ack();
|
|
1599
1412
|
} catch (err: any) {
|
|
1600
1413
|
console.error(`[queue:consumer] Failed ${queueName}:${jobId}:`, err?.message || err);
|
|
1601
|
-
// Don't retry via CF Queue — job is in D1,
|
|
1414
|
+
// Don't retry via CF Queue — job is in D1, scheduled() will re-dispatch
|
|
1602
1415
|
msg.ack();
|
|
1603
1416
|
}
|
|
1604
1417
|
}
|
|
1605
1418
|
|
|
1606
|
-
await
|
|
1419
|
+
await flushQueueWork();
|
|
1607
1420
|
},
|
|
1608
1421
|
};
|