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
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// Phase 3 (W1′) — TenantModel landed on the REAL models.
|
|
2
|
+
//
|
|
3
|
+
// The spike (tenant-model-spike.spec.ts) proved the mechanism on controlled
|
|
4
|
+
// same-named tables against both engines. This proves the PRODUCTION wiring:
|
|
5
|
+
// the real Coupon class (with its init/associations/hooks) extends TenantModel
|
|
6
|
+
// and is transparently tenant-scoped across the full 6-class matrix.
|
|
7
|
+
import { Sequelize } from 'sequelize';
|
|
8
|
+
|
|
9
|
+
import { withTenant } from '../../src/libs/context';
|
|
10
|
+
import { TENANT_MISMATCH } from '../../src/libs/tenant';
|
|
11
|
+
import { Coupon, initialize } from '../../src/store/models';
|
|
12
|
+
import { isTenantTable, scopeWhere, stampTenant } from '../../src/store/scoped-core';
|
|
13
|
+
import { TENANT_A, TENANT_B } from '../fixtures/tenants';
|
|
14
|
+
|
|
15
|
+
const sequelize = new Sequelize('sqlite::memory:', { logging: false });
|
|
16
|
+
initialize(sequelize);
|
|
17
|
+
|
|
18
|
+
beforeAll(async () => {
|
|
19
|
+
await sequelize.sync({ force: true });
|
|
20
|
+
});
|
|
21
|
+
afterAll(() => sequelize.close());
|
|
22
|
+
|
|
23
|
+
beforeEach(async () => {
|
|
24
|
+
// truncate between tests so counts are deterministic
|
|
25
|
+
await sequelize.query('DELETE FROM coupons');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
function makeCoupon(overrides: Record<string, any> = {}) {
|
|
29
|
+
return {
|
|
30
|
+
livemode: false,
|
|
31
|
+
duration: 'once',
|
|
32
|
+
name: 'spring-sale',
|
|
33
|
+
created_via: 'api',
|
|
34
|
+
...overrides,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe('TenantModel on the real Coupon model', () => {
|
|
39
|
+
describe('Happy path', () => {
|
|
40
|
+
it('findAll under tenant A returns only A rows; create stamps instance_did', async () => {
|
|
41
|
+
await withTenant(TENANT_A, () => Coupon.create(makeCoupon({ name: 'a-1' }) as any));
|
|
42
|
+
await withTenant(TENANT_B, () => Coupon.create(makeCoupon({ name: 'b-1' }) as any));
|
|
43
|
+
|
|
44
|
+
const aRows = await withTenant(TENANT_A, () => Coupon.findAll());
|
|
45
|
+
expect(aRows.map((r: any) => r.name)).toEqual(['a-1']);
|
|
46
|
+
expect(aRows.every((r: any) => r.instance_did === TENANT_A)).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('Bad input', () => {
|
|
51
|
+
it('explicit conflicting where.instance_did fails closed (reject, not resolve)', async () => {
|
|
52
|
+
await expect(
|
|
53
|
+
withTenant(TENANT_A, () => Coupon.findOne({ where: { instance_did: TENANT_B } as any }))
|
|
54
|
+
).rejects.toMatchObject({ code: TENANT_MISMATCH });
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('undefined / empty where does not crash', async () => {
|
|
58
|
+
await withTenant(TENANT_A, () => Coupon.create(makeCoupon() as any));
|
|
59
|
+
await expect(withTenant(TENANT_A, () => Coupon.findAll())).resolves.toBeDefined();
|
|
60
|
+
await expect(withTenant(TENANT_A, () => Coupon.findOne({}))).resolves.toBeDefined();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('Security', () => {
|
|
65
|
+
it('prototype-pollution keys in a where payload do not poison scope injection', async () => {
|
|
66
|
+
// scopeWhere spreads the caller where into a fresh object; a malicious
|
|
67
|
+
// __proto__ own-key must not leak onto Object.prototype.
|
|
68
|
+
const malicious = JSON.parse('{"__proto__": {"polluted": true}, "name": "x"}');
|
|
69
|
+
const scoped = await withTenant(TENANT_A, async () => scopeWhere(malicious));
|
|
70
|
+
expect(({} as any).polluted).toBeUndefined();
|
|
71
|
+
expect(scoped.instance_did).toBe(TENANT_A);
|
|
72
|
+
expect(scoped.name).toBe('x');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('create cannot stamp a foreign tenant via explicit instance_did', async () => {
|
|
76
|
+
await expect(
|
|
77
|
+
withTenant(TENANT_A, () => Coupon.create(makeCoupon({ instance_did: TENANT_B }) as any))
|
|
78
|
+
).rejects.toMatchObject({ code: TENANT_MISMATCH });
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe('Data loss', () => {
|
|
83
|
+
it('update/destroy never cross tenants', async () => {
|
|
84
|
+
await withTenant(TENANT_A, () => Coupon.create(makeCoupon({ name: 'keep' }) as any));
|
|
85
|
+
await withTenant(TENANT_B, () => Coupon.create(makeCoupon({ name: 'victim' }) as any));
|
|
86
|
+
|
|
87
|
+
// A updates "everything" — must not touch B's row
|
|
88
|
+
await withTenant(TENANT_A, () => Coupon.update({ name: 'renamed' }, { where: {} }));
|
|
89
|
+
const bRow = await withTenant(TENANT_B, () => Coupon.findOne());
|
|
90
|
+
expect(bRow!.name).toBe('victim');
|
|
91
|
+
|
|
92
|
+
// A destroys "everything" — B's row survives
|
|
93
|
+
await withTenant(TENANT_A, () => Coupon.destroy({ where: {} }));
|
|
94
|
+
const bCount = await withTenant(TENANT_B, () => Coupon.count());
|
|
95
|
+
const aCount = await withTenant(TENANT_A, () => Coupon.count());
|
|
96
|
+
expect({ aCount, bCount }).toEqual({ aCount: 0, bCount: 1 });
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('Data damage', () => {
|
|
101
|
+
it('roundtrip create -> findByPk preserves unicode name + metadata', async () => {
|
|
102
|
+
const created: any = await withTenant(TENANT_A, () =>
|
|
103
|
+
Coupon.create(makeCoupon({ name: '春季促销 🎉', metadata: { k: 'välue', n: 1 } }) as any)
|
|
104
|
+
);
|
|
105
|
+
const fetched: any = await withTenant(TENANT_A, () => Coupon.findByPk(created.id));
|
|
106
|
+
expect(fetched.name).toBe('春季促销 🎉');
|
|
107
|
+
expect(fetched.metadata).toEqual({ k: 'välue', n: 1 });
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('Data leak', () => {
|
|
112
|
+
it('cross-tenant findByPk returns null; aggregates do not cross', async () => {
|
|
113
|
+
const bRow: any = await withTenant(TENANT_B, () => Coupon.create(makeCoupon({ name: 'b-only' }) as any));
|
|
114
|
+
await withTenant(TENANT_A, () => Coupon.create(makeCoupon({ name: 'a-only' }) as any));
|
|
115
|
+
|
|
116
|
+
// A cannot fetch B's row by its (globally unique) id
|
|
117
|
+
const leaked = await withTenant(TENANT_A, () => Coupon.findByPk(bRow.id));
|
|
118
|
+
expect(leaked).toBeNull();
|
|
119
|
+
|
|
120
|
+
// findAndCountAll count excludes the other tenant
|
|
121
|
+
const { count } = await withTenant(TENANT_A, () => Coupon.findAndCountAll());
|
|
122
|
+
expect(count).toBe(1);
|
|
123
|
+
|
|
124
|
+
const aCount = await withTenant(TENANT_A, () => Coupon.count());
|
|
125
|
+
expect(aCount).toBe(1);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('sum/max aggregates never cross tenants', async () => {
|
|
129
|
+
// A: two coupons (10 + 30 = 40, max 30); B: one coupon (99)
|
|
130
|
+
await withTenant(TENANT_A, () => Coupon.create(makeCoupon({ name: 'a-10', percent_off: 10 }) as any));
|
|
131
|
+
await withTenant(TENANT_A, () => Coupon.create(makeCoupon({ name: 'a-30', percent_off: 30 }) as any));
|
|
132
|
+
await withTenant(TENANT_B, () => Coupon.create(makeCoupon({ name: 'b-99', percent_off: 99 }) as any));
|
|
133
|
+
|
|
134
|
+
const aSum = await withTenant(TENANT_A, () => Coupon.sum('percent_off'));
|
|
135
|
+
const aMax = await withTenant(TENANT_A, () => Coupon.max('percent_off'));
|
|
136
|
+
const bSum = await withTenant(TENANT_B, () => Coupon.sum('percent_off'));
|
|
137
|
+
|
|
138
|
+
// A's aggregates exclude B's 99 entirely
|
|
139
|
+
expect(aSum).toBe(40);
|
|
140
|
+
expect(aMax).toBe(30);
|
|
141
|
+
expect(bSum).toBe(99);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe('scoped-core is engine-agnostic (Phase 3 cross-engine import assertion)', () => {
|
|
147
|
+
// The same scopeWhere/stampTenant/isTenantTable the worker shim imports.
|
|
148
|
+
// No Node-only dependency is required to inject the tenant.
|
|
149
|
+
it('scopeWhere injects instance_did for tenant tables under withTenant', async () => {
|
|
150
|
+
await withTenant(TENANT_A, async () => {
|
|
151
|
+
expect(isTenantTable('coupons')).toBe(true);
|
|
152
|
+
expect(scopeWhere({ name: 'x' })).toEqual({ name: 'x', instance_did: TENANT_A });
|
|
153
|
+
expect(stampTenant({ name: 'x' })).toEqual({ name: 'x', instance_did: TENANT_A });
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('scopeWhere fails closed on a conflicting explicit tenant', async () => {
|
|
158
|
+
await expect(withTenant(TENANT_A, async () => scopeWhere({ instance_did: TENANT_B }))).rejects.toMatchObject({
|
|
159
|
+
code: TENANT_MISMATCH,
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
});
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// Phase 4 (W1′) — residual-hole closure on top of Phase 3's TenantModel.
|
|
2
|
+
//
|
|
3
|
+
// Covers the three Phase 4 deliverables that close the escapes the base class
|
|
4
|
+
// can't reach on its own:
|
|
5
|
+
// - 洞 G: raw sequelize.query on tenant tables is instance_did-guarded.
|
|
6
|
+
// - 洞 H observability: a forged cross-tenant job emits a structured
|
|
7
|
+
// TENANT_VIOLATION alert (not a silent scoped null).
|
|
8
|
+
// - system* security: the cross-tenant bypass is explicit-only — a normal
|
|
9
|
+
// route/tenant context can never read across tenants.
|
|
10
|
+
import { Sequelize } from 'sequelize';
|
|
11
|
+
|
|
12
|
+
import { context, isSystemContext, withTenant } from '../../src/libs/context';
|
|
13
|
+
import { assertJobObjectTenant } from '../../src/libs/queue';
|
|
14
|
+
import { TENANT_CONTEXT_MISSING, TENANT_MISMATCH } from '../../src/libs/tenant';
|
|
15
|
+
import logger from '../../src/libs/logger';
|
|
16
|
+
import { Coupon, MeterEvent, PromotionCode, initialize } from '../../src/store/models';
|
|
17
|
+
import { systemFindByPk } from '../../src/store/scoped';
|
|
18
|
+
import { TENANT_A, TENANT_B } from '../fixtures/tenants';
|
|
19
|
+
|
|
20
|
+
jest.mock('../../src/libs/logger');
|
|
21
|
+
|
|
22
|
+
const sequelize = new Sequelize('sqlite::memory:', { logging: false });
|
|
23
|
+
initialize(sequelize);
|
|
24
|
+
|
|
25
|
+
beforeAll(async () => {
|
|
26
|
+
await sequelize.sync({ force: true });
|
|
27
|
+
});
|
|
28
|
+
afterAll(() => sequelize.close());
|
|
29
|
+
|
|
30
|
+
beforeEach(async () => {
|
|
31
|
+
jest.clearAllMocks();
|
|
32
|
+
await sequelize.query('DELETE FROM meter_events');
|
|
33
|
+
await sequelize.query('DELETE FROM coupons');
|
|
34
|
+
await sequelize.query('DELETE FROM promotion_codes');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('洞 G — raw query instance_did guard (MeterEvent.getEventStats)', () => {
|
|
38
|
+
let seq = 0;
|
|
39
|
+
const seedEvent = (tenant: string, value: number) => {
|
|
40
|
+
seq += 1;
|
|
41
|
+
return withTenant(tenant, () =>
|
|
42
|
+
MeterEvent.create({
|
|
43
|
+
event_name: 'api.calls',
|
|
44
|
+
identifier: `evt-${tenant}-${seq}`,
|
|
45
|
+
timestamp: 1700000000 + seq,
|
|
46
|
+
created_via: 'api',
|
|
47
|
+
livemode: true,
|
|
48
|
+
status: 'completed',
|
|
49
|
+
payload: { value: String(value), customer_id: 'c-1' },
|
|
50
|
+
} as any)
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
it('the raw SUM aggregate never crosses tenants', async () => {
|
|
55
|
+
await seedEvent(TENANT_A, 10);
|
|
56
|
+
await seedEvent(TENANT_A, 30);
|
|
57
|
+
await seedEvent(TENANT_B, 999);
|
|
58
|
+
|
|
59
|
+
const aStats = await withTenant(TENANT_A, () => MeterEvent.getEventStats('api.calls'));
|
|
60
|
+
const bStats = await withTenant(TENANT_B, () => MeterEvent.getEventStats('api.calls'));
|
|
61
|
+
|
|
62
|
+
// A's total_value (raw SUM) excludes B's 999 entirely
|
|
63
|
+
expect(Number(aStats.total_value)).toBe(40);
|
|
64
|
+
expect(aStats.total_events).toBe(2);
|
|
65
|
+
expect(Number(bStats.total_value)).toBe(999);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('Bad input: a guarded read fails closed with no tenant context (multi mode) — never full-scans', async () => {
|
|
69
|
+
// The raw guards bind getInstanceDid() into the WHERE; that same getter is
|
|
70
|
+
// the fail-closed foundation. In multi mode with no withTenant context it
|
|
71
|
+
// throws TENANT_CONTEXT_MISSING, so the guarded query is REFUSED rather than
|
|
72
|
+
// run unbound (which would scan every tenant's rows). Proven here on a single
|
|
73
|
+
// scoped op (no Promise.all fan-out -> no orphaned rejections).
|
|
74
|
+
const saved = process.env.PAYMENT_TENANT_MODE;
|
|
75
|
+
process.env.PAYMENT_TENANT_MODE = 'multi';
|
|
76
|
+
try {
|
|
77
|
+
await expect(Coupon.count()).rejects.toMatchObject({ code: TENANT_CONTEXT_MISSING });
|
|
78
|
+
} finally {
|
|
79
|
+
if (saved === undefined) delete process.env.PAYMENT_TENANT_MODE;
|
|
80
|
+
else process.env.PAYMENT_TENANT_MODE = saved;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('洞 H observability — assertJobObjectTenant emits TENANT_VIOLATION', () => {
|
|
86
|
+
it('a forged cross-tenant job logs a structured violation AND throws', async () => {
|
|
87
|
+
await withTenant(TENANT_A, () => {
|
|
88
|
+
// row belongs to B, job is running under A -> forged
|
|
89
|
+
let thrown: any;
|
|
90
|
+
try {
|
|
91
|
+
assertJobObjectTenant({ instance_did: TENANT_B });
|
|
92
|
+
} catch (err) {
|
|
93
|
+
thrown = err;
|
|
94
|
+
}
|
|
95
|
+
expect(thrown?.code).toBe(TENANT_MISMATCH);
|
|
96
|
+
expect((thrown as any)?.nonRetryable).toBe(true);
|
|
97
|
+
expect(logger.error).toHaveBeenCalledWith(
|
|
98
|
+
'TENANT_VIOLATION',
|
|
99
|
+
expect.objectContaining({ code: TENANT_MISMATCH, rowTenant: TENANT_B, jobTenant: TENANT_A })
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('a same-tenant job neither logs nor throws', async () => {
|
|
105
|
+
await withTenant(TENANT_A, () => {
|
|
106
|
+
expect(() => assertJobObjectTenant({ instance_did: TENANT_A })).not.toThrow();
|
|
107
|
+
expect(logger.error).not.toHaveBeenCalled();
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('an absent object is the handler no-op path (no violation)', async () => {
|
|
112
|
+
await withTenant(TENANT_A, () => {
|
|
113
|
+
expect(() => assertJobObjectTenant(null)).not.toThrow();
|
|
114
|
+
expect(logger.error).not.toHaveBeenCalled();
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('system* security — the bypass is explicit-only', () => {
|
|
120
|
+
it('a normal tenant context is NOT a system context', async () => {
|
|
121
|
+
await withTenant(TENANT_A, () => {
|
|
122
|
+
expect(isSystemContext()).toBe(false);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('a normal context query stays scoped (cannot read cross-tenant)', async () => {
|
|
127
|
+
const bRow: any = await withTenant(TENANT_B, () =>
|
|
128
|
+
Coupon.create({ livemode: false, duration: 'once', name: 'b', created_via: 'api' } as any)
|
|
129
|
+
);
|
|
130
|
+
// bare findByPk under A -> scoped -> null (no leak)
|
|
131
|
+
const leaked = await withTenant(TENANT_A, () => Coupon.findByPk(bRow.id));
|
|
132
|
+
expect(leaked).toBeNull();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('only the system* helper enters runAsSystem; it restores afterwards', async () => {
|
|
136
|
+
const bRow: any = await withTenant(TENANT_B, () =>
|
|
137
|
+
Coupon.create({ livemode: false, duration: 'once', name: 'b2', created_via: 'api' } as any)
|
|
138
|
+
);
|
|
139
|
+
await withTenant(TENANT_A, async () => {
|
|
140
|
+
// inside systemFindByPk the bypass is active -> cross-tenant row loads
|
|
141
|
+
const viaSystem = await systemFindByPk(Coupon, bRow.id);
|
|
142
|
+
expect(viaSystem).not.toBeNull();
|
|
143
|
+
// and the flag is restored the moment the helper settles
|
|
144
|
+
expect(isSystemContext()).toBe(false);
|
|
145
|
+
// so a subsequent bare read is scoped again
|
|
146
|
+
const leaked = await Coupon.findByPk(bRow.id);
|
|
147
|
+
expect(leaked).toBeNull();
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('runAsSystem is reentrant-safe and does not leak across withTenant siblings', async () => {
|
|
152
|
+
let insideFlag = false;
|
|
153
|
+
await context.runAsSystem(async () => {
|
|
154
|
+
insideFlag = isSystemContext();
|
|
155
|
+
});
|
|
156
|
+
expect(insideFlag).toBe(true);
|
|
157
|
+
// outside the span, flag is gone
|
|
158
|
+
expect(isSystemContext()).toBe(false);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('洞 F — eager include transitive safety (scoped root => same-tenant children)', () => {
|
|
163
|
+
// Base-class override does not fire for the JOIN itself, but the ROOT query is
|
|
164
|
+
// scoped, and associations join by globally-unique FK ids that belong to one
|
|
165
|
+
// tenant, so children follow the (scoped) root — no cross-tenant child rows.
|
|
166
|
+
const seedPromo = async (tenant: string, name: string) =>
|
|
167
|
+
withTenant(tenant, async () => {
|
|
168
|
+
const coupon: any = await Coupon.create({
|
|
169
|
+
livemode: false,
|
|
170
|
+
duration: 'once',
|
|
171
|
+
name,
|
|
172
|
+
created_via: 'api',
|
|
173
|
+
} as any);
|
|
174
|
+
return PromotionCode.create({
|
|
175
|
+
livemode: false,
|
|
176
|
+
active: true,
|
|
177
|
+
code: `CODE-${name}`,
|
|
178
|
+
coupon_id: coupon.id,
|
|
179
|
+
} as any);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('findAll with include returns only the active tenant rows + their own coupon', async () => {
|
|
183
|
+
await seedPromo(TENANT_A, 'a');
|
|
184
|
+
await seedPromo(TENANT_B, 'b');
|
|
185
|
+
|
|
186
|
+
const aRows: any = await withTenant(TENANT_A, () =>
|
|
187
|
+
PromotionCode.findAll({ include: [{ model: Coupon, as: 'coupon' }] })
|
|
188
|
+
);
|
|
189
|
+
expect(aRows).toHaveLength(1);
|
|
190
|
+
expect(aRows[0].code).toBe('CODE-a');
|
|
191
|
+
expect(aRows[0].instance_did).toBe(TENANT_A);
|
|
192
|
+
// the eager-loaded child belongs to the same tenant (FK-consistent)
|
|
193
|
+
expect(aRows[0].coupon.instance_did).toBe(TENANT_A);
|
|
194
|
+
expect(aRows[0].coupon.name).toBe('a');
|
|
195
|
+
});
|
|
196
|
+
});
|
package/api/third.d.ts
CHANGED
package/blocklet.yml
CHANGED
|
@@ -104,15 +104,11 @@ sqlite3 $PAYMENT_DB "SELECT name FROM sqlite_master WHERE type='table' AND name
|
|
|
104
104
|
### 0.3 创建 Cloudflare 资源
|
|
105
105
|
|
|
106
106
|
```bash
|
|
107
|
-
# 1. D1
|
|
107
|
+
# 1. D1 数据库(业务数据 + DID Connect token 握手态都在这里)
|
|
108
108
|
wrangler d1 create payment-kit-prod
|
|
109
109
|
# 记录 database_id,填入 wrangler.json
|
|
110
110
|
|
|
111
|
-
# 2.
|
|
112
|
-
wrangler kv namespace create DID_CONNECT_KV
|
|
113
|
-
# 记录 id,填入 wrangler.json
|
|
114
|
-
|
|
115
|
-
# 3. CF Queue(任务队列)
|
|
111
|
+
# 2. CF Queue(任务队列)
|
|
116
112
|
wrangler queues create payment-kit-jobs
|
|
117
113
|
|
|
118
114
|
# 4. Dead Letter Queue(可选,用于失败任务追踪)
|
|
@@ -376,8 +372,7 @@ CF Worker 使用 `_did_connect_tokens` 表(存在 D1 中)而非 KV 存储 DI
|
|
|
376
372
|
以保证强一致性。该表由 schema migration 自动创建,无需从 Blocklet Server 迁移历史 token 数据
|
|
377
373
|
(用户会重新登录)。
|
|
378
374
|
|
|
379
|
-
> 注:
|
|
380
|
-
> KV 可用于其他缓存用途。
|
|
375
|
+
> 注: KV (`DID_CONNECT_KV`) 已彻底移除——token 握手态完全走 D1,路由挂载以 `env.DB` 为前提。
|
|
381
376
|
|
|
382
377
|
---
|
|
383
378
|
|
package/cloudflare/README.md
CHANGED
|
@@ -6,9 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
```
|
|
8
8
|
Payment Kit Worker
|
|
9
|
-
├── D1 Database #
|
|
10
|
-
├──
|
|
11
|
-
├── CF Queue # 任务队列(fastq → CF Queue 适配)
|
|
9
|
+
├── D1 Database # 业务数据 + DID Connect token 握手态(_did_connect_tokens)
|
|
10
|
+
├── CF Queue # 可选 executor 绑定;队列引擎是 api/src/libs/queue(node 引擎)
|
|
12
11
|
├── Hyperdrive # (可选)连接 Postgres,仅在启用时使用
|
|
13
12
|
├── Service Binding
|
|
14
13
|
│ ├── AUTH_SERVICE → blocklet-service (BlockletServiceRPC)
|
|
@@ -20,9 +19,29 @@ Payment Kit Worker
|
|
|
20
19
|
|
|
21
20
|
- **Hono** 替代 Express(路由 + 中间件适配层)
|
|
22
21
|
- **Sequelize-D1 shim** 替代 SQLite,表结构自动 sync
|
|
23
|
-
- **CF Queue** 替代 fastq,加 D1-native cron fallback
|
|
24
22
|
- **wrapAsyncListener** 追踪 `EventEmitter` async listener 的 Promise,防止 CF runtime 提前回收
|
|
25
23
|
|
|
24
|
+
### worker 是 host adapter(Phase 12,2026-06)
|
|
25
|
+
|
|
26
|
+
worker **不再自带**一套业务引擎。它只是一个 host adapter:
|
|
27
|
+
|
|
28
|
+
- **业务 `/api` 入口统一经 `createEmbeddedPaymentService({ config, db, slots })`**,worker 只挂载
|
|
29
|
+
`svc.http.resourceRoutes`,DID-Connect 走 worker 自己的 Hono shell。worker **绝不**访问 `svc.handler`
|
|
30
|
+
(那是 node-only Express app 壳)——`ensurePaymentService` 用 Proxy 把 `.handler` 访问 trap 成 hard error。
|
|
31
|
+
- **队列只有一套引擎** = `api/src/libs/queue`(node 引擎)。worker 不再用已删除的 `shims/queue.ts`。host 只替换
|
|
32
|
+
trigger / executor / flush:executor = `shims/fastq`,trigger = `scheduled()` 调 `dispatchDueJobs()`,
|
|
33
|
+
flush = `flushQueueWork()`,全部经 `api/src/libs/queue/runtime.ts` 这个 host-facing surface 驱动。
|
|
34
|
+
workerd 模式下 node 引擎的后台 `loop()` 被禁用(冻结的 isolate 不能跑后台 timer)。
|
|
35
|
+
- **config 经 config slot 授权**:`envToPaymentCoreConfig(env)` → `setCoreConfig`,HTTP 与 scheduled/queue 两条
|
|
36
|
+
路径都不再有 `process.env` mirror。
|
|
37
|
+
- **构建**:**`run-build.js` 是 standalone 的 CF deploy build**(`node run-build.js` → `wrangler deploy`),
|
|
38
|
+
带全部 bundle 优化(stripe-cf、axios-lite、native fetch、cbor-only、ethers wordlists drop、noop packages、
|
|
39
|
+
node builtin shims;见 `docs/2026-06-10-bundle-size-analysis.md`,gzip ~1 MiB,实测部署 + 支付路由验证过)。
|
|
40
|
+
Phase 12b/12c 只从中移除了两个已死的 plugin:`queue-shim`(option A:队列引擎统一为 `api/src/libs/queue`,
|
|
41
|
+
`shims/queue.ts` 已删)和 `lock-shim`(lock 已改成 Phase 8 driver,`shims/lock.ts` 已删)。
|
|
42
|
+
**`build.ts` 是 diagnostic/backup 构建**(`npx tsx build.ts`,arc-integration 诊断用)——它**不**带上述优化、
|
|
43
|
+
用不同的 node-builtin 策略,**不是** deploy build,部署一律用 `run-build.js`。
|
|
44
|
+
|
|
26
45
|
---
|
|
27
46
|
|
|
28
47
|
## ⚠️ 必须先部署的前置服务
|
|
@@ -82,19 +101,15 @@ Payment Kit Worker → 本指南涵盖的部分
|
|
|
82
101
|
在 Payment Kit 部署前创建以下资源,把生成的 ID 填到 `wrangler.jsonc`:
|
|
83
102
|
|
|
84
103
|
```bash
|
|
85
|
-
# 1. D1
|
|
104
|
+
# 1. D1 数据库(业务数据 + DID Connect token 握手态都在这里)
|
|
86
105
|
wrangler d1 create payment-kit-prod
|
|
87
106
|
# → 记下 database_id
|
|
88
107
|
|
|
89
|
-
# 2.
|
|
90
|
-
wrangler kv namespace create DID_CONNECT_KV
|
|
91
|
-
# → 记下 id
|
|
92
|
-
|
|
93
|
-
# 3. CF Queue + DLQ(任务队列)
|
|
108
|
+
# 2. CF Queue + DLQ(任务队列)
|
|
94
109
|
wrangler queues create payment-kit-jobs
|
|
95
110
|
wrangler queues create payment-kit-jobs-dlq
|
|
96
111
|
|
|
97
|
-
#
|
|
112
|
+
# 3.(可选)Hyperdrive,仅当需要连接外部 Postgres 时
|
|
98
113
|
wrangler hyperdrive create payment-kit-hyperdrive --connection-string "postgres://..."
|
|
99
114
|
```
|
|
100
115
|
|
|
@@ -163,13 +178,6 @@ wrangler hyperdrive create payment-kit-hyperdrive --connection-string "postgres:
|
|
|
163
178
|
}
|
|
164
179
|
],
|
|
165
180
|
|
|
166
|
-
"kv_namespaces": [
|
|
167
|
-
{
|
|
168
|
-
"binding": "DID_CONNECT_KV",
|
|
169
|
-
"id": "<your-kv-namespace-id>"
|
|
170
|
-
}
|
|
171
|
-
],
|
|
172
|
-
|
|
173
181
|
// 可选:启用 Hyperdrive(连接外部 Postgres)
|
|
174
182
|
// "hyperdrive": [
|
|
175
183
|
// { "binding": "HYPERDRIVE", "id": "<your-hyperdrive-id>" }
|
|
@@ -235,12 +243,11 @@ wrangler deployments list --name media-kit
|
|
|
235
243
|
```bash
|
|
236
244
|
cd blocklets/core/cloudflare
|
|
237
245
|
wrangler d1 create payment-kit-prod
|
|
238
|
-
wrangler kv namespace create DID_CONNECT_KV
|
|
239
246
|
wrangler queues create payment-kit-jobs
|
|
240
247
|
wrangler queues create payment-kit-jobs-dlq
|
|
241
248
|
```
|
|
242
249
|
|
|
243
|
-
把返回的 `database_id`
|
|
250
|
+
把返回的 `database_id` 填到 `wrangler.jsonc` 对应位置。
|
|
244
251
|
|
|
245
252
|
### Phase 2:填写 `wrangler.jsonc`
|
|
246
253
|
|
|
@@ -309,7 +316,7 @@ window.blocklet = { appName: 'Payment Kit', appUrl: '...', appPid: '...', ... }
|
|
|
309
316
|
|
|
310
317
|
1. `ensureModelsInit()` — Sequelize-D1 sync,在 D1 里自动建表
|
|
311
318
|
2. `initFromAuthService(env)` — 从 `AUTH_SERVICE.getAppEk(APP_PID)` 拉 EK 并初始化 crypto chain(`PBKDF2 → AES password`)
|
|
312
|
-
3. DID Connect 路由挂载 — `APP_SK` + `
|
|
319
|
+
3. DID Connect 路由挂载 — `APP_SK` + `DB`(D1)都就绪才会挂载
|
|
313
320
|
|
|
314
321
|
查看首请求日志:
|
|
315
322
|
|
|
@@ -456,7 +463,7 @@ wrangler d1 execute payment-kit-prod --remote \
|
|
|
456
463
|
### DID Connect 登录卡在 challenge 阶段
|
|
457
464
|
|
|
458
465
|
- `APP_SK` 必须是 hex 字符串,对应的 public key 必须在 blocklet-service 里注册为该 APP_PID 的 key
|
|
459
|
-
- `
|
|
466
|
+
- `DB`(D1)binding 必须正确:DID Connect token 握手态存在 `_did_connect_tokens` 表,路由挂载也以 `env.DB` 为前提
|
|
460
467
|
|
|
461
468
|
---
|
|
462
469
|
|
|
@@ -465,10 +472,11 @@ wrangler d1 execute payment-kit-prod --remote \
|
|
|
465
472
|
```
|
|
466
473
|
blocklets/core/cloudflare/
|
|
467
474
|
├── worker.ts # Worker 入口(Hono 路由 + Express 适配)
|
|
468
|
-
├── run-build.js # esbuild
|
|
469
|
-
├── build.ts #
|
|
475
|
+
├── run-build.js # standalone CF deploy build(esbuild + 全部 bundle 优化)← 部署用这个
|
|
476
|
+
├── build.ts # diagnostic/backup 构建(更少优化;不是 deploy build)
|
|
477
|
+
├── queue-runtime-mode.ts # 把 core 队列引擎 pin 成 workerd(在业务队列加载前)
|
|
470
478
|
├── vite.config.ts # 前端 Vite 构建配置
|
|
471
|
-
├── did-connect-auth.ts # DID Connect 登录路由(挂载需要 APP_SK +
|
|
479
|
+
├── did-connect-auth.ts # DID Connect 登录路由(挂载需要 APP_SK + DB;token 握手态用 D1 _did_connect_tokens)
|
|
472
480
|
├── wrangler.jsonc # 生产配置模板
|
|
473
481
|
├── wrangler.migration-test.json # 迁移测试环境配置
|
|
474
482
|
├── index.html # 前端开发入口
|
|
@@ -480,8 +488,7 @@ blocklets/core/cloudflare/
|
|
|
480
488
|
│ │ ├── verify-sign.ts # 组件签名验证(委托 AUTH_SERVICE)
|
|
481
489
|
│ │ └── ...
|
|
482
490
|
│ ├── sequelize-d1/ # Sequelize → D1 适配
|
|
483
|
-
│ ├──
|
|
484
|
-
│ ├── lock.ts # 分布式锁(D1 实现)
|
|
491
|
+
│ ├── fastq.ts # fastq executor 替身(队列引擎仍是 api/src/libs/queue)
|
|
485
492
|
│ ├── cron.ts # 定时任务
|
|
486
493
|
│ └── ...
|
|
487
494
|
└── test-aigne-hub/ # Gateway 集成参考实现
|
|
@@ -166,15 +166,11 @@ curl https://<MEDIA_KIT_URL>/
|
|
|
166
166
|
```bash
|
|
167
167
|
cd <payment-kit-repo>/blocklets/core/cloudflare
|
|
168
168
|
|
|
169
|
-
# 4.1 D1
|
|
169
|
+
# 4.1 D1 数据库(业务数据 + DID Connect token 握手态都在这里)
|
|
170
170
|
wrangler d1 create payment-kit-staging
|
|
171
171
|
# 记下返回的 database_id,记为 $D1_ID
|
|
172
172
|
|
|
173
|
-
# 4.2
|
|
174
|
-
wrangler kv namespace create payment-kit-staging-didconnect
|
|
175
|
-
# 记下返回的 id,记为 $KV_ID
|
|
176
|
-
|
|
177
|
-
# 4.3 CF Queue 主队列 + DLQ
|
|
173
|
+
# 4.2 CF Queue 主队列 + DLQ
|
|
178
174
|
wrangler queues create payment-kit-staging-jobs
|
|
179
175
|
wrangler queues create payment-kit-staging-jobs-dlq
|
|
180
176
|
```
|
|
@@ -183,7 +179,6 @@ wrangler queues create payment-kit-staging-jobs-dlq
|
|
|
183
179
|
|
|
184
180
|
```
|
|
185
181
|
D1_ID=<...>
|
|
186
|
-
KV_ID=<...>
|
|
187
182
|
```
|
|
188
183
|
|
|
189
184
|
## Phase 5:Payment Kit — 导出 AWS 数据(20 min)
|
|
@@ -327,13 +322,6 @@ cp wrangler.migration-test.json wrangler.staging.json
|
|
|
327
322
|
}
|
|
328
323
|
],
|
|
329
324
|
|
|
330
|
-
"kv_namespaces": [
|
|
331
|
-
{
|
|
332
|
-
"binding": "DID_CONNECT_KV",
|
|
333
|
-
"id": "<Phase 4.2 的 KV_ID>"
|
|
334
|
-
}
|
|
335
|
-
],
|
|
336
|
-
|
|
337
325
|
"services": [
|
|
338
326
|
{
|
|
339
327
|
"binding": "MEDIA_KIT",
|
|
@@ -529,7 +517,7 @@ wrangler d1 execute payment-kit-staging --remote --command \
|
|
|
529
517
|
**如果 Phase 10~12 任何一步发现致命问题**:
|
|
530
518
|
|
|
531
519
|
1. **停止切流**:staging 还在 `*.workers.dev`,AWS 源端仍然运行,无需回滚 DNS
|
|
532
|
-
2. **保留 CF 资源**:D1 /
|
|
520
|
+
2. **保留 CF 资源**:D1 / Queue 不删,便于复现问题
|
|
533
521
|
3. **修复后重跑**:Phase 5~12 是幂等的(`INSERT OR IGNORE`),可以安全重跑
|
|
534
522
|
4. **确认失败原因**:查 `wrangler tail` 日志 + 对比 AWS 源端行为
|
|
535
523
|
|