tapimo 0.2.0 → 0.3.0
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/README.md +31 -3
- package/dist/ApiKey.d.ts +1 -0
- package/dist/ApiKey.d.ts.map +1 -1
- package/dist/ApiKey.js +5 -2
- package/dist/ApiKey.js.map +1 -1
- package/dist/ApiKeys.d.ts +28 -1
- package/dist/ApiKeys.d.ts.map +1 -1
- package/dist/ApiKeys.js +34 -1
- package/dist/ApiKeys.js.map +1 -1
- package/dist/App.d.ts +7167 -4507
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +8 -0
- package/dist/App.js.map +1 -1
- package/dist/Scope.d.ts +11 -2
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +14 -3
- package/dist/Scope.js.map +1 -1
- package/dist/admin/App.d.ts +128 -118
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +7 -0
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/scopes.d.ts +8 -0
- package/dist/admin/apps/scopes.d.ts.map +1 -1
- package/dist/admin/apps/scopes.js +3 -0
- package/dist/admin/apps/scopes.js.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +57 -57
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.js +13 -13
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/analytics/Analytics.d.ts +151 -0
- package/dist/analytics/Analytics.d.ts.map +1 -0
- package/dist/analytics/Analytics.js +212 -0
- package/dist/analytics/Analytics.js.map +1 -0
- package/dist/analytics/tables/requestEvents.d.ts +215 -0
- package/dist/analytics/tables/requestEvents.d.ts.map +1 -0
- package/dist/analytics/tables/requestEvents.js +264 -0
- package/dist/analytics/tables/requestEvents.js.map +1 -0
- package/dist/apps/data/App.d.ts +1024 -624
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +36 -0
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +45 -45
- package/dist/apps/data/routes/coingecko.d.ts +176 -176
- package/dist/apps/data/routes/exchanges.d.ts +455 -455
- package/dist/apps/data/routes/fee-amm.d.ts +101 -101
- package/dist/apps/data/routes/funding.d.ts +765 -0
- package/dist/apps/data/routes/funding.d.ts.map +1 -0
- package/dist/apps/data/routes/funding.js +383 -0
- package/dist/apps/data/routes/funding.js.map +1 -0
- package/dist/apps/data/routes/receipts.d.ts.map +1 -1
- package/dist/apps/data/routes/receipts.js +2 -8
- package/dist/apps/data/routes/receipts.js.map +1 -1
- package/dist/apps/data/routes/rpc.d.ts +29 -36
- package/dist/apps/data/routes/rpc.d.ts.map +1 -1
- package/dist/apps/data/routes/rpc.js +29 -1
- package/dist/apps/data/routes/rpc.js.map +1 -1
- package/dist/apps/data/routes/tokenlist.d.ts +36 -36
- package/dist/apps/data/routes/transfers.d.ts +56 -56
- package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
- package/dist/apps/data/routes/webhooks.d.ts +45 -45
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +8 -8
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/docs/App.d.ts +4 -2
- package/dist/apps/docs/App.d.ts.map +1 -1
- package/dist/apps/docs/App.js +27 -95
- package/dist/apps/docs/App.js.map +1 -1
- package/dist/apps/docs/reference.d.ts +21 -0
- package/dist/apps/docs/reference.d.ts.map +1 -0
- package/dist/apps/docs/reference.js +112 -0
- package/dist/apps/docs/reference.js.map +1 -0
- package/dist/apps/index.d.ts +1 -0
- package/dist/apps/index.d.ts.map +1 -1
- package/dist/apps/index.js +1 -0
- package/dist/apps/index.js.map +1 -1
- package/dist/apps/mcp.d.ts +76 -0
- package/dist/apps/mcp.d.ts.map +1 -0
- package/dist/apps/mcp.js +188 -0
- package/dist/apps/mcp.js.map +1 -0
- package/dist/apps/platform/App.d.ts +3005 -728
- package/dist/apps/platform/App.d.ts.map +1 -1
- package/dist/apps/platform/App.js +13 -3
- package/dist/apps/platform/App.js.map +1 -1
- package/dist/apps/platform/Billing.d.ts +279 -0
- package/dist/apps/platform/Billing.d.ts.map +1 -0
- package/dist/apps/platform/Billing.js +477 -0
- package/dist/apps/platform/Billing.js.map +1 -0
- package/dist/apps/platform/index.d.ts +1 -0
- package/dist/apps/platform/index.d.ts.map +1 -1
- package/dist/apps/platform/index.js +1 -0
- package/dist/apps/platform/index.js.map +1 -1
- package/dist/apps/platform/routes/api-keys.d.ts +341 -49
- package/dist/apps/platform/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/platform/routes/api-keys.js +115 -13
- package/dist/apps/platform/routes/api-keys.js.map +1 -1
- package/dist/apps/platform/routes/billing.d.ts +1520 -0
- package/dist/apps/platform/routes/billing.d.ts.map +1 -0
- package/dist/apps/platform/routes/billing.js +795 -0
- package/dist/apps/platform/routes/billing.js.map +1 -0
- package/dist/apps/platform/routes/invitations.d.ts +201 -83
- package/dist/apps/platform/routes/invitations.d.ts.map +1 -1
- package/dist/apps/platform/routes/invitations.js +58 -0
- package/dist/apps/platform/routes/invitations.js.map +1 -1
- package/dist/apps/platform/routes/me.d.ts +17 -17
- package/dist/apps/platform/routes/orgs.d.ts +50 -36
- package/dist/apps/platform/routes/orgs.d.ts.map +1 -1
- package/dist/apps/platform/routes/orgs.js +14 -3
- package/dist/apps/platform/routes/orgs.js.map +1 -1
- package/dist/apps/platform/routes/projects.d.ts +97 -97
- package/dist/apps/platform/routes/scopes.d.ts +107 -0
- package/dist/apps/platform/routes/scopes.d.ts.map +1 -0
- package/dist/apps/platform/routes/scopes.js +66 -0
- package/dist/apps/platform/routes/scopes.js.map +1 -0
- package/dist/apps/platform/routes/usage.d.ts +588 -0
- package/dist/apps/platform/routes/usage.d.ts.map +1 -0
- package/dist/apps/platform/routes/usage.js +391 -0
- package/dist/apps/platform/routes/usage.js.map +1 -0
- package/dist/apps/relay/App.d.ts +4 -1
- package/dist/apps/relay/App.d.ts.map +1 -1
- package/dist/apps/relay/App.js +118 -10
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/apps/relay/Sponsorships.d.ts.map +1 -1
- package/dist/apps/relay/Sponsorships.js +2 -1
- package/dist/apps/relay/Sponsorships.js.map +1 -1
- package/dist/cli/Cli.d.ts +8 -1
- package/dist/cli/Cli.d.ts.map +1 -1
- package/dist/cli/Cli.js +11 -3
- package/dist/cli/Cli.js.map +1 -1
- package/dist/cli/commands/admin.d.ts +4 -0
- package/dist/cli/commands/admin.d.ts.map +1 -1
- package/dist/cli/commands/admin.js +80 -1
- package/dist/cli/commands/admin.js.map +1 -1
- package/dist/db/Db.d.ts +7 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +126 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +79 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +88 -2
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/billingSettings.d.ts +55 -0
- package/dist/db/tables/billingSettings.d.ts.map +1 -0
- package/dist/db/tables/billingSettings.js +70 -0
- package/dist/db/tables/billingSettings.js.map +1 -0
- package/dist/db/tables/invitations.js +2 -2
- package/dist/db/tables/invitations.js.map +1 -1
- package/dist/db/tables/organizations.d.ts +6 -2
- package/dist/db/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +10 -4
- package/dist/db/tables/organizations.js.map +1 -1
- package/dist/db/tables/projects.js +2 -2
- package/dist/db/tables/projects.js.map +1 -1
- package/dist/db/tables/requestUsage.d.ts +121 -0
- package/dist/db/tables/requestUsage.d.ts.map +1 -0
- package/dist/db/tables/requestUsage.js +150 -0
- package/dist/db/tables/requestUsage.js.map +1 -0
- package/dist/db/tables/sponsoredTransactions.d.ts +119 -0
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
- package/dist/db/tables/sponsoredTransactions.js +155 -2
- package/dist/db/tables/sponsoredTransactions.js.map +1 -1
- package/dist/db/tables/stripeCustomers.d.ts +81 -0
- package/dist/db/tables/stripeCustomers.d.ts.map +1 -0
- package/dist/db/tables/stripeCustomers.js +120 -0
- package/dist/db/tables/stripeCustomers.js.map +1 -0
- package/dist/db/tables/users.js +2 -2
- package/dist/db/tables/users.js.map +1 -1
- package/dist/handlers/internal/sponsorship.d.ts +5 -2
- package/dist/handlers/internal/sponsorship.d.ts.map +1 -1
- package/dist/handlers/internal/sponsorship.js +19 -5
- package/dist/handlers/internal/sponsorship.js.map +1 -1
- package/dist/handlers/internal/utils.d.ts +10 -3
- package/dist/handlers/internal/utils.d.ts.map +1 -1
- package/dist/handlers/internal/utils.js +14 -6
- package/dist/handlers/internal/utils.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/Auth.d.ts +18 -4
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +148 -76
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/Cache.d.ts.map +1 -1
- package/dist/internal/Cache.js +4 -1
- package/dist/internal/Cache.js.map +1 -1
- package/dist/internal/EdgeCache.d.ts +10 -0
- package/dist/internal/EdgeCache.d.ts.map +1 -1
- package/dist/internal/EdgeCache.js +39 -8
- package/dist/internal/EdgeCache.js.map +1 -1
- package/dist/internal/Fees.d.ts +27 -0
- package/dist/internal/Fees.d.ts.map +1 -0
- package/dist/internal/Fees.js +48 -0
- package/dist/internal/Fees.js.map +1 -0
- package/dist/internal/FundingRoutes.d.ts +183 -0
- package/dist/internal/FundingRoutes.d.ts.map +1 -0
- package/dist/internal/FundingRoutes.js +490 -0
- package/dist/internal/FundingRoutes.js.map +1 -0
- package/dist/internal/Id.d.ts +15 -0
- package/dist/internal/Id.d.ts.map +1 -0
- package/dist/internal/Id.js +22 -0
- package/dist/internal/Id.js.map +1 -0
- package/dist/internal/Log.d.ts +2 -0
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +3 -0
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/OpenApi.d.ts +10 -1
- package/dist/internal/OpenApi.d.ts.map +1 -1
- package/dist/internal/OpenApi.js +8 -1
- package/dist/internal/OpenApi.js.map +1 -1
- package/dist/internal/RateLimit.d.ts +25 -0
- package/dist/internal/RateLimit.d.ts.map +1 -1
- package/dist/internal/RateLimit.js +51 -0
- package/dist/internal/RateLimit.js.map +1 -1
- package/dist/internal/Response.d.ts +1 -1
- package/dist/internal/Response.d.ts.map +1 -1
- package/dist/internal/Schema.d.ts +8 -0
- package/dist/internal/Schema.d.ts.map +1 -1
- package/dist/internal/Schema.js +7 -0
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +3 -3
- package/dist/internal/Webhooks.js.map +1 -1
- package/package.json +4 -3
- package/src/ApiKey.ts +9 -2
- package/src/ApiKeys.test.ts +51 -0
- package/src/ApiKeys.ts +50 -1
- package/src/App.test.ts +33 -86718
- package/src/App.ts +18 -0
- package/src/Client.test-d.ts +351 -0
- package/src/Scope.ts +16 -3
- package/src/admin/apps/scopes.test.ts +6 -0
- package/src/admin/apps/scopes.ts +3 -0
- package/src/admin/ui/src/components/Page.tsx +2 -3
- package/src/admin/ui/src/routes/__root.tsx +2 -2
- package/src/admin/ui/src/routes/api-keys.tsx +2 -2
- package/src/admin/ui.generated.ts +13 -13
- package/src/analytics/Analytics.test.ts +527 -0
- package/src/analytics/Analytics.ts +318 -0
- package/src/analytics/tables/requestEvents.ts +523 -0
- package/src/apps/data/App.test.ts +72 -0
- package/src/apps/data/App.ts +35 -0
- package/src/apps/data/routes/funding.test.ts +214 -0
- package/src/apps/data/routes/funding.ts +584 -0
- package/src/apps/data/routes/receipts.ts +2 -9
- package/src/apps/data/routes/rpc.test.ts +78 -2
- package/src/apps/data/routes/rpc.ts +31 -1
- package/src/apps/data/routes/webhooks.test.ts +1 -1
- package/src/apps/data/routes/webhooks.ts +11 -8
- package/src/apps/docs/App.ts +29 -100
- package/src/apps/docs/reference.ts +132 -0
- package/src/apps/index.ts +1 -0
- package/src/apps/mcp.test.ts +206 -0
- package/src/apps/mcp.ts +250 -0
- package/src/apps/platform/App.ts +43 -4
- package/src/apps/platform/Billing.test.ts +530 -0
- package/src/apps/platform/Billing.ts +682 -0
- package/src/apps/platform/index.ts +1 -0
- package/src/apps/platform/routes/api-keys.test.ts +172 -9
- package/src/apps/platform/routes/api-keys.ts +136 -20
- package/src/apps/platform/routes/billing.test.ts +1100 -0
- package/src/apps/platform/routes/billing.ts +966 -0
- package/src/apps/platform/routes/invitations.test.ts +55 -7
- package/src/apps/platform/routes/invitations.ts +68 -0
- package/src/apps/platform/routes/me.test.ts +230 -0
- package/src/apps/platform/routes/orgs.ts +26 -4
- package/src/apps/platform/routes/scopes.test.ts +29 -0
- package/src/apps/platform/routes/scopes.ts +85 -0
- package/src/apps/platform/routes/usage.test.ts +422 -0
- package/src/apps/platform/routes/usage.ts +527 -0
- package/src/apps/relay/App.test.ts +457 -8
- package/src/apps/relay/App.ts +120 -12
- package/src/apps/relay/Sponsorships.test.ts +1 -1
- package/src/apps/relay/Sponsorships.ts +2 -1
- package/src/cli/Cli.test.ts +25 -2
- package/src/cli/Cli.ts +17 -2
- package/src/cli/commands/admin.test.ts +83 -0
- package/src/cli/commands/admin.ts +89 -1
- package/src/db/Db.ts +137 -0
- package/src/db/Schema.ts +104 -2
- package/src/db/tables/billingSettings.test.ts +87 -0
- package/src/db/tables/billingSettings.ts +108 -0
- package/src/db/tables/invitations.ts +2 -2
- package/src/db/tables/organizations.test.ts +1 -1
- package/src/db/tables/organizations.ts +15 -5
- package/src/db/tables/projects.ts +2 -2
- package/src/db/tables/requestUsage.ts +231 -0
- package/src/db/tables/sponsoredTransactions.test.ts +108 -1
- package/src/db/tables/sponsoredTransactions.ts +230 -2
- package/src/db/tables/stripeCustomers.test.ts +109 -0
- package/src/db/tables/stripeCustomers.ts +162 -0
- package/src/db/tables/users.ts +2 -2
- package/src/handlers/internal/sponsorship.ts +34 -7
- package/src/handlers/internal/utils.ts +19 -7
- package/src/index.ts +1 -0
- package/src/internal/Auth.test.ts +175 -25
- package/src/internal/Auth.ts +175 -86
- package/src/internal/Cache.ts +5 -1
- package/src/internal/EdgeCache.test.ts +27 -4
- package/src/internal/EdgeCache.ts +47 -9
- package/src/internal/Fees.test.ts +32 -0
- package/src/internal/Fees.ts +51 -0
- package/src/internal/FundingRoutes.ts +583 -0
- package/src/internal/Id.test.ts +17 -0
- package/src/internal/Id.ts +24 -0
- package/src/internal/Log.test.ts +1 -0
- package/src/internal/Log.ts +5 -0
- package/src/internal/OpenApi.ts +12 -1
- package/src/internal/RateLimit.test.ts +84 -0
- package/src/internal/RateLimit.ts +70 -0
- package/src/internal/Response.ts +1 -1
- package/src/internal/Schema.ts +8 -0
- package/src/internal/Webhooks.test.ts +3 -3
- package/src/internal/Webhooks.ts +3 -3
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { nanoid } from 'nanoid'
|
|
2
|
+
|
|
3
|
+
import { postgresUrl } from '../../../test/containers.js'
|
|
4
|
+
import * as Db from '../Db.js'
|
|
5
|
+
import * as StripeCustomers from './stripeCustomers.js'
|
|
6
|
+
|
|
7
|
+
const create = () => Db.postgres({ connectionString: postgresUrl, schema: `t_${nanoid()}` })
|
|
8
|
+
|
|
9
|
+
/** Redacts nondeterministic row fields (timestamps). */
|
|
10
|
+
function redact(record: StripeCustomers.Record) {
|
|
11
|
+
return { ...record, createdAt: '<timestamp>', updatedAt: '<timestamp>' }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('create', () => {
|
|
15
|
+
test('behavior: inserts with status none', async () => {
|
|
16
|
+
const db = create()
|
|
17
|
+
await db.migrate()
|
|
18
|
+
const record = await StripeCustomers.create(db, {
|
|
19
|
+
orgId: 'org_1',
|
|
20
|
+
stripeCustomerId: 'cus_a',
|
|
21
|
+
})
|
|
22
|
+
expect(redact(record)).toMatchInlineSnapshot(`
|
|
23
|
+
{
|
|
24
|
+
"createdAt": "<timestamp>",
|
|
25
|
+
"environment": "production",
|
|
26
|
+
"orgId": "org_1",
|
|
27
|
+
"status": "none",
|
|
28
|
+
"stripeCustomerId": "cus_a",
|
|
29
|
+
"updatedAt": "<timestamp>",
|
|
30
|
+
}
|
|
31
|
+
`)
|
|
32
|
+
await db.close()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('behavior: a concurrent duplicate resolves to the winning row', async () => {
|
|
36
|
+
const db = create()
|
|
37
|
+
await db.migrate()
|
|
38
|
+
const winner = await StripeCustomers.create(db, { orgId: 'org_1', stripeCustomerId: 'cus_a' })
|
|
39
|
+
// Loser of the first-checkout race: same org, its own Stripe customer.
|
|
40
|
+
const resolved = await StripeCustomers.create(db, { orgId: 'org_1', stripeCustomerId: 'cus_b' }) // prettier-ignore
|
|
41
|
+
expect(resolved).toStrictEqual(winner)
|
|
42
|
+
expect(await StripeCustomers.getByCustomer(db, 'cus_b')).toBeUndefined()
|
|
43
|
+
await db.close()
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
describe('get', () => {
|
|
48
|
+
test('behavior: reads by org; undefined when absent', async () => {
|
|
49
|
+
const db = create()
|
|
50
|
+
await db.migrate()
|
|
51
|
+
const record = await StripeCustomers.create(db, { orgId: 'org_1', stripeCustomerId: 'cus_a' })
|
|
52
|
+
expect(await StripeCustomers.get(db, 'org_1')).toStrictEqual(record)
|
|
53
|
+
expect(await StripeCustomers.get(db, 'org_missing')).toBeUndefined()
|
|
54
|
+
await db.close()
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('behavior: production and sandbox rows coexist per org and read disjointly', async () => {
|
|
58
|
+
const db = create()
|
|
59
|
+
await db.migrate()
|
|
60
|
+
const production = await StripeCustomers.create(db, { orgId: 'org_1', stripeCustomerId: 'cus_live' }) // prettier-ignore
|
|
61
|
+
const sandbox = await StripeCustomers.create(db, { environment: 'sandbox', orgId: 'org_1', stripeCustomerId: 'cus_test' }) // prettier-ignore
|
|
62
|
+
expect(sandbox.environment).toBe('sandbox')
|
|
63
|
+
expect(await StripeCustomers.get(db, 'org_1')).toStrictEqual(production)
|
|
64
|
+
expect(await StripeCustomers.get(db, 'org_1', 'sandbox')).toStrictEqual(sandbox)
|
|
65
|
+
await db.close()
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
describe('getByCustomer', () => {
|
|
70
|
+
test('behavior: reads by Stripe customer id; undefined for unknown customers', async () => {
|
|
71
|
+
const db = create()
|
|
72
|
+
await db.migrate()
|
|
73
|
+
const record = await StripeCustomers.create(db, { orgId: 'org_1', stripeCustomerId: 'cus_a' })
|
|
74
|
+
expect(await StripeCustomers.getByCustomer(db, 'cus_a')).toStrictEqual(record)
|
|
75
|
+
expect(await StripeCustomers.getByCustomer(db, 'cus_unknown')).toBeUndefined()
|
|
76
|
+
await db.close()
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
describe('setStatus', () => {
|
|
81
|
+
test('behavior: persists the status and bumps updatedAt', async () => {
|
|
82
|
+
const db = create()
|
|
83
|
+
await db.migrate()
|
|
84
|
+
const record = await StripeCustomers.create(db, { orgId: 'org_1', stripeCustomerId: 'cus_a' })
|
|
85
|
+
const updated = await StripeCustomers.setStatus(db, 'org_1', 'active')
|
|
86
|
+
expect(updated?.status).toMatchInlineSnapshot(`"active"`)
|
|
87
|
+
expect(updated?.createdAt).toBe(record.createdAt)
|
|
88
|
+
await db.close()
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test('behavior: undefined when the org has no billing account', async () => {
|
|
92
|
+
const db = create()
|
|
93
|
+
await db.migrate()
|
|
94
|
+
expect(await StripeCustomers.setStatus(db, 'org_missing', 'active')).toBeUndefined()
|
|
95
|
+
await db.close()
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
describe('deleteByOrg', () => {
|
|
100
|
+
test('behavior: deletes the row; false when absent', async () => {
|
|
101
|
+
const db = create()
|
|
102
|
+
await db.migrate()
|
|
103
|
+
await StripeCustomers.create(db, { orgId: 'org_1', stripeCustomerId: 'cus_a' })
|
|
104
|
+
expect(await StripeCustomers.deleteByOrg(db, 'org_1')).toBe(true)
|
|
105
|
+
expect(await StripeCustomers.get(db, 'org_1')).toBeUndefined()
|
|
106
|
+
expect(await StripeCustomers.deleteByOrg(db, 'org_1')).toBe(false)
|
|
107
|
+
await db.close()
|
|
108
|
+
})
|
|
109
|
+
})
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type { Selectable } from 'kysely'
|
|
2
|
+
|
|
3
|
+
import type * as Db from '../Db.js'
|
|
4
|
+
import type * as db_Schema from '../Schema.js'
|
|
5
|
+
|
|
6
|
+
/** Columns of the `stripe_customers` table, derived from `Schema.StripeCustomer`. */
|
|
7
|
+
export type Table = db_Schema.StripeCustomer
|
|
8
|
+
|
|
9
|
+
/** A stored Stripe billing-source row. */
|
|
10
|
+
export type Record = Selectable<Table>
|
|
11
|
+
|
|
12
|
+
/** A billing status. */
|
|
13
|
+
export type Status = Record['status']
|
|
14
|
+
|
|
15
|
+
/** Billing statuses; `active` alone opens the production sponsorship gate. */
|
|
16
|
+
export const statuses = ['active', 'canceled', 'none', 'past_due'] as const satisfies readonly Status[] // prettier-ignore
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Inserts a Stripe billing source (status `none`) and returns the stored record.
|
|
20
|
+
* Idempotent per organization: a concurrent first-checkout race resolves to
|
|
21
|
+
* the winner's row — the loser's Stripe customer stays orphaned in Stripe,
|
|
22
|
+
* which is harmless.
|
|
23
|
+
*
|
|
24
|
+
* @param db - The database.
|
|
25
|
+
* @param input - The Stripe source to insert.
|
|
26
|
+
* @returns The stored record.
|
|
27
|
+
*/
|
|
28
|
+
export async function create(db: Db.Db, input: create.Input): Promise<Record> {
|
|
29
|
+
const now = new Date().toISOString()
|
|
30
|
+
const environment = input.environment ?? 'production'
|
|
31
|
+
const inserted = await db.kysely
|
|
32
|
+
.insertInto('stripe_customers')
|
|
33
|
+
.values({
|
|
34
|
+
createdAt: now,
|
|
35
|
+
environment,
|
|
36
|
+
orgId: input.orgId,
|
|
37
|
+
status: 'none',
|
|
38
|
+
stripeCustomerId: input.stripeCustomerId,
|
|
39
|
+
updatedAt: now,
|
|
40
|
+
})
|
|
41
|
+
// Untargeted so it holds against either the org pkey (before the pkey drop
|
|
42
|
+
// release) or the `(org_id, environment)` unique index (after).
|
|
43
|
+
.onConflict((oc) => oc.doNothing())
|
|
44
|
+
.returningAll()
|
|
45
|
+
.executeTakeFirst()
|
|
46
|
+
if (inserted) return inserted
|
|
47
|
+
return await db.kysely
|
|
48
|
+
.selectFrom('stripe_customers')
|
|
49
|
+
.selectAll()
|
|
50
|
+
.where('orgId', '=', input.orgId)
|
|
51
|
+
.where('environment', '=', environment)
|
|
52
|
+
.executeTakeFirstOrThrow()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export declare namespace create {
|
|
56
|
+
/** Fields accepted when inserting a Stripe billing source. */
|
|
57
|
+
type Input = {
|
|
58
|
+
/** Environment this source backs; defaults to `production`. */
|
|
59
|
+
environment?: Record['environment'] | undefined
|
|
60
|
+
/** Organization id (`org_…`) the Stripe source belongs to. */
|
|
61
|
+
orgId: string
|
|
62
|
+
/** Stripe customer id backing this account. */
|
|
63
|
+
stripeCustomerId: string
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Deletes an organization's Stripe billing source. The Stripe customer is never
|
|
69
|
+
* deleted; its invoice history stays in Stripe for audit.
|
|
70
|
+
*
|
|
71
|
+
* @param db - The database.
|
|
72
|
+
* @param orgId - The organization id (`org_…`).
|
|
73
|
+
* @returns Whether a Stripe source existed and was deleted.
|
|
74
|
+
*/
|
|
75
|
+
export async function deleteByOrg(db: Db.Db, orgId: string): Promise<boolean> {
|
|
76
|
+
const result = await db.kysely
|
|
77
|
+
.deleteFrom('stripe_customers')
|
|
78
|
+
.where('orgId', '=', orgId)
|
|
79
|
+
.executeTakeFirst()
|
|
80
|
+
return result.numDeletedRows > 0n
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Reads an organization's Stripe billing source.
|
|
85
|
+
*
|
|
86
|
+
* @param db - The database.
|
|
87
|
+
* @param orgId - The organization id (`org_…`).
|
|
88
|
+
* @param environment - The environment to read; defaults to `production`.
|
|
89
|
+
* @returns The stored record, or `undefined` when the org has none.
|
|
90
|
+
*/
|
|
91
|
+
export function get(
|
|
92
|
+
db: Db.Db,
|
|
93
|
+
orgId: string,
|
|
94
|
+
environment: Record['environment'] = 'production',
|
|
95
|
+
): Promise<Record | undefined> {
|
|
96
|
+
return db.kysely
|
|
97
|
+
.selectFrom('stripe_customers')
|
|
98
|
+
.selectAll()
|
|
99
|
+
.where('orgId', '=', orgId)
|
|
100
|
+
.where('environment', '=', environment)
|
|
101
|
+
.executeTakeFirst()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Lists every billing source in an environment — the driver set for the cron
|
|
106
|
+
* job that reconciles cached billing snapshots (e.g. API-key records) against
|
|
107
|
+
* this authoritative table.
|
|
108
|
+
*
|
|
109
|
+
* @param db - The database.
|
|
110
|
+
* @param environment - The environment to enumerate.
|
|
111
|
+
* @returns The stored records for that environment.
|
|
112
|
+
*/
|
|
113
|
+
export function listByEnvironment(
|
|
114
|
+
db: Db.Db,
|
|
115
|
+
environment: Record['environment'],
|
|
116
|
+
): Promise<Record[]> {
|
|
117
|
+
return db.kysely
|
|
118
|
+
.selectFrom('stripe_customers')
|
|
119
|
+
.selectAll()
|
|
120
|
+
.where('environment', '=', environment)
|
|
121
|
+
.execute()
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Reads a Stripe billing source by its customer id — the webhook handler's
|
|
126
|
+
* lookup for inbound events.
|
|
127
|
+
*
|
|
128
|
+
* @param db - The database.
|
|
129
|
+
* @param stripeCustomerId - The Stripe customer id.
|
|
130
|
+
* @returns The stored record, or `undefined` for unknown customers.
|
|
131
|
+
*/
|
|
132
|
+
export function getByCustomer(db: Db.Db, stripeCustomerId: string): Promise<Record | undefined> {
|
|
133
|
+
return db.kysely
|
|
134
|
+
.selectFrom('stripe_customers')
|
|
135
|
+
.selectAll()
|
|
136
|
+
.where('stripeCustomerId', '=', stripeCustomerId)
|
|
137
|
+
.executeTakeFirst()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Sets an organization's billing status.
|
|
142
|
+
*
|
|
143
|
+
* @param db - The database.
|
|
144
|
+
* @param orgId - The organization id (`org_…`).
|
|
145
|
+
* @param status - The derived status to persist.
|
|
146
|
+
* @param environment - The environment to update; defaults to `production`.
|
|
147
|
+
* @returns The updated record, or `undefined` when the org has no Stripe source.
|
|
148
|
+
*/
|
|
149
|
+
export function setStatus(
|
|
150
|
+
db: Db.Db,
|
|
151
|
+
orgId: string,
|
|
152
|
+
status: Status,
|
|
153
|
+
environment: Record['environment'] = 'production',
|
|
154
|
+
): Promise<Record | undefined> {
|
|
155
|
+
return db.kysely
|
|
156
|
+
.updateTable('stripe_customers')
|
|
157
|
+
.set({ status, updatedAt: new Date().toISOString() })
|
|
158
|
+
.where('orgId', '=', orgId)
|
|
159
|
+
.where('environment', '=', environment)
|
|
160
|
+
.returningAll()
|
|
161
|
+
.executeTakeFirst()
|
|
162
|
+
}
|
package/src/db/tables/users.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Selectable } from 'kysely'
|
|
2
|
-
import { nanoid } from 'nanoid'
|
|
3
2
|
|
|
3
|
+
import * as Id from '../../internal/Id.js'
|
|
4
4
|
import type * as Db from '../Db.js'
|
|
5
5
|
import type * as db_Schema from '../Schema.js'
|
|
6
6
|
|
|
@@ -49,7 +49,7 @@ export async function upsertByAddress(db: Db.Db, input: upsertByAddress.Input):
|
|
|
49
49
|
const now = new Date().toISOString()
|
|
50
50
|
const inserted = await db.kysely
|
|
51
51
|
.insertInto('users')
|
|
52
|
-
.values({ address, createdAt: now, email: null, id:
|
|
52
|
+
.values({ address, createdAt: now, email: null, id: Id.generate('usr'), updatedAt: now })
|
|
53
53
|
.onConflict((oc) => oc.column('address').doNothing())
|
|
54
54
|
.returningAll()
|
|
55
55
|
.executeTakeFirst()
|
|
@@ -9,11 +9,30 @@ import * as Utils from './utils.js'
|
|
|
9
9
|
/**
|
|
10
10
|
* Sponsorship approval callback. The candidate always carries its `chainId`:
|
|
11
11
|
* raw submissions deserialize it, and fills validate only prepared/filled
|
|
12
|
-
* transactions.
|
|
12
|
+
* transactions. Anything but `true` refuses; raw submissions surface a named
|
|
13
|
+
* reason to the caller while fills fall back to an unsponsored fill.
|
|
13
14
|
*/
|
|
14
15
|
export type Validate = (
|
|
15
16
|
request: Transaction.TransactionRequest & { chainId?: number | Hex.Hex | undefined },
|
|
16
|
-
) =>
|
|
17
|
+
) => Validation | Promise<Validation>
|
|
18
|
+
|
|
19
|
+
/** A sponsorship verdict: `true` sponsors; `false` or a named reason refuses. */
|
|
20
|
+
export type Validation =
|
|
21
|
+
| boolean
|
|
22
|
+
| 'billing_past_due'
|
|
23
|
+
| 'billing_required'
|
|
24
|
+
| 'fee_token_unsupported'
|
|
25
|
+
| 'spend_limit_exceeded'
|
|
26
|
+
| 'tx_fee_limit_exceeded'
|
|
27
|
+
|
|
28
|
+
/** Refusal messages keyed by named validation reason. */
|
|
29
|
+
const refusalMessages = {
|
|
30
|
+
billing_past_due: 'Billing past due.',
|
|
31
|
+
billing_required: 'Billing required.',
|
|
32
|
+
fee_token_unsupported: 'Fee token unsupported.',
|
|
33
|
+
spend_limit_exceeded: 'Spend limit exceeded.',
|
|
34
|
+
tx_fee_limit_exceeded: 'Transaction fee limit exceeded.',
|
|
35
|
+
} as const satisfies Record<Exclude<Validation, boolean>, string>
|
|
17
36
|
|
|
18
37
|
/** Returns sponsor metadata for `eth_fillTransaction` responses. */
|
|
19
38
|
export function getSponsor(options: getSponsor.Options): getSponsor.ReturnType {
|
|
@@ -49,10 +68,13 @@ export declare namespace getSponsor {
|
|
|
49
68
|
export async function shouldSponsor(options: shouldSponsor.Options) {
|
|
50
69
|
const { sender, transaction, validate } = options
|
|
51
70
|
if (!validate) return true
|
|
52
|
-
|
|
71
|
+
// Named refusal reasons collapse to false: fills fall back to an
|
|
72
|
+
// unsponsored fill instead of surfacing the reason.
|
|
73
|
+
const verdict = await validate({
|
|
53
74
|
...transaction,
|
|
54
75
|
from: sender,
|
|
55
76
|
} as Transaction.TransactionRequest)
|
|
77
|
+
return verdict === true
|
|
56
78
|
}
|
|
57
79
|
|
|
58
80
|
export declare namespace shouldSponsor {
|
|
@@ -166,10 +188,15 @@ export async function handleRawTransaction(options: handleRawTransaction.Options
|
|
|
166
188
|
feeToken_chain
|
|
167
189
|
const transaction_sponsored = feeToken ? { ...transaction, feeToken } : transaction
|
|
168
190
|
|
|
169
|
-
if (validate
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
191
|
+
if (validate) {
|
|
192
|
+
const verdict = await validate(transaction_sponsored as Transaction.TransactionRequest)
|
|
193
|
+
if (verdict !== true)
|
|
194
|
+
throw new RpcResponse.InvalidParamsError(
|
|
195
|
+
verdict === false
|
|
196
|
+
? { message: 'Sponsorship rejected.' }
|
|
197
|
+
: { data: { code: verdict }, message: refusalMessages[verdict] },
|
|
198
|
+
)
|
|
199
|
+
}
|
|
173
200
|
|
|
174
201
|
const envelope = TxEnvelopeTempo.from(transaction_sponsored as never)
|
|
175
202
|
const signPayload = TxEnvelopeTempo.getFeePayerSignPayload(envelope, { sender })
|
|
@@ -80,18 +80,30 @@ export function normalizeTempoTransaction(value: Record<string, unknown> | undef
|
|
|
80
80
|
|
|
81
81
|
/** Returns a raw JSON-RPC error response object (not wrapped in a `Response`). */
|
|
82
82
|
export function rpcErrorJson(request: RpcRequest.RpcRequest, error: unknown) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
// Serialize error instances to plain objects: `Error.message` is
|
|
84
|
+
// non-enumerable, so instances stringify onto the wire without it.
|
|
85
|
+
if (
|
|
86
|
+
error instanceof RpcResponse.InvalidParamsError ||
|
|
87
|
+
error instanceof RpcResponse.MethodNotSupportedError
|
|
88
|
+
)
|
|
89
|
+
return RpcResponse.from(
|
|
90
|
+
{
|
|
91
|
+
error: {
|
|
92
|
+
code: error.code,
|
|
93
|
+
message: error.message,
|
|
94
|
+
...(error.data !== undefined ? { data: error.data } : {}),
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
{ request },
|
|
98
|
+
)
|
|
88
99
|
|
|
89
100
|
if ((error as { name?: string | undefined }).name === 'ZodError')
|
|
90
101
|
return RpcResponse.from(
|
|
91
102
|
{
|
|
92
|
-
error:
|
|
103
|
+
error: {
|
|
104
|
+
code: RpcResponse.InvalidParamsError.code,
|
|
93
105
|
message: (error as Error).message,
|
|
94
|
-
}
|
|
106
|
+
},
|
|
95
107
|
},
|
|
96
108
|
{ request },
|
|
97
109
|
)
|
package/src/index.ts
CHANGED