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
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
2
|
-
import { SpaceClient, GetObjectCommand, PutObjectCommand } from '@blocklet/did-space-js';
|
|
3
|
-
import { blocklet, wallet } from './auth';
|
|
4
|
-
import logger from './logger';
|
|
5
|
-
import env from './env';
|
|
6
|
-
import { streamToString } from './util';
|
|
7
|
-
|
|
8
|
-
// Get user's DID Space endpoint
|
|
9
|
-
export const getEndpointAndSpaceDid = async (userDid: string): Promise<{ endpoint: string; spaceDid: string }> => {
|
|
10
|
-
const { user } = await blocklet.getUser(userDid);
|
|
11
|
-
if (!user) {
|
|
12
|
-
throw new Error('User not found');
|
|
13
|
-
}
|
|
14
|
-
if (!user.didSpace.endpoint) {
|
|
15
|
-
throw new Error(`DID Space endpoint is not set for user ${userDid}`);
|
|
16
|
-
}
|
|
17
|
-
return {
|
|
18
|
-
endpoint: user.didSpace.endpoint,
|
|
19
|
-
spaceDid: user.didSpace.did,
|
|
20
|
-
};
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
// Initialize DID Space client
|
|
24
|
-
export const getSpaceClient = async (userDid: string, endpoint?: string) => {
|
|
25
|
-
let spaceEndpoint = endpoint;
|
|
26
|
-
if (!spaceEndpoint) {
|
|
27
|
-
const result = await getEndpointAndSpaceDid(userDid);
|
|
28
|
-
spaceEndpoint = result.endpoint;
|
|
29
|
-
}
|
|
30
|
-
return new SpaceClient({
|
|
31
|
-
endpoint: spaceEndpoint,
|
|
32
|
-
wallet,
|
|
33
|
-
});
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
// Universal billing information format
|
|
37
|
-
export interface BillingInfo {
|
|
38
|
-
tx_hash: string; // Transaction hash
|
|
39
|
-
timestamp: number; // Transaction timestamp
|
|
40
|
-
|
|
41
|
-
// Basic billing information
|
|
42
|
-
invoice_id: string; // Invoice ID
|
|
43
|
-
category: string; // Bill category
|
|
44
|
-
description?: string; // Bill description: subscription payment, recharge, refund, etc.
|
|
45
|
-
|
|
46
|
-
// Payment information
|
|
47
|
-
amount: string; // Payment amount
|
|
48
|
-
currency: {
|
|
49
|
-
// Payment medium information
|
|
50
|
-
symbol: string; // Currency symbol
|
|
51
|
-
decimal: number; // Decimal places
|
|
52
|
-
type: string; // Payment type (arcblock/ethereum)
|
|
53
|
-
chain_id: string; // Chain ID
|
|
54
|
-
explorer_host: string; // Explorer host
|
|
55
|
-
explorer_tx_url: string; // Explorer transaction URL
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
// Related business information
|
|
59
|
-
related?: {
|
|
60
|
-
type: string; // Relation type (subscription/invoice)
|
|
61
|
-
id: string; // Related entity ID
|
|
62
|
-
name: string; // Related entity name
|
|
63
|
-
status?: string; // Related entity status
|
|
64
|
-
link?: string; // Link to the related entity
|
|
65
|
-
period_start?: number; // Period start time
|
|
66
|
-
period_end?: number; // Period end time
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
customer_did: string; // Customer DID
|
|
70
|
-
tags?: string[]; // User defined tags
|
|
71
|
-
app_pid: string; // App PID
|
|
72
|
-
link?: string; // Link to the related entity
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Merge billing information, preserving user customizations
|
|
76
|
-
const mergeBillingInfo = (existing: BillingInfo, updated: Partial<BillingInfo>): BillingInfo => {
|
|
77
|
-
return {
|
|
78
|
-
...(existing || {}),
|
|
79
|
-
...updated,
|
|
80
|
-
// Preserve user customizations if they exist
|
|
81
|
-
description: existing?.description || updated?.description,
|
|
82
|
-
category: existing?.category || updated?.category || 'other',
|
|
83
|
-
tags: Array.from(new Set([...(existing.tags || []), ...(updated.tags || [])])),
|
|
84
|
-
};
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
// Upload or update billing information to DID Space
|
|
88
|
-
export const uploadBillingInfo = async (
|
|
89
|
-
userDid: string,
|
|
90
|
-
billingInfo: BillingInfo,
|
|
91
|
-
endpoint?: string
|
|
92
|
-
): Promise<boolean> => {
|
|
93
|
-
try {
|
|
94
|
-
// Validate required fields
|
|
95
|
-
if (!billingInfo.tx_hash || !billingInfo.invoice_id || !billingInfo.amount) {
|
|
96
|
-
logger.error('Missing required fields in billingInfo');
|
|
97
|
-
return false;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const client = await getSpaceClient(userDid, endpoint);
|
|
101
|
-
|
|
102
|
-
const key = `txs/${billingInfo.tx_hash}/metadata.json`;
|
|
103
|
-
|
|
104
|
-
let finalBillingInfo = {
|
|
105
|
-
...billingInfo,
|
|
106
|
-
app_pid: billingInfo?.app_pid || env.appPid,
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
// Check existing bill
|
|
110
|
-
const existingBill = await client.send(new GetObjectCommand({ key }));
|
|
111
|
-
if (existingBill.statusCode === 200 && existingBill.data) {
|
|
112
|
-
// Merge with existing data if force update
|
|
113
|
-
const existing = JSON.parse(await streamToString(existingBill.data)) as BillingInfo;
|
|
114
|
-
finalBillingInfo = mergeBillingInfo(existing, billingInfo);
|
|
115
|
-
logger.info('Updating existing billing:', { userDid, txHash: billingInfo.tx_hash });
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const payload = new PutObjectCommand({
|
|
119
|
-
key,
|
|
120
|
-
data: JSON.stringify(finalBillingInfo),
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
const result = await client.send(payload);
|
|
124
|
-
|
|
125
|
-
if (result.statusCode !== 200) {
|
|
126
|
-
logger.error('Upload billing info failed:', result);
|
|
127
|
-
return false;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
logger.info(existingBill.statusCode === 200 ? 'Bill updated:' : 'Bill created:', {
|
|
131
|
-
userDid,
|
|
132
|
-
txHash: billingInfo.tx_hash,
|
|
133
|
-
});
|
|
134
|
-
return true;
|
|
135
|
-
} catch (error) {
|
|
136
|
-
logger.error('Upload billing info error:', error);
|
|
137
|
-
return false;
|
|
138
|
-
}
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
// Get specific billing information by transaction hash
|
|
142
|
-
export const getBillingInfo = async (
|
|
143
|
-
userDid: string,
|
|
144
|
-
txHash: string,
|
|
145
|
-
endpoint?: string
|
|
146
|
-
): Promise<BillingInfo | null> => {
|
|
147
|
-
try {
|
|
148
|
-
if (!txHash) {
|
|
149
|
-
logger.error('Transaction hash is required');
|
|
150
|
-
return null;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const client = await getSpaceClient(userDid, endpoint);
|
|
154
|
-
const payload = new GetObjectCommand({
|
|
155
|
-
key: `txs/${txHash}/metadata.json`,
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
const result = await client.send(payload);
|
|
159
|
-
|
|
160
|
-
if (result.statusCode !== 200 || !result.data) {
|
|
161
|
-
logger.debug('Bill not found:', { userDid, txHash });
|
|
162
|
-
return null;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const billingInfo = JSON.parse(await streamToString(result.data)) as BillingInfo;
|
|
166
|
-
|
|
167
|
-
// Validate parsed data
|
|
168
|
-
if (!billingInfo.tx_hash || !billingInfo.invoice_id) {
|
|
169
|
-
logger.error('Invalid billing data structure:', { userDid, txHash });
|
|
170
|
-
return null;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return billingInfo;
|
|
174
|
-
} catch (error) {
|
|
175
|
-
logger.error('Get billing info error:', { userDid, txHash, error });
|
|
176
|
-
return null;
|
|
177
|
-
}
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
// Update specific fields of billing information
|
|
181
|
-
export const updateBillingInfo = async (
|
|
182
|
-
userDid: string,
|
|
183
|
-
txHash: string,
|
|
184
|
-
updates: Partial<BillingInfo>,
|
|
185
|
-
endpoint?: string
|
|
186
|
-
): Promise<boolean> => {
|
|
187
|
-
try {
|
|
188
|
-
const existing = await getBillingInfo(userDid, txHash, endpoint);
|
|
189
|
-
if (!existing) {
|
|
190
|
-
logger.error('Billing not found for update:', { userDid, txHash });
|
|
191
|
-
return false;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const updated = mergeBillingInfo(existing, updates);
|
|
195
|
-
if (!updated.tx_hash || !updated.invoice_id || !updated.amount) {
|
|
196
|
-
logger.error('Invalid billing data structure:', { userDid, txHash });
|
|
197
|
-
return false;
|
|
198
|
-
}
|
|
199
|
-
const result = await uploadBillingInfo(userDid, updated, endpoint);
|
|
200
|
-
return result;
|
|
201
|
-
} catch (error) {
|
|
202
|
-
logger.error('Update billing info error:', { userDid, txHash, error });
|
|
203
|
-
return false;
|
|
204
|
-
}
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
// Batch upload/update billing information
|
|
208
|
-
export const uploadBillingInfoBatch = async (
|
|
209
|
-
userDid: string,
|
|
210
|
-
billingInfoList: BillingInfo[],
|
|
211
|
-
endpoint?: string
|
|
212
|
-
): Promise<boolean[]> => {
|
|
213
|
-
try {
|
|
214
|
-
if (!Array.isArray(billingInfoList) || billingInfoList.length === 0) {
|
|
215
|
-
logger.error('Invalid billingInfoList:', { userDid });
|
|
216
|
-
return [];
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Validate all bills before upload
|
|
220
|
-
const validBills = billingInfoList.filter((bill) => bill.tx_hash && bill.invoice_id && bill.amount);
|
|
221
|
-
|
|
222
|
-
if (validBills.length !== billingInfoList.length) {
|
|
223
|
-
logger.warn('Some bills are invalid and will be skipped');
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const results = await Promise.all(
|
|
227
|
-
validBills.map((billingInfo) => uploadBillingInfo(userDid, billingInfo, endpoint))
|
|
228
|
-
);
|
|
229
|
-
|
|
230
|
-
return results;
|
|
231
|
-
} catch (error) {
|
|
232
|
-
logger.error('Batch upload error:', { userDid, error });
|
|
233
|
-
return [];
|
|
234
|
-
}
|
|
235
|
-
};
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/* eslint-disable import/prefer-default-export */
|
|
2
|
-
import type { NextFunction, Request, Response } from 'express';
|
|
3
|
-
import { verify } from '@blocklet/sdk/lib/util/verify-sign';
|
|
4
|
-
import { translate } from '../locales';
|
|
5
|
-
import { context } from './context';
|
|
6
|
-
|
|
7
|
-
export function ensureI18n() {
|
|
8
|
-
return (req: Request, _: Response, next: NextFunction) => {
|
|
9
|
-
req.locale = String(req.query.locale || 'en');
|
|
10
|
-
req.t = translate;
|
|
11
|
-
next();
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export async function contextMiddleware(req: Request, _res: Response, next: NextFunction) {
|
|
16
|
-
const requestId =
|
|
17
|
-
(req.headers['x-request-id'] as string) || `req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
18
|
-
let requestedBy = 'system';
|
|
19
|
-
|
|
20
|
-
// Check component signature
|
|
21
|
-
const sig = req.get('x-component-sig');
|
|
22
|
-
const componentDid = req.get('x-component-did');
|
|
23
|
-
if (sig && componentDid) {
|
|
24
|
-
const data = typeof req.body === 'undefined' ? {} : req.body;
|
|
25
|
-
const verified = await verify(data, sig);
|
|
26
|
-
if (verified) {
|
|
27
|
-
requestedBy = componentDid;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Check user DID from headers
|
|
32
|
-
if (req.headers['x-user-did']) {
|
|
33
|
-
requestedBy = req.headers['x-user-did'] as string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Check authenticated user
|
|
37
|
-
if (req.user?.did) {
|
|
38
|
-
requestedBy = req.user.did;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return context.run(
|
|
42
|
-
{
|
|
43
|
-
requestId,
|
|
44
|
-
requestedBy,
|
|
45
|
-
},
|
|
46
|
-
async () => {
|
|
47
|
-
await next();
|
|
48
|
-
}
|
|
49
|
-
);
|
|
50
|
-
}
|
package/api/src/libs/security.ts
DELETED
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
import { auth } from '@blocklet/sdk/lib/middlewares';
|
|
2
|
-
import { getVerifyData, verify } from '@blocklet/sdk/lib/util/verify-sign';
|
|
3
|
-
import { verifyLoginToken } from '@blocklet/sdk/lib/util/verify-session';
|
|
4
|
-
import { getWallet } from '@blocklet/sdk/lib/wallet';
|
|
5
|
-
import type { NextFunction, Request, Response } from 'express';
|
|
6
|
-
import type { Model } from 'sequelize';
|
|
7
|
-
|
|
8
|
-
import { Customer } from '../store/models/customer';
|
|
9
|
-
|
|
10
|
-
export const ensureAdmin = auth({ roles: ['owner', 'admin'] });
|
|
11
|
-
|
|
12
|
-
const wallet = getWallet();
|
|
13
|
-
|
|
14
|
-
type PermissionSpec<T extends Model> = {
|
|
15
|
-
component?: boolean; // allow component calls
|
|
16
|
-
roles?: string[]; // allow current session user with one of the specified roles
|
|
17
|
-
record?: {
|
|
18
|
-
// allow record owner
|
|
19
|
-
model: T;
|
|
20
|
-
field: string;
|
|
21
|
-
findById?: (id: string) => Promise<T | null>;
|
|
22
|
-
};
|
|
23
|
-
mine?: boolean;
|
|
24
|
-
embed?: boolean;
|
|
25
|
-
ensureLogin?: boolean;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* This middleware is used to authenticate request by session or component call.
|
|
30
|
-
* If a request is authenticated, it will set `req.user` to the session user.
|
|
31
|
-
* If a request is authenticated by component call, it will set `req.user` to the component user.
|
|
32
|
-
* If a request is authenticated by record owner, it will set `req.user` to the session user and set `req.doc` to the record.
|
|
33
|
-
*/
|
|
34
|
-
export function authenticate<T extends Model>({
|
|
35
|
-
component,
|
|
36
|
-
roles,
|
|
37
|
-
record,
|
|
38
|
-
mine,
|
|
39
|
-
embed,
|
|
40
|
-
ensureLogin,
|
|
41
|
-
}: PermissionSpec<T>) {
|
|
42
|
-
return async (req: Request, res: Response, next: NextFunction) => {
|
|
43
|
-
// Dev-only bypass: requires both NODE_ENV=development AND the explicit
|
|
44
|
-
// opt-in env ENABLE_DEV_FAKE_AUTH=1, plus the x-dev-fake-did header on
|
|
45
|
-
// the request. Lets mobile demo clients that don't go through DID Connect
|
|
46
|
-
// (e.g. the local-tunnel backend which bypasses Blocklet Server) still
|
|
47
|
-
// exercise real handlers. Production never sets ENABLE_DEV_FAKE_AUTH, so
|
|
48
|
-
// this branch can't trigger there; dev defaults off, so we don't
|
|
49
|
-
// accidentally regress to fake auth after wiring the real flow.
|
|
50
|
-
if (process.env.NODE_ENV === 'development' && process.env.ENABLE_DEV_FAKE_AUTH === '1') {
|
|
51
|
-
const devDid = req.get('x-dev-fake-did');
|
|
52
|
-
if (devDid) {
|
|
53
|
-
req.user = {
|
|
54
|
-
did: devDid,
|
|
55
|
-
role: 'owner', // satisfies routes that require owner/admin
|
|
56
|
-
provider: 'dev',
|
|
57
|
-
fullName: 'dev-fake-user',
|
|
58
|
-
walletOS: '',
|
|
59
|
-
via: 'dev',
|
|
60
|
-
};
|
|
61
|
-
return next();
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Authenticate by Authorization: Bearer <login-token>. The token is a JWT
|
|
66
|
-
// signed with this blocklet's session secret (see @blocklet/sdk session
|
|
67
|
-
// middleware). When clients hit us through a tunnel that bypasses Blocklet
|
|
68
|
-
// Server (so x-user-did is NOT injected), we need to verify the token
|
|
69
|
-
// ourselves. verifyLoginToken does local JWT signature verification, no
|
|
70
|
-
// HTTP callback. On success we forward into the existing x-user-did branch
|
|
71
|
-
// by populating req.headers so the role/mine/record cascade below applies
|
|
72
|
-
// unchanged.
|
|
73
|
-
const authHeader = req.get('authorization');
|
|
74
|
-
if (authHeader && /^Bearer\s+/i.test(authHeader) && !req.headers['x-user-did']) {
|
|
75
|
-
const token = authHeader.replace(/^Bearer\s+/i, '').trim();
|
|
76
|
-
if (token) {
|
|
77
|
-
const session = await verifyLoginToken({ token, strictMode: false }).catch(() => null);
|
|
78
|
-
if (session?.did) {
|
|
79
|
-
// Some BS versions put a bare base58 address in the JWT, others
|
|
80
|
-
// the canonical `did:abt:…` form. Normalize so downstream code
|
|
81
|
-
// (entitlement self-check, mine: lookups by req.user.did) sees the
|
|
82
|
-
// same shape clients send in `customer_did` query params.
|
|
83
|
-
const canonicalDid = session.did.startsWith('did:abt:') ? session.did : `did:abt:${session.did}`;
|
|
84
|
-
req.headers['x-user-did'] = canonicalDid;
|
|
85
|
-
req.headers['x-user-role'] = `blocklet-${session.role || 'user'}`;
|
|
86
|
-
req.headers['x-user-provider'] = session.provider || 'wallet';
|
|
87
|
-
req.headers['x-user-fullname'] = encodeURIComponent(session.fullName || '');
|
|
88
|
-
req.headers['x-user-wallet-os'] = session.walletOS || '';
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// authenticate by component call
|
|
94
|
-
const sig = req.get('x-component-sig');
|
|
95
|
-
if (component && sig) {
|
|
96
|
-
const { data } = getVerifyData(req as any, 'component');
|
|
97
|
-
const verified = await verify(data, sig);
|
|
98
|
-
if (!verified) {
|
|
99
|
-
return res.status(401).json({ error: 'Invalid signature for component call' });
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
req.user = {
|
|
103
|
-
did: <string>req.get('x-component-did'),
|
|
104
|
-
role: 'owner',
|
|
105
|
-
provider: 'wallet',
|
|
106
|
-
fullName: <string>req.get('x-component-did'),
|
|
107
|
-
walletOS: '',
|
|
108
|
-
via: 'api',
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
return next();
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// authenticate by authToken for embed
|
|
115
|
-
const token = req.query.authToken || '';
|
|
116
|
-
const id = req.params.id || req.query.subscription_id || '';
|
|
117
|
-
if (embed && token && id) {
|
|
118
|
-
try {
|
|
119
|
-
const verified = await wallet.verify(id as string, token as string);
|
|
120
|
-
if (!verified) {
|
|
121
|
-
return res.status(401).json({ error: `Invalid signature for embed: ${id}` });
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
req.user = {
|
|
125
|
-
did: wallet.address,
|
|
126
|
-
role: 'owner',
|
|
127
|
-
provider: 'wallet',
|
|
128
|
-
fullName: 'embed',
|
|
129
|
-
walletOS: '',
|
|
130
|
-
via: 'embed',
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
return next();
|
|
134
|
-
} catch (err) {
|
|
135
|
-
return res.status(401).json({ error: `Invalid signature for embed: ${id}: ${err.message}` });
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (req.headers['x-user-did']) {
|
|
140
|
-
const role = (<string>req.headers['x-user-role'] || '').replace('blocklet-', '') || 'guest';
|
|
141
|
-
req.user = {
|
|
142
|
-
did: <string>req.headers['x-user-did'],
|
|
143
|
-
role,
|
|
144
|
-
provider: <string>req.headers['x-user-provider'],
|
|
145
|
-
fullName: decodeURIComponent(<string>req.headers['x-user-fullname']),
|
|
146
|
-
walletOS: <string>req.headers['x-user-wallet-os'],
|
|
147
|
-
via: 'dashboard',
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
// authenticate by session user
|
|
151
|
-
if (roles) {
|
|
152
|
-
if (roles.includes(req.user?.role)) {
|
|
153
|
-
return next();
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (ensureLogin) {
|
|
158
|
-
req.user.via = 'api';
|
|
159
|
-
return next();
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (mine) {
|
|
163
|
-
const customer = await Customer.findOne({ where: { did: req.user.did } });
|
|
164
|
-
if (customer) {
|
|
165
|
-
req.customer = customer;
|
|
166
|
-
req.query.customer_id = customer.id;
|
|
167
|
-
return next();
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// authenticate by record owner
|
|
172
|
-
if (record) {
|
|
173
|
-
const { model, field = 'customer_id', findById } = record;
|
|
174
|
-
const doc: T | null =
|
|
175
|
-
findById && typeof findById === 'function'
|
|
176
|
-
? await findById(req.params.id as string)
|
|
177
|
-
: await (model as any).findByPk(req.params.id);
|
|
178
|
-
if (doc && doc[field as keyof T]) {
|
|
179
|
-
const customer = await Customer.findOne({ where: { did: req.user.did } });
|
|
180
|
-
req.doc = doc;
|
|
181
|
-
req.customer = customer;
|
|
182
|
-
if (customer && customer.id === doc[field as keyof T]) {
|
|
183
|
-
req.user.via = 'portal';
|
|
184
|
-
return next();
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return res.status(403).json({ error: 'Not authorized to perform this action' });
|
|
191
|
-
};
|
|
192
|
-
}
|