payment-kit 1.29.1 → 1.29.2
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 +36 -0
- package/api/src/crons/base.ts +3 -3
- package/api/src/crons/currency.ts +1 -1
- package/api/src/crons/index.ts +27 -24
- 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/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 +99 -38
- package/api/src/libs/context.ts +78 -1
- package/api/src/libs/currency.ts +2 -2
- package/api/src/libs/dayjs.ts +8 -2
- 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 +81 -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 +50 -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 +259 -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 +73 -0
- package/api/src/middlewares/hono/csrf.ts +72 -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 +214 -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 +17 -12
- 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 +12 -4
- 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 +41 -28
- package/api/src/queues/payout.ts +9 -5
- package/api/src/queues/refund.ts +18 -12
- 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} +193 -223
- 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} +96 -80
- 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 +667 -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 +66 -22
- 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/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/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/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 +236 -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/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/README.md +26 -6
- package/cloudflare/build.ts +28 -13
- package/cloudflare/did-connect-auth.ts +0 -217
- 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 +10 -56
- 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/util-csrf.ts +13 -0
- package/cloudflare/shims/blocklet-sdk/util-wallet.ts +8 -0
- 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/tenant-middleware.spec.ts +160 -0
- package/cloudflare/tests/worker-handler-gate.spec.ts +44 -0
- package/cloudflare/worker.ts +204 -433
- package/cloudflare/wrangler.local-e2e.jsonc +26 -0
- package/jest.config.js +3 -1
- package/package.json +33 -38
- 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/env.d.ts +13 -1
- package/tsconfig.json +1 -1
- 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/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/dev.ts
CHANGED
|
@@ -1,10 +1,49 @@
|
|
|
1
|
+
// Dev shell — express→hono Phase 4. Dev-only (NOT bundled into production:
|
|
2
|
+
// tsconfig.api excludes api/dev.ts; the production entry is ./src → index.ts).
|
|
3
|
+
//
|
|
4
|
+
// Hosts the Vite client + HMR on a `connect` app and routes backend paths to the
|
|
5
|
+
// hono service. `vite-plugin-blocklet`'s setupClient only knows `app.use(vite
|
|
6
|
+
// .middlewares)`, so the dev shell hosts Vite on connect (NOT express — core is
|
|
7
|
+
// express-free). Backend paths are routed to service.fetch EXPLICITLY by prefix:
|
|
8
|
+
// a Web-Fetch 404 cannot call connect next(), so hono must never be the trailing
|
|
9
|
+
// catch-all (that would swallow client routes the SPA fallback should serve).
|
|
10
|
+
import { createServer } from 'http';
|
|
11
|
+
|
|
12
|
+
import { getRequestListener } from '@hono/node-server';
|
|
13
|
+
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
14
|
+
import connect from 'connect';
|
|
1
15
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
2
16
|
import { setupClient } from 'vite-plugin-blocklet';
|
|
3
17
|
|
|
4
|
-
import {
|
|
18
|
+
import { buildService, onListening } from './src/bootstrap';
|
|
19
|
+
|
|
20
|
+
const { service, port } = buildService();
|
|
21
|
+
|
|
22
|
+
// Hand backend paths to the hono app; everything else falls through to the Vite
|
|
23
|
+
// middleware setupClient() mounts after this router.
|
|
24
|
+
const honoListener = getRequestListener(service.fetch);
|
|
25
|
+
const isBackendPath = (url: string): boolean => {
|
|
26
|
+
const pathname = url.split('?')[0] || '';
|
|
27
|
+
return pathname === '/api' || pathname.startsWith('/api/') || pathname.startsWith('/.well-known/');
|
|
28
|
+
};
|
|
5
29
|
|
|
30
|
+
const app = connect();
|
|
31
|
+
app.use((req, res, next) => {
|
|
32
|
+
if (isBackendPath(req.url || '')) {
|
|
33
|
+
honoListener(req, res);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
next();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const server = createServer(app);
|
|
40
|
+
|
|
41
|
+
// setupClient appends the Vite connect middleware (client assets + HMR + SPA
|
|
42
|
+
// fallback) AFTER our backend router, and wires the HMR ws upgrade onto `server`.
|
|
6
43
|
setupClient(app, {
|
|
7
44
|
server,
|
|
8
|
-
// @ts-ignore
|
|
45
|
+
// @ts-ignore — import.meta.hot is injected by tsx/vite-node at runtime
|
|
9
46
|
importMetaHot: import.meta.hot,
|
|
10
47
|
});
|
|
48
|
+
|
|
49
|
+
server.listen(port, () => onListening(service, port));
|
package/api/hono.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// ContextVariableMap skeleton (Phase 0, express→hono migration).
|
|
2
|
+
//
|
|
3
|
+
// Mirrors the express `Request` augmentation in api/third.d.ts so that
|
|
4
|
+
// `c.set(...)` / `c.get(...)` are typed everywhere across the migration. This
|
|
5
|
+
// file only declares the key→type contract; the actual injection lands later:
|
|
6
|
+
// - user / customer / doc / customer_id → Phase 1 (libs/security.ts)
|
|
7
|
+
// - locale / t → Phase 1 (libs/middleware.ts)
|
|
8
|
+
// - sanitizedBody → Phase 1 (forked xss middleware)
|
|
9
|
+
// - livemode / baseCurrency → Phase 3 (routes/index.ts middleware)
|
|
10
|
+
// - stripeEvent / stripeClient → Phase 3e (stripe verifyWebhookSig)
|
|
11
|
+
//
|
|
12
|
+
// Two keys exist only on the hono side (no express equivalent), because hono's
|
|
13
|
+
// Request is immutable and cannot be rewritten in place the way express was:
|
|
14
|
+
// - sanitizedBody : xss is the single body read-point; routes read this, never
|
|
15
|
+
// c.req.json() (a re-read would return the UN-sanitized
|
|
16
|
+
// original — see design §7 security note).
|
|
17
|
+
// - customer_id : security "mine" mode injects the verified customer id here
|
|
18
|
+
// (express wrote req.query.customer_id =, impossible on hono).
|
|
19
|
+
import 'hono';
|
|
20
|
+
|
|
21
|
+
declare module 'hono' {
|
|
22
|
+
interface ContextVariableMap {
|
|
23
|
+
user: {
|
|
24
|
+
did: string;
|
|
25
|
+
role: string;
|
|
26
|
+
provider: string;
|
|
27
|
+
fullName: string;
|
|
28
|
+
walletOS: string;
|
|
29
|
+
via?: string;
|
|
30
|
+
};
|
|
31
|
+
livemode: boolean;
|
|
32
|
+
locale: string;
|
|
33
|
+
t: (key: string, ...args: any[]) => string;
|
|
34
|
+
doc: any;
|
|
35
|
+
customer: any;
|
|
36
|
+
baseCurrency: any;
|
|
37
|
+
customer_id: string;
|
|
38
|
+
sanitizedBody: any;
|
|
39
|
+
stripeEvent: any;
|
|
40
|
+
stripeClient: any;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Ambient declaration for node:sqlite (experimental, Node 22+). It exists at
|
|
2
|
+
// runtime (the tests + schema-drift-guard run on a node that ships it) but
|
|
3
|
+
// @types/node does not yet declare it, so tsc --noEmit fails to resolve the import.
|
|
4
|
+
// Declared as a class so it works both as a value (`new DatabaseSync()`) and as a
|
|
5
|
+
// type (`let db: DatabaseSync`). Typed loosely — test/tooling-only consumers.
|
|
6
|
+
declare module 'node:sqlite' {
|
|
7
|
+
export class DatabaseSync {
|
|
8
|
+
constructor(path?: string, options?: any);
|
|
9
|
+
|
|
10
|
+
[key: string]: any;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Blocklet server service bootstrap — shared by the production entry
|
|
2
|
+
// (./index.ts → api/dist/index.js) and the dev shell (../dev.ts).
|
|
3
|
+
//
|
|
4
|
+
// Phase 4 (express→hono): the listened surface is the hono app (service.fetch).
|
|
5
|
+
// Production serves it directly via @hono/node-server. Dev hosts the Vite client
|
|
6
|
+
// + HMR on a `connect` shell (../dev.ts) and routes backend paths to the same
|
|
7
|
+
// service.fetch — so both entries build the service identically here and only
|
|
8
|
+
// differ in HOW they put it behind a socket.
|
|
9
|
+
|
|
10
|
+
import logger from './libs/logger';
|
|
11
|
+
import { getDefaultInstanceDid, getTenantMode } from './libs/tenant';
|
|
12
|
+
import { createEmbeddedPaymentService, type EmbeddedPaymentService } from './service';
|
|
13
|
+
import { sequelize } from './store/sequelize';
|
|
14
|
+
import { blockletPort } from './libs/env';
|
|
15
|
+
|
|
16
|
+
export function buildService(): { service: EmbeddedPaymentService; port: number } {
|
|
17
|
+
const tenancy =
|
|
18
|
+
getTenantMode() === 'multi'
|
|
19
|
+
? ({ mode: 'multi' } as const)
|
|
20
|
+
: ({ mode: 'single', instanceDid: getDefaultInstanceDid() } as const);
|
|
21
|
+
|
|
22
|
+
const service = createEmbeddedPaymentService({
|
|
23
|
+
config: { ...process.env }, // Phase 12 narrows this to an explicit schema
|
|
24
|
+
db: { sequelize },
|
|
25
|
+
tenancy,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const port = parseInt(blockletPort()!, 10);
|
|
29
|
+
return { service, port };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Start background services once the socket is bound (the listen callback). */
|
|
33
|
+
export function onListening(service: EmbeddedPaymentService, port: number): void {
|
|
34
|
+
logger.info(`> payment-kit ready on ${port}`);
|
|
35
|
+
service.lifecycle.start().catch((startErr) => logger.error('failed to start background services', startErr));
|
|
36
|
+
}
|
package/api/src/crons/base.ts
CHANGED
|
@@ -175,7 +175,7 @@ export abstract class BaseSubscriptionScheduleNotification<Options extends any>
|
|
|
175
175
|
await pAll(
|
|
176
176
|
deleteTasks.map((task) => () => task()),
|
|
177
177
|
{
|
|
178
|
-
concurrency: notificationCronConcurrency,
|
|
178
|
+
concurrency: notificationCronConcurrency(),
|
|
179
179
|
stopOnError: false,
|
|
180
180
|
}
|
|
181
181
|
);
|
|
@@ -192,7 +192,7 @@ export abstract class BaseSubscriptionScheduleNotification<Options extends any>
|
|
|
192
192
|
};
|
|
193
193
|
}),
|
|
194
194
|
{
|
|
195
|
-
concurrency: notificationCronConcurrency,
|
|
195
|
+
concurrency: notificationCronConcurrency(),
|
|
196
196
|
stopOnError: false,
|
|
197
197
|
}
|
|
198
198
|
);
|
|
@@ -231,7 +231,7 @@ export abstract class BaseSubscriptionScheduleNotification<Options extends any>
|
|
|
231
231
|
);
|
|
232
232
|
|
|
233
233
|
await pAll(deletePromises, {
|
|
234
|
-
concurrency: notificationCronConcurrency,
|
|
234
|
+
concurrency: notificationCronConcurrency(),
|
|
235
235
|
stopOnError: false,
|
|
236
236
|
});
|
|
237
237
|
}
|
package/api/src/crons/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { getCronDriver } from '../libs/drivers/cron';
|
|
2
2
|
|
|
3
3
|
import { checkStakeRevokeTx } from '../integrations/arcblock/stake';
|
|
4
4
|
import { runIapReconcile } from '../integrations/iap-reconcile';
|
|
@@ -63,30 +63,33 @@ function init() {
|
|
|
63
63
|
},
|
|
64
64
|
]
|
|
65
65
|
: [];
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
// D2: register through the injected cron driver (no bare @abtnode/cron). On
|
|
67
|
+
// the node host the driver self-schedules via @abtnode/cron; on CF the host
|
|
68
|
+
// drives runDue() from scheduled(). register() is idempotent (clears + re-adds
|
|
69
|
+
// + restarts the scheduler), so a stop()/start() cycle never double-registers.
|
|
70
|
+
getCronDriver().register(
|
|
71
|
+
[
|
|
69
72
|
{
|
|
70
73
|
name: 'subscription.will.renew',
|
|
71
|
-
time: notificationCronTime,
|
|
74
|
+
time: notificationCronTime(),
|
|
72
75
|
fn: () => new SubscriptionWillRenewSchedule().run(),
|
|
73
76
|
options: { runOnInit: true },
|
|
74
77
|
},
|
|
75
78
|
{
|
|
76
79
|
name: 'subscription.trial.will.end',
|
|
77
|
-
time: notificationCronTime,
|
|
80
|
+
time: notificationCronTime(),
|
|
78
81
|
fn: () => new SubscriptionTrialWillEndSchedule().run(),
|
|
79
82
|
options: { runOnInit: true },
|
|
80
83
|
},
|
|
81
84
|
{
|
|
82
85
|
name: 'customer.subscription.will_canceled',
|
|
83
|
-
time: notificationCronTime,
|
|
86
|
+
time: notificationCronTime(),
|
|
84
87
|
fn: () => new SubscriptionWillCanceledSchedule().run(),
|
|
85
88
|
options: { runOnInit: true },
|
|
86
89
|
},
|
|
87
90
|
{
|
|
88
91
|
name: 'subscription.schedule.retry',
|
|
89
|
-
time: subscriptionCronTime,
|
|
92
|
+
time: subscriptionCronTime(),
|
|
90
93
|
fn: startSubscriptionQueue,
|
|
91
94
|
options: { runOnInit: false },
|
|
92
95
|
},
|
|
@@ -101,7 +104,7 @@ function init() {
|
|
|
101
104
|
},
|
|
102
105
|
{
|
|
103
106
|
name: 'checkoutSession.cleanup.expired',
|
|
104
|
-
time: expiredSessionCleanupCronTime,
|
|
107
|
+
time: expiredSessionCleanupCronTime(),
|
|
105
108
|
fn: async () => {
|
|
106
109
|
const removedCount = await CheckoutSession.cleanupExpiredSessions();
|
|
107
110
|
logger.info('CheckoutSession.cleanupExpiredSessions', { removedCount });
|
|
@@ -110,25 +113,25 @@ function init() {
|
|
|
110
113
|
},
|
|
111
114
|
{
|
|
112
115
|
name: 'stripe.invoice.sync',
|
|
113
|
-
time: stripeInvoiceCronTime,
|
|
116
|
+
time: stripeInvoiceCronTime(),
|
|
114
117
|
fn: batchHandleStripeInvoices,
|
|
115
118
|
options: { runOnInit: false },
|
|
116
119
|
},
|
|
117
120
|
{
|
|
118
121
|
name: 'stripe.payment.sync',
|
|
119
|
-
time: stripePaymentCronTime,
|
|
122
|
+
time: stripePaymentCronTime(),
|
|
120
123
|
fn: batchHandleStripePayments,
|
|
121
124
|
options: { runOnInit: false },
|
|
122
125
|
},
|
|
123
126
|
{
|
|
124
127
|
name: 'stripe.subscription.sync',
|
|
125
|
-
time: stripeSubscriptionCronTime,
|
|
128
|
+
time: stripeSubscriptionCronTime(),
|
|
126
129
|
fn: batchHandleStripeSubscriptions,
|
|
127
130
|
options: { runOnInit: false },
|
|
128
131
|
},
|
|
129
132
|
{
|
|
130
133
|
name: 'customer.stake.revoked',
|
|
131
|
-
time: revokeStakeCronTime,
|
|
134
|
+
time: revokeStakeCronTime(),
|
|
132
135
|
fn: checkStakeRevokeTx,
|
|
133
136
|
options: { runOnInit: false },
|
|
134
137
|
},
|
|
@@ -137,7 +140,7 @@ function init() {
|
|
|
137
140
|
// subscription state and patches local drift (refunds/renewals the
|
|
138
141
|
// webhook missed). See blocklets/core/api/src/integrations/iap-reconcile.ts.
|
|
139
142
|
name: 'iap.reconcile',
|
|
140
|
-
time: iapReconcileCronTime,
|
|
143
|
+
time: iapReconcileCronTime(),
|
|
141
144
|
fn: () => runIapReconcile(),
|
|
142
145
|
options: { runOnInit: false },
|
|
143
146
|
},
|
|
@@ -146,52 +149,52 @@ function init() {
|
|
|
146
149
|
// with pending_webhooks>0 older than 60s and re-invokes handleEvent.
|
|
147
150
|
// See blocklets/core/api/src/crons/retry-pending-events.ts.
|
|
148
151
|
name: 'event.retry',
|
|
149
|
-
time: eventRetryCronTime,
|
|
152
|
+
time: eventRetryCronTime(),
|
|
150
153
|
fn: () => retryPendingEvents(),
|
|
151
154
|
options: { runOnInit: false },
|
|
152
155
|
},
|
|
153
156
|
{
|
|
154
157
|
name: 'payment.stat',
|
|
155
|
-
time: paymentStatCronTime,
|
|
158
|
+
time: paymentStatCronTime(),
|
|
156
159
|
fn: () => createPaymentStat(),
|
|
157
160
|
options: { runOnInit: false },
|
|
158
161
|
},
|
|
159
162
|
{
|
|
160
163
|
name: 'payment.daily.report',
|
|
161
|
-
time: overdueDetectionCronTime,
|
|
164
|
+
time: overdueDetectionCronTime(),
|
|
162
165
|
fn: () => createOverdueDetection(),
|
|
163
166
|
options: { runOnInit: false },
|
|
164
167
|
},
|
|
165
168
|
{
|
|
166
169
|
name: 'deposit.vault',
|
|
167
|
-
time: depositVaultCronTime,
|
|
170
|
+
time: depositVaultCronTime(),
|
|
168
171
|
fn: () => startDepositVaultQueue(),
|
|
169
172
|
options: { runOnInit: true },
|
|
170
173
|
},
|
|
171
174
|
{
|
|
172
175
|
name: 'credit.consumption',
|
|
173
|
-
time: creditConsumptionCronTime,
|
|
176
|
+
time: creditConsumptionCronTime(),
|
|
174
177
|
fn: () => startCreditConsumeQueue(),
|
|
175
178
|
options: { runOnInit: true },
|
|
176
179
|
},
|
|
177
180
|
{
|
|
178
181
|
name: 'vendor.status.check',
|
|
179
|
-
time: vendorStatusCheckCronTime,
|
|
182
|
+
time: vendorStatusCheckCronTime(),
|
|
180
183
|
fn: () => startVendorStatusCheckSchedule(),
|
|
181
184
|
options: { runOnInit: false },
|
|
182
185
|
},
|
|
183
186
|
{
|
|
184
187
|
name: 'vendor.return.scan',
|
|
185
|
-
time: vendorReturnScanCronTime,
|
|
188
|
+
time: vendorReturnScanCronTime(),
|
|
186
189
|
fn: () => scheduleVendorReturnScan(),
|
|
187
190
|
options: { runOnInit: false },
|
|
188
191
|
},
|
|
189
192
|
...archiveJobs,
|
|
190
193
|
],
|
|
191
|
-
|
|
194
|
+
(error: Error, name: string) => {
|
|
192
195
|
logger.error('run job failed', { name, error });
|
|
193
|
-
}
|
|
194
|
-
|
|
196
|
+
}
|
|
197
|
+
);
|
|
195
198
|
}
|
|
196
199
|
|
|
197
200
|
export default {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import Notification from '@blocklet/sdk/service/notification';
|
|
2
2
|
import { getUrl } from '@blocklet/sdk/lib/component';
|
|
3
3
|
import { env } from '@blocklet/sdk/lib/config';
|
|
4
4
|
import { fromUnitToToken, BN } from '@ocap/util';
|
|
@@ -175,7 +175,7 @@ export class HealthReportTemplate implements BaseEmailTemplate<HealthReportConte
|
|
|
175
175
|
currencyTotals.set(currencyId, currentTotal.add(group.total));
|
|
176
176
|
|
|
177
177
|
// Check if this user's pending exceeds threshold for this currency
|
|
178
|
-
const thresholdInUnit = new BN(overdueThreshold.toString()).mul(new BN(10).pow(new BN(currency.decimal)));
|
|
178
|
+
const thresholdInUnit = new BN(overdueThreshold().toString()).mul(new BN(10).pow(new BN(currency.decimal)));
|
|
179
179
|
if (group.total.gt(thresholdInUnit)) {
|
|
180
180
|
exceedsThreshold = true;
|
|
181
181
|
}
|
|
@@ -33,6 +33,12 @@ const REALTIME_GRACE_SECONDS = 60;
|
|
|
33
33
|
// generous headroom and we still drain the backlog over a few minutes.
|
|
34
34
|
const BATCH_LIMIT = 5;
|
|
35
35
|
|
|
36
|
+
// Phase 4 (W1-3): this scan is deliberately tenant-agnostic — it only
|
|
37
|
+
// re-enqueues event IDs. Tenant enforcement happens downstream in
|
|
38
|
+
// handleEvent's fanout (endpoints filtered by the event's instance_did) and
|
|
39
|
+
// handleWebhook's event/endpoint tenant invariant. Events whose creation was
|
|
40
|
+
// tenant-rejected never produced a row, so this backstop cannot "rescue"
|
|
41
|
+
// them — permanent loss of rejected events is the intended semantic.
|
|
36
42
|
export async function retryPendingEvents(): Promise<void> {
|
|
37
43
|
const threshold = new Date(Date.now() - REALTIME_GRACE_SECONDS * 1000);
|
|
38
44
|
const docs = await Event.findAll({
|
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
|
}
|