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/api/src/index.ts
CHANGED
|
@@ -1,168 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
// Blocklet server shell — Phase 7 (W2-0); express→hono Phase 4.
|
|
2
|
+
//
|
|
3
|
+
// Service assembly lives in ./bootstrap (shared with the dev shell ../dev.ts).
|
|
4
|
+
// This file is the PRODUCTION entry (api/dist/index.js): build the service and
|
|
5
|
+
// LISTEN the hono app via @hono/node-server `serve({ fetch })`, then start
|
|
6
|
+
// background services in the listen callback. Other hosts import
|
|
7
|
+
// @arcblock/payment-service and own listen/lifecycle themselves.
|
|
8
|
+
//
|
|
9
|
+
// Phase 4: the loopback bridge + express app shell are gone — the hono app
|
|
10
|
+
// (service.fetch) serves every route natively (resource domains + DID-Connect +
|
|
11
|
+
// production static/SPA fallback). The dev client + HMR are served by ../dev.ts
|
|
12
|
+
// (a connect shell), never by this production entry.
|
|
8
13
|
import dotenv from 'dotenv-flow';
|
|
9
|
-
import
|
|
10
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
11
|
-
import { CustomError, formatError, getStatusFromError } from '@blocklet/error';
|
|
12
|
-
import { csrf } from '@blocklet/sdk/lib/middlewares';
|
|
13
|
-
import { cdn } from '@blocklet/sdk/lib/middlewares/cdn';
|
|
14
|
-
import { xss } from '@blocklet/xss';
|
|
15
|
-
|
|
16
|
-
import { syncCurrencyLogo } from './crons/currency';
|
|
17
|
-
import crons from './crons/index';
|
|
18
|
-
import { ensureStakedForGas } from './integrations/arcblock/stake';
|
|
19
|
-
import { initResourceHandler } from './integrations/blocklet/resource';
|
|
20
|
-
import { initUserHandler } from './integrations/blocklet/user';
|
|
21
|
-
import { ensureWebhookRegistered } from './integrations/stripe/setup';
|
|
22
|
-
import { handlers } from './libs/auth';
|
|
23
|
-
import logger, { setupAccessLogger } from './libs/logger';
|
|
24
|
-
import { contextMiddleware, ensureI18n } from './libs/middleware';
|
|
25
|
-
import { ensureCreateOverdraftProtectionPrices } from './libs/overdraft-protection';
|
|
26
|
-
import { initEventBroadcast } from './libs/ws';
|
|
27
|
-
import { startCheckoutSessionQueue } from './queues/checkout-session';
|
|
28
|
-
import { startCreditConsumeQueue } from './queues/credit-consume';
|
|
29
|
-
import { startCreditGrantQueue } from './queues/credit-grant';
|
|
30
|
-
import { startReconciliationQueue } from './queues/credit-reconciliation';
|
|
31
|
-
import { startDiscountStatusQueue } from './queues/discount-status';
|
|
32
|
-
import { startEventQueue } from './queues/event';
|
|
33
|
-
import { startExchangeRateHealthQueue } from './queues/exchange-rate-health';
|
|
34
|
-
import { startInvoiceQueue } from './queues/invoice';
|
|
35
|
-
import { startNotificationQueue } from './queues/notification';
|
|
36
|
-
import { startPaymentQueue } from './queues/payment';
|
|
37
|
-
import { startPayoutQueue } from './queues/payout';
|
|
38
|
-
import { startRefundQueue } from './queues/refund';
|
|
39
|
-
import { startUploadBillingInfoListener } from './queues/space';
|
|
40
|
-
import { startSubscriptionQueue } from './queues/subscription';
|
|
41
|
-
import { startTokenTransferQueue } from './queues/token-transfer';
|
|
42
|
-
import { startVendorCommissionQueue } from './queues/vendors/commission';
|
|
43
|
-
import { startVendorFulfillmentQueue } from './queues/vendors/fulfillment';
|
|
44
|
-
import { startCoordinatedFulfillmentQueue } from './queues/vendors/fulfillment-coordinator';
|
|
45
|
-
import routes from './routes';
|
|
46
|
-
import autoRechargeAuthorizationHandlers from './routes/connect/auto-recharge-auth';
|
|
47
|
-
import changePaymentHandlers from './routes/connect/change-payment';
|
|
48
|
-
import changePlanHandlers from './routes/connect/change-plan';
|
|
49
|
-
import collectHandlers from './routes/connect/collect';
|
|
50
|
-
import collectBatchHandlers from './routes/connect/collect-batch';
|
|
51
|
-
import delegationHandlers from './routes/connect/delegation';
|
|
52
|
-
import overdraftProtectionHandlers from './routes/connect/overdraft-protection';
|
|
53
|
-
import payHandlers from './routes/connect/pay';
|
|
54
|
-
import reStakeHandlers from './routes/connect/re-stake';
|
|
55
|
-
import rechargeHandlers from './routes/connect/recharge';
|
|
56
|
-
import rechargeAccountHandlers from './routes/connect/recharge-account';
|
|
57
|
-
import setupHandlers from './routes/connect/setup';
|
|
58
|
-
import subscribeHandlers from './routes/connect/subscribe';
|
|
59
|
-
import changePayerHandlers from './routes/connect/change-payer';
|
|
60
|
-
import { initialize } from './store/models';
|
|
61
|
-
import { sequelize } from './store/sequelize';
|
|
14
|
+
import { serve } from '@hono/node-server';
|
|
62
15
|
|
|
63
16
|
dotenv.config();
|
|
64
17
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
export const app = express();
|
|
68
|
-
setupAccessLogger(app);
|
|
69
|
-
app.set('trust proxy', true);
|
|
70
|
-
app.use(cookieParser());
|
|
71
|
-
app.use((req, res, next) => {
|
|
72
|
-
if (req.originalUrl.startsWith('/api/integrations/stripe/webhook')) {
|
|
73
|
-
next();
|
|
74
|
-
} else {
|
|
75
|
-
express.json({ limit: '1 mb' })(req, res, next);
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
app.use(express.urlencoded({ extended: true, limit: '1 mb' }));
|
|
79
|
-
app.use(cors());
|
|
80
|
-
app.use(xss({ allowedKeys: [] }));
|
|
81
|
-
app.use(csrf());
|
|
82
|
-
app.use(ensureI18n());
|
|
83
|
-
app.use(cdn());
|
|
84
|
-
|
|
85
|
-
const router = express.Router();
|
|
86
|
-
handlers.attach(Object.assign({ app: router }, collectHandlers));
|
|
87
|
-
handlers.attach(Object.assign({ app: router }, collectBatchHandlers));
|
|
88
|
-
handlers.attach(Object.assign({ app: router }, payHandlers));
|
|
89
|
-
handlers.attach(Object.assign({ app: router }, setupHandlers));
|
|
90
|
-
handlers.attach(Object.assign({ app: router }, subscribeHandlers));
|
|
91
|
-
handlers.attach(Object.assign({ app: router }, changePaymentHandlers));
|
|
92
|
-
handlers.attach(Object.assign({ app: router }, changePlanHandlers));
|
|
93
|
-
handlers.attach(Object.assign({ app: router }, rechargeHandlers));
|
|
94
|
-
handlers.attach(Object.assign({ app: router }, rechargeAccountHandlers));
|
|
95
|
-
handlers.attach(Object.assign({ app: router }, delegationHandlers));
|
|
96
|
-
handlers.attach(Object.assign({ app: router }, overdraftProtectionHandlers));
|
|
97
|
-
handlers.attach(Object.assign({ app: router }, reStakeHandlers));
|
|
98
|
-
handlers.attach(Object.assign({ app: router }, autoRechargeAuthorizationHandlers));
|
|
99
|
-
handlers.attach(Object.assign({ app: router }, changePayerHandlers));
|
|
100
|
-
router.use('/api', routes);
|
|
101
|
-
|
|
102
|
-
const isProduction = process.env.BLOCKLET_MODE === 'production';
|
|
103
|
-
|
|
104
|
-
app.use(contextMiddleware);
|
|
105
|
-
app.use(router);
|
|
106
|
-
|
|
107
|
-
if (isProduction) {
|
|
108
|
-
const staticDir = path.resolve(process.env.BLOCKLET_APP_DIR!, 'dist');
|
|
109
|
-
app.use(express.static(staticDir, { maxAge: '30d', index: false }));
|
|
110
|
-
app.use(fallback('index.html', { root: staticDir }));
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
114
|
-
app.use(<ErrorRequestHandler>((err, req, res, _next) => {
|
|
115
|
-
logger.error('handle router error', err);
|
|
116
|
-
if (err instanceof CustomError) {
|
|
117
|
-
res.status(getStatusFromError(err)).json({ error: formatError(err) });
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
if (req.accepts('json')) {
|
|
121
|
-
res.status(500).send({ error: err.message });
|
|
122
|
-
} else {
|
|
123
|
-
res.status(500).send('Something broke!');
|
|
124
|
-
}
|
|
125
|
-
}));
|
|
126
|
-
|
|
127
|
-
const port = parseInt(process.env.BLOCKLET_PORT!, 10);
|
|
128
|
-
|
|
129
|
-
export const server = app.listen(port, (err?: any) => {
|
|
130
|
-
if (err) throw err;
|
|
131
|
-
logger.info(`> payment-kit ready on ${port}`);
|
|
132
|
-
|
|
133
|
-
syncCurrencyLogo();
|
|
134
|
-
ensureCreateOverdraftProtectionPrices();
|
|
135
|
-
startPaymentQueue().then(() => logger.info('payment queue started'));
|
|
136
|
-
startInvoiceQueue().then(() => logger.info('invoice queue started'));
|
|
137
|
-
startSubscriptionQueue().then(() => logger.info('subscription queue started'));
|
|
138
|
-
startEventQueue().then(() => logger.info('event queue started'));
|
|
139
|
-
startPayoutQueue().then(() => logger.info('payout queue started'));
|
|
140
|
-
startVendorCommissionQueue().then(() => logger.info('vendor commission queue started'));
|
|
141
|
-
startVendorFulfillmentQueue().then(() => logger.info('vendor fulfillment queue started'));
|
|
142
|
-
startCoordinatedFulfillmentQueue().then(() => logger.info('coordinated fulfillment queue started'));
|
|
143
|
-
startCheckoutSessionQueue().then(() => logger.info('checkoutSession queue started'));
|
|
144
|
-
startNotificationQueue().then(() => logger.info('notification queue started'));
|
|
145
|
-
startRefundQueue().then(() => logger.info('refund queue started'));
|
|
146
|
-
startCreditConsumeQueue().then(() => logger.info('credit queue started'));
|
|
147
|
-
startCreditGrantQueue().then(() => logger.info('credit grant queue started'));
|
|
148
|
-
startTokenTransferQueue().then(() => logger.info('token transfer queue started'));
|
|
149
|
-
startReconciliationQueue().then(() => logger.info('credit reconciliation queue started'));
|
|
150
|
-
startDiscountStatusQueue().then(() => logger.info('discount status queue started'));
|
|
151
|
-
startExchangeRateHealthQueue();
|
|
152
|
-
logger.info('exchange rate health queue started');
|
|
153
|
-
startUploadBillingInfoListener();
|
|
154
|
-
|
|
155
|
-
if (process.env.BLOCKLET_MODE === 'production') {
|
|
156
|
-
ensureWebhookRegistered().catch(console.error);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
ensureStakedForGas().catch(console.error);
|
|
160
|
-
|
|
161
|
-
crons.init();
|
|
18
|
+
// eslint-disable-next-line import/first
|
|
19
|
+
import { buildService, onListening } from './bootstrap';
|
|
162
20
|
|
|
163
|
-
|
|
21
|
+
const { service, port } = buildService();
|
|
164
22
|
|
|
165
|
-
|
|
23
|
+
// @hono/node-server serve(): the listeningListener is the exact equivalent of the
|
|
24
|
+
// old express app.listen(port, cb) — it fires once the socket is bound, which is
|
|
25
|
+
// where background services start. serve() returns the underlying Node Server
|
|
26
|
+
// (used by dev tooling for HMR upgrades and by lifecycle teardown).
|
|
27
|
+
export const server = serve({ fetch: service.fetch, port }, (info) => onListening(service, info.port));
|
|
166
28
|
|
|
167
|
-
|
|
168
|
-
});
|
|
29
|
+
export { service };
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
// Tests replace this class via jest mocks.
|
|
19
19
|
|
|
20
20
|
import { config as configApple, validate as validateAppleReceipt } from 'node-apple-receipt-verify';
|
|
21
|
+
import { appStoreWriteEnabled } from '../../libs/env';
|
|
21
22
|
|
|
22
23
|
import logger from '../../libs/logger';
|
|
23
24
|
import {
|
|
@@ -112,8 +113,6 @@ export type AppStoreSettings = {
|
|
|
112
113
|
private_key_pem?: string;
|
|
113
114
|
};
|
|
114
115
|
|
|
115
|
-
const WRITE_ENABLED = process.env.APP_STORE_WRITE_ENABLED === 'true';
|
|
116
|
-
|
|
117
116
|
export class AppStoreClient {
|
|
118
117
|
declare readonly bundleId: string;
|
|
119
118
|
|
|
@@ -348,7 +347,7 @@ export class AppStoreClient {
|
|
|
348
347
|
/** Refund — Apple actually doesn't let third parties refund subscriptions; this is here for symmetry with GooglePlayClient. */
|
|
349
348
|
// eslint-disable-next-line class-methods-use-this, require-await
|
|
350
349
|
public async refundSubscription(originalTransactionId: string): Promise<void> {
|
|
351
|
-
if (!
|
|
350
|
+
if (!appStoreWriteEnabled()) {
|
|
352
351
|
throw new Error(
|
|
353
352
|
`refundSubscription(${originalTransactionId}) is gated behind APP_STORE_WRITE_ENABLED env. Note: Apple Server API has no third-party refund endpoint — refunds must be requested by the end user via reportProblem.apple.com or by Apple support.`
|
|
354
353
|
);
|
|
@@ -359,7 +358,7 @@ export class AppStoreClient {
|
|
|
359
358
|
/** Cancel — same caveat as refund: Apple expects the user to cancel via App Store > Subscriptions. */
|
|
360
359
|
// eslint-disable-next-line class-methods-use-this, require-await
|
|
361
360
|
public async cancelSubscription(originalTransactionId: string): Promise<void> {
|
|
362
|
-
if (!
|
|
361
|
+
if (!appStoreWriteEnabled()) {
|
|
363
362
|
throw new Error(
|
|
364
363
|
`cancelSubscription(${originalTransactionId}) is gated behind APP_STORE_WRITE_ENABLED env. Apple does not expose a server-initiated cancel — the user must cancel from their device.`
|
|
365
364
|
);
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// `transactionId` changes per charge. We store both in payment_details for
|
|
11
11
|
// audit, but uniqueness/de-dup keys off originalTransactionId.
|
|
12
12
|
|
|
13
|
-
import { createEvent } from '../../../libs/audit';
|
|
13
|
+
import { createEvent, reportAuditFailure } from '../../../libs/audit';
|
|
14
14
|
import logger from '../../../libs/logger';
|
|
15
15
|
import { Customer, PaymentMethod, Price, Subscription, SubscriptionItem } from '../../../store/models';
|
|
16
16
|
import {
|
|
@@ -156,7 +156,7 @@ export async function ingestVerifiedAppStorePurchase({
|
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
await existing.update(updatePatch);
|
|
159
|
-
createEvent('Subscription', 'customer.subscription.started', existing).catch(
|
|
159
|
+
createEvent('Subscription', 'customer.subscription.started', existing).catch(reportAuditFailure);
|
|
160
160
|
logger.info('app_store verify: reactivated lapsed subscription from fresh transaction', {
|
|
161
161
|
subscriptionId: existing.id,
|
|
162
162
|
originalTransactionId: transaction.originalTransactionId,
|
|
@@ -331,7 +331,7 @@ export async function ingestVerifiedAppStorePurchase({
|
|
|
331
331
|
} as any);
|
|
332
332
|
}
|
|
333
333
|
|
|
334
|
-
createEvent('Subscription', 'customer.subscription.started', subscription).catch(
|
|
334
|
+
createEvent('Subscription', 'customer.subscription.started', subscription).catch(reportAuditFailure);
|
|
335
335
|
logger.info('app_store verify: subscription created', {
|
|
336
336
|
subscriptionId: subscription.id,
|
|
337
337
|
customerId: customer.id,
|
|
@@ -545,7 +545,7 @@ async function handleAppStoreSubscribed({
|
|
|
545
545
|
pending_invoice_item_interval: { interval: 'month', interval_count: 1 } as any,
|
|
546
546
|
} as any);
|
|
547
547
|
|
|
548
|
-
createEvent('Subscription', 'customer.subscription.started', subscription).catch(
|
|
548
|
+
createEvent('Subscription', 'customer.subscription.started', subscription).catch(reportAuditFailure);
|
|
549
549
|
logger.info('app_store SUBSCRIBED — subscription created from webhook', {
|
|
550
550
|
subscriptionId: subscription.id,
|
|
551
551
|
customerId: customer.id,
|
|
@@ -574,13 +574,13 @@ async function handleAppStoreRenewed(
|
|
|
574
574
|
},
|
|
575
575
|
},
|
|
576
576
|
});
|
|
577
|
-
createEvent('Subscription', 'customer.subscription.started', subscription).catch(
|
|
577
|
+
createEvent('Subscription', 'customer.subscription.started', subscription).catch(reportAuditFailure);
|
|
578
578
|
}
|
|
579
579
|
|
|
580
580
|
async function markAppStoreExpired(subscription: Subscription): Promise<void> {
|
|
581
581
|
if (['canceled', 'incomplete_expired'].includes(subscription.status as string)) return;
|
|
582
582
|
await subscription.update({ status: 'canceled', ended_at: Math.floor(Date.now() / 1000) });
|
|
583
|
-
createEvent('Subscription', 'customer.subscription.deleted', subscription).catch(
|
|
583
|
+
createEvent('Subscription', 'customer.subscription.deleted', subscription).catch(reportAuditFailure);
|
|
584
584
|
}
|
|
585
585
|
|
|
586
586
|
async function markAppStorePastDue(subscription: Subscription): Promise<void> {
|
|
@@ -631,5 +631,5 @@ async function handleAppStoreRevoked(
|
|
|
631
631
|
refunded: isRefund,
|
|
632
632
|
});
|
|
633
633
|
|
|
634
|
-
createEvent('Subscription', 'customer.subscription.deleted', subscription).catch(
|
|
634
|
+
createEvent('Subscription', 'customer.subscription.deleted', subscription).catch(reportAuditFailure);
|
|
635
635
|
}
|
|
@@ -26,6 +26,7 @@ import type {
|
|
|
26
26
|
} from '@apple/app-store-server-library';
|
|
27
27
|
|
|
28
28
|
import logger from '../../libs/logger';
|
|
29
|
+
import { appStoreSkipSignatureVerify, isProduction } from '../../libs/env';
|
|
29
30
|
import { APPLE_ROOT_CERTS } from './apple-root-certs';
|
|
30
31
|
|
|
31
32
|
const verifierCache = new Map<string, SignedDataVerifier>();
|
|
@@ -46,13 +47,13 @@ async function getVerifier(bundleId: string, environment: 'production' | 'sandbo
|
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
export function isSignatureVerificationSkipped(): boolean {
|
|
49
|
-
if (
|
|
50
|
+
if (!appStoreSkipSignatureVerify()) return false;
|
|
50
51
|
// Production fail-closed: even when the bypass flag is set we refuse to
|
|
51
52
|
// honor it in production, and log loudly so the misconfiguration is
|
|
52
53
|
// visible. The flag exists for unit tests / local sandbox debugging where
|
|
53
54
|
// the synthetic JWS isn't signed by Apple; in production it would silently
|
|
54
55
|
// downgrade a critical trust boundary to decode-only (CWE-347).
|
|
55
|
-
if (
|
|
56
|
+
if (isProduction()) {
|
|
56
57
|
logger.error(
|
|
57
58
|
'app_store: APP_STORE_SKIP_SIGNATURE_VERIFY=true ignored in production — JWS signature verification stays enabled'
|
|
58
59
|
);
|
|
@@ -5,16 +5,30 @@ import { BN, fromTokenToUnit, fromUnitToToken, toBN, toBase58, toBase64 } from '
|
|
|
5
5
|
import { types } from '@ocap/mcrypto';
|
|
6
6
|
import { verify as verifyVC } from '@arcblock/vc';
|
|
7
7
|
import { BlockletService } from '@blocklet/sdk/service/blocklet';
|
|
8
|
+
import env, { blockletAppHost } from '../../libs/env';
|
|
8
9
|
|
|
9
10
|
import { PaymentMethod } from '../../store/models';
|
|
10
11
|
import type { TPaymentCurrency } from '../../store/models';
|
|
11
12
|
import { wallet } from '../../libs/auth';
|
|
12
13
|
import logger from '../../libs/logger';
|
|
13
|
-
import env from '../../libs/env';
|
|
14
14
|
import { sleep } from '../../libs/util';
|
|
15
15
|
import { getGasPayerExtra } from '../../libs/payment';
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
// Lazy singleton: `new BlockletService()` runs `checkBlockletEnvironment()`,
|
|
18
|
+
// which throws unless BLOCKLET_APP_ID/DID/EK/ABT_NODE_* are present. Those env
|
|
19
|
+
// vars only exist inside a blocklet runtime — when payment-core is embedded in
|
|
20
|
+
// a host like arc, constructing this at module-init crashes the whole graph on
|
|
21
|
+
// import. Defer to first use so the import is environment-agnostic; the routing
|
|
22
|
+
// -rule feature (the only consumer) simply requires the host to supply that env
|
|
23
|
+
// when it is actually exercised.
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention -- intentional _-prefixed module singleton
|
|
25
|
+
let _blockletService: InstanceType<typeof BlockletService> | undefined;
|
|
26
|
+
function getBlockletService(): InstanceType<typeof BlockletService> {
|
|
27
|
+
if (!_blockletService) {
|
|
28
|
+
_blockletService = new BlockletService();
|
|
29
|
+
}
|
|
30
|
+
return _blockletService;
|
|
31
|
+
}
|
|
18
32
|
|
|
19
33
|
export function isOnchainCredit(paymentCurrency: TPaymentCurrency) {
|
|
20
34
|
return paymentCurrency.type === 'credit' && !!paymentCurrency.token_config;
|
|
@@ -154,7 +168,7 @@ async function createTokenVC(data: { tokenAddress: string; symbol: string; websi
|
|
|
154
168
|
* Step 2: Publish VC (Verifiable Credential) for token via routing rule
|
|
155
169
|
*/
|
|
156
170
|
async function publishTokenVC(vc: any) {
|
|
157
|
-
const { blocklet: blockletInfo } = await
|
|
171
|
+
const { blocklet: blockletInfo } = await getBlockletService().getBlocklet();
|
|
158
172
|
const site = blockletInfo?.site;
|
|
159
173
|
|
|
160
174
|
if (!site?.id) {
|
|
@@ -226,7 +240,7 @@ async function publishTokenVC(vc: any) {
|
|
|
226
240
|
try {
|
|
227
241
|
let result;
|
|
228
242
|
if (isUpdate) {
|
|
229
|
-
result = await
|
|
243
|
+
result = await getBlockletService().updateRoutingRule({
|
|
230
244
|
id: site.id,
|
|
231
245
|
rule: {
|
|
232
246
|
id: existingRule.id,
|
|
@@ -234,7 +248,7 @@ async function publishTokenVC(vc: any) {
|
|
|
234
248
|
},
|
|
235
249
|
});
|
|
236
250
|
} else {
|
|
237
|
-
result = await
|
|
251
|
+
result = await getBlockletService().addRoutingRule({
|
|
238
252
|
id: site.id,
|
|
239
253
|
rule,
|
|
240
254
|
});
|
|
@@ -279,7 +293,7 @@ export async function createToken(data: { name: string; symbol: string; decimal?
|
|
|
279
293
|
name: data.name,
|
|
280
294
|
symbol: data.symbol,
|
|
281
295
|
description: `Token created by ${env.appName || 'Payment Kit'}`,
|
|
282
|
-
website: env.appUrl ||
|
|
296
|
+
website: env.appUrl || blockletAppHost(),
|
|
283
297
|
icon: '',
|
|
284
298
|
maxTotalSupply: null,
|
|
285
299
|
decimal: data.decimal ?? 10,
|
|
@@ -307,7 +321,7 @@ export async function createToken(data: { name: string; symbol: string; decimal?
|
|
|
307
321
|
const vc = await createTokenVC({
|
|
308
322
|
tokenAddress: factoryItx.token.address,
|
|
309
323
|
symbol: data.symbol,
|
|
310
|
-
website: env.appUrl ||
|
|
324
|
+
website: env.appUrl || blockletAppHost()!,
|
|
311
325
|
chainId,
|
|
312
326
|
});
|
|
313
327
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { Op } from 'sequelize';
|
|
9
9
|
|
|
10
|
-
import { createEvent } from '../../../libs/audit';
|
|
10
|
+
import { createEvent, reportAuditFailure } from '../../../libs/audit';
|
|
11
11
|
import logger from '../../../libs/logger';
|
|
12
12
|
import { Customer, PaymentMethod, Price, Subscription, SubscriptionItem } from '../../../store/models';
|
|
13
13
|
import { GooglePlayClient, GooglePlaySubscriptionPurchase } from '../client';
|
|
@@ -268,12 +268,12 @@ async function handleRenewedOrDeferred({
|
|
|
268
268
|
},
|
|
269
269
|
},
|
|
270
270
|
});
|
|
271
|
-
createEvent('Subscription', 'customer.subscription.started', subscription).catch(
|
|
271
|
+
createEvent('Subscription', 'customer.subscription.started', subscription).catch(reportAuditFailure);
|
|
272
272
|
}
|
|
273
273
|
|
|
274
274
|
async function handleResumed(subscription: Subscription): Promise<void> {
|
|
275
275
|
await subscription.update({ status: 'active' });
|
|
276
|
-
createEvent('Subscription', 'customer.subscription.started', subscription).catch(
|
|
276
|
+
createEvent('Subscription', 'customer.subscription.started', subscription).catch(reportAuditFailure);
|
|
277
277
|
}
|
|
278
278
|
|
|
279
279
|
async function markPastDue(subscription: Subscription): Promise<void> {
|
|
@@ -293,7 +293,7 @@ async function scheduleCancelAtPeriodEnd(subscription: Subscription): Promise<vo
|
|
|
293
293
|
async function markExpired(subscription: Subscription): Promise<void> {
|
|
294
294
|
if (['canceled', 'incomplete_expired'].includes(subscription.status as string)) return;
|
|
295
295
|
await subscription.update({ status: 'canceled', canceled_at: Math.floor(Date.now() / 1000) });
|
|
296
|
-
createEvent('Subscription', 'customer.subscription.deleted', subscription).catch(
|
|
296
|
+
createEvent('Subscription', 'customer.subscription.deleted', subscription).catch(reportAuditFailure);
|
|
297
297
|
}
|
|
298
298
|
|
|
299
299
|
/**
|
|
@@ -526,7 +526,7 @@ export async function ingestVerifiedGooglePlayPurchase({
|
|
|
526
526
|
return { subscription: winner, isFirstSubscribe: false, purchase };
|
|
527
527
|
}
|
|
528
528
|
|
|
529
|
-
createEvent('Subscription', 'customer.subscription.started', subscription).catch(
|
|
529
|
+
createEvent('Subscription', 'customer.subscription.started', subscription).catch(reportAuditFailure);
|
|
530
530
|
logger.info('google_play verify: subscription created', {
|
|
531
531
|
subscriptionId: subscription.id,
|
|
532
532
|
customerId: customer.id,
|
|
@@ -561,5 +561,5 @@ async function handleRevoked(subscription: Subscription): Promise<void> {
|
|
|
561
561
|
subscriptionId: subscription.id,
|
|
562
562
|
});
|
|
563
563
|
|
|
564
|
-
createEvent('Subscription', 'customer.subscription.deleted', subscription).catch(
|
|
564
|
+
createEvent('Subscription', 'customer.subscription.deleted', subscription).catch(reportAuditFailure);
|
|
565
565
|
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// refundType: 1 = FULL, 2 = QUANTITY_BASED
|
|
12
12
|
// }
|
|
13
13
|
|
|
14
|
-
import { createEvent } from '../../../libs/audit';
|
|
14
|
+
import { createEvent, reportAuditFailure } from '../../../libs/audit';
|
|
15
15
|
import logger from '../../../libs/logger';
|
|
16
16
|
import { Subscription } from '../../../store/models';
|
|
17
17
|
|
|
@@ -91,7 +91,7 @@ export async function handleGooglePlayVoidedPurchase({
|
|
|
91
91
|
google_play_voided_refund_type: refundType,
|
|
92
92
|
},
|
|
93
93
|
});
|
|
94
|
-
createEvent('Subscription', 'customer.subscription.deleted', subscription).catch(
|
|
94
|
+
createEvent('Subscription', 'customer.subscription.deleted', subscription).catch(reportAuditFailure);
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
// TODO: create a Refund row for audit. Blocked on payment_intent_id schema —
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// Cloudflare Workers(payment-kit 是统一代码)。
|
|
10
10
|
|
|
11
11
|
import logger from '../../libs/logger';
|
|
12
|
+
import { googlePubsubSkipSignatureVerify, isProduction } from '../../libs/env';
|
|
12
13
|
|
|
13
14
|
export type PubSubJwtClaims = {
|
|
14
15
|
iss: string;
|
|
@@ -80,7 +81,7 @@ export type VerifyOptions = {
|
|
|
80
81
|
};
|
|
81
82
|
|
|
82
83
|
function defaultSkipSignature(): boolean {
|
|
83
|
-
return
|
|
84
|
+
return googlePubsubSkipSignatureVerify();
|
|
84
85
|
}
|
|
85
86
|
|
|
86
87
|
/**
|
|
@@ -93,7 +94,7 @@ function defaultSkipSignature(): boolean {
|
|
|
93
94
|
*/
|
|
94
95
|
function effectiveSkipSignature(requested: boolean): boolean {
|
|
95
96
|
if (!requested) return false;
|
|
96
|
-
if (
|
|
97
|
+
if (isProduction()) {
|
|
97
98
|
logger.error(
|
|
98
99
|
'google_play: signature verification skip refused in production — Pub/Sub JWT signature verification stays enabled'
|
|
99
100
|
);
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
// every 5 minutes by default; tuneable via `IAP_RECONCILE_CRON_TIME`.
|
|
21
21
|
|
|
22
22
|
import { Op } from 'sequelize';
|
|
23
|
+
import { iapReconcileBatchSize } from '../libs/env';
|
|
23
24
|
|
|
24
25
|
import { createEvent } from '../libs/audit';
|
|
25
26
|
import logger from '../libs/logger';
|
|
@@ -30,9 +31,6 @@ import { GooglePlayClient, GooglePlaySubscriptionPurchase } from './google-play/
|
|
|
30
31
|
/** Don't re-check subs that were updated by a webhook within the last 5 minutes. */
|
|
31
32
|
const RECENT_UPDATE_GUARD_MS = 5 * 60 * 1000;
|
|
32
33
|
|
|
33
|
-
/** Per-channel batch cap so a single cron tick can't stall on Apple/Google rate limits. */
|
|
34
|
-
const DEFAULT_BATCH_SIZE = Number(process.env.IAP_RECONCILE_BATCH_SIZE ?? '100');
|
|
35
|
-
|
|
36
34
|
type ReconcileStats = { checked: number; updated: number; errors: number };
|
|
37
35
|
|
|
38
36
|
const emptyStats = (): ReconcileStats => ({ checked: 0, updated: 0, errors: 0 });
|
|
@@ -149,7 +147,7 @@ async function backfillMissingSubscriptionItems(): Promise<void> {
|
|
|
149
147
|
// App Store
|
|
150
148
|
// ---------------------------------------------------------------------------
|
|
151
149
|
|
|
152
|
-
export async function reconcileAppStore(batchSize =
|
|
150
|
+
export async function reconcileAppStore(batchSize = iapReconcileBatchSize()): Promise<ReconcileStats> {
|
|
153
151
|
const stats = emptyStats();
|
|
154
152
|
const methods = await PaymentMethod.findAll({ where: { type: 'app_store' } });
|
|
155
153
|
if (methods.length === 0) return stats;
|
|
@@ -293,7 +291,7 @@ export async function applyAppStoreTransactionDrift(
|
|
|
293
291
|
// Google Play
|
|
294
292
|
// ---------------------------------------------------------------------------
|
|
295
293
|
|
|
296
|
-
export async function reconcileGooglePlay(batchSize =
|
|
294
|
+
export async function reconcileGooglePlay(batchSize = iapReconcileBatchSize()): Promise<ReconcileStats> {
|
|
297
295
|
const stats = emptyStats();
|
|
298
296
|
const methods = await PaymentMethod.findAll({ where: { type: 'google_play' } });
|
|
299
297
|
if (methods.length === 0) return stats;
|
|
@@ -6,7 +6,7 @@ import type Stripe from 'stripe';
|
|
|
6
6
|
|
|
7
7
|
import type { WhereOptions } from 'sequelize';
|
|
8
8
|
import { checkUsageReportEmpty } from '../../../libs/subscription';
|
|
9
|
-
import { createEvent } from '../../../libs/audit';
|
|
9
|
+
import { createEvent, reportAuditFailure } from '../../../libs/audit';
|
|
10
10
|
import { getLock } from '../../../libs/lock';
|
|
11
11
|
import logger from '../../../libs/logger';
|
|
12
12
|
import {
|
|
@@ -361,7 +361,7 @@ export async function handleStripeInvoiceCreated(event: TEventExpanded, client:
|
|
|
361
361
|
createEvent('Subscription', 'usage.report.empty', subscription, {
|
|
362
362
|
usageReportStart,
|
|
363
363
|
usageReportEnd,
|
|
364
|
-
}).catch(
|
|
364
|
+
}).catch(reportAuditFailure);
|
|
365
365
|
logger.info('create usage report empty event', {
|
|
366
366
|
subscriptionId: subscription.id,
|
|
367
367
|
usageReportStart,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from '../../../libs/subscription';
|
|
13
13
|
import { CheckoutSession, PaymentMethod, Subscription, TEventExpanded } from '../../../store/models';
|
|
14
14
|
import { getCheckoutSessionSubscriptionIds } from '../../../libs/session';
|
|
15
|
-
import { createEvent } from '../../../libs/audit';
|
|
15
|
+
import { createEvent, reportAuditFailure } from '../../../libs/audit';
|
|
16
16
|
|
|
17
17
|
export async function handleStripeSubscriptionSucceed(subscription: Subscription, status: string) {
|
|
18
18
|
if (!subscription.payment_details?.stripe?.subscription_id) {
|
|
@@ -51,9 +51,9 @@ export async function handleStripeSubscriptionSucceed(subscription: Subscription
|
|
|
51
51
|
|
|
52
52
|
await subscription.update({ status });
|
|
53
53
|
if (subscription.trial_end && subscription.trial_end > Date.now() / 1000 && subscription.status === 'trialing') {
|
|
54
|
-
createEvent('Subscription', 'customer.subscription.trial_start', subscription).catch(
|
|
54
|
+
createEvent('Subscription', 'customer.subscription.trial_start', subscription).catch(reportAuditFailure);
|
|
55
55
|
} else if (subscription.status === 'active') {
|
|
56
|
-
createEvent('Subscription', 'customer.subscription.started', subscription).catch(
|
|
56
|
+
createEvent('Subscription', 'customer.subscription.started', subscription).catch(reportAuditFailure);
|
|
57
57
|
}
|
|
58
58
|
logger.info('subscription become active on stripe event', { id: subscription.id, status: subscription.status });
|
|
59
59
|
|
|
@@ -5,9 +5,14 @@ import path from 'path';
|
|
|
5
5
|
import { QueryTypes, Op } from 'sequelize';
|
|
6
6
|
|
|
7
7
|
import logger from '../logger';
|
|
8
|
+
import { getInstanceDid } from '../context';
|
|
9
|
+
import { getTenantMode } from '../tenant';
|
|
10
|
+
import { TENANT_TABLES } from '../../store/tenant-tables';
|
|
8
11
|
import { ArchiveMetadata } from '../../store/models/archive-metadata';
|
|
9
12
|
import { listArchiveFiles, openArchiveSequelize } from './store';
|
|
10
13
|
|
|
14
|
+
const TENANT_TABLE_SET: ReadonlySet<string> = new Set(TENANT_TABLES);
|
|
15
|
+
|
|
11
16
|
type ArchiveQueryParams = {
|
|
12
17
|
table: string;
|
|
13
18
|
id?: string;
|
|
@@ -52,6 +57,20 @@ function buildWhereClause(params: ArchiveQueryParams) {
|
|
|
52
57
|
replacements.customerId = params.customer_id;
|
|
53
58
|
}
|
|
54
59
|
|
|
60
|
+
// 洞 G (Phase 4): archived rows are snapshots of tenant tables — scope the
|
|
61
|
+
// read by instance_did so an admin can't query another tenant's archive.
|
|
62
|
+
// multi mode is strict (fail-closed); single mode also accepts legacy
|
|
63
|
+
// pre-backfill snapshots (instance_did NULL), which all belong to the one
|
|
64
|
+
// deployment tenant (mirrors the resolveRowTenant single-mode default).
|
|
65
|
+
if (TENANT_TABLE_SET.has(params.table)) {
|
|
66
|
+
replacements.instance_did = getInstanceDid();
|
|
67
|
+
if (getTenantMode() === 'single') {
|
|
68
|
+
conditions.push('(instance_did = :instance_did OR instance_did IS NULL)');
|
|
69
|
+
} else {
|
|
70
|
+
conditions.push('instance_did = :instance_did');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
55
74
|
return { clause: conditions.join(' AND '), replacements };
|
|
56
75
|
}
|
|
57
76
|
|