tapimo 0.5.3 → 0.7.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/dist/App.d.ts +15830 -7220
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +9 -1
- package/dist/App.js.map +1 -1
- package/dist/Scope.d.ts +4 -0
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +5 -0
- package/dist/Scope.js.map +1 -1
- package/dist/admin/App.d.ts +1011 -412
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/App.js +4 -0
- package/dist/admin/App.js.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +2 -1
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/early-access.d.ts +148 -0
- package/dist/admin/apps/early-access.d.ts.map +1 -0
- package/dist/admin/apps/early-access.js +133 -0
- package/dist/admin/apps/early-access.js.map +1 -0
- package/dist/admin/apps/organizations.d.ts +124 -1
- package/dist/admin/apps/organizations.d.ts.map +1 -1
- package/dist/admin/apps/organizations.js +97 -6
- package/dist/admin/apps/organizations.js.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +526 -1
- package/dist/admin/apps/verified-tokens.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.js +214 -1
- package/dist/admin/apps/verified-tokens.js.map +1 -1
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.d.ts.map +1 -1
- package/dist/admin/ui.generated.js +95 -75
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/analytics/Analytics.d.ts.map +1 -1
- package/dist/analytics/Analytics.js +20 -0
- package/dist/analytics/Analytics.js.map +1 -1
- package/dist/analytics/tables/requestEvents.d.ts +18 -0
- package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
- package/dist/analytics/tables/requestEvents.js +36 -7
- package/dist/analytics/tables/requestEvents.js.map +1 -1
- package/dist/apps/data/App.d.ts +6260 -4924
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +11 -6
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/FundingQuoteProviders.d.ts +34 -1
- package/dist/apps/data/FundingQuoteProviders.d.ts.map +1 -1
- package/dist/apps/data/FundingQuoteProviders.js +194 -16
- package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
- package/dist/apps/data/FxOracle.d.ts +71 -0
- package/dist/apps/data/FxOracle.d.ts.map +1 -0
- package/dist/apps/data/FxOracle.js +97 -0
- package/dist/apps/data/FxOracle.js.map +1 -0
- package/dist/apps/data/index.d.ts +1 -0
- package/dist/apps/data/index.d.ts.map +1 -1
- package/dist/apps/data/index.js +1 -0
- package/dist/apps/data/index.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +3599 -1382
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +250 -88
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/addresses.d.ts +872 -251
- package/dist/apps/data/routes/addresses.d.ts.map +1 -1
- package/dist/apps/data/routes/addresses.js +11 -9
- package/dist/apps/data/routes/addresses.js.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +77 -26
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +88 -45
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/blocks.d.ts +39 -39
- package/dist/apps/data/routes/coingecko.d.ts +54 -54
- package/dist/apps/data/routes/exchanges.d.ts +856 -105
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
- package/dist/apps/data/routes/exchanges.js +518 -76
- package/dist/apps/data/routes/exchanges.js.map +1 -1
- package/dist/apps/data/routes/fee-amm.d.ts +81 -67
- package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
- package/dist/apps/data/routes/fee-amm.js +76 -91
- package/dist/apps/data/routes/fee-amm.js.map +1 -1
- package/dist/apps/data/routes/funding.d.ts +80 -36
- package/dist/apps/data/routes/funding.d.ts.map +1 -1
- package/dist/apps/data/routes/funding.js +65 -31
- package/dist/apps/data/routes/funding.js.map +1 -1
- package/dist/apps/data/routes/receipts.d.ts +392 -315
- package/dist/apps/data/routes/receipts.d.ts.map +1 -1
- package/dist/apps/data/routes/receipts.js +152 -77
- package/dist/apps/data/routes/receipts.js.map +1 -1
- package/dist/apps/data/routes/tokens.d.ts +167 -174
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/tokens.js +15 -13
- package/dist/apps/data/routes/tokens.js.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +277 -536
- package/dist/apps/data/routes/transactions.d.ts.map +1 -1
- package/dist/apps/data/routes/transactions.js +96 -77
- package/dist/apps/data/routes/transactions.js.map +1 -1
- package/dist/apps/data/routes/transfers.d.ts +157 -81
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +272 -130
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/valuation.d.ts +307 -0
- package/dist/apps/data/routes/valuation.d.ts.map +1 -0
- package/dist/apps/data/routes/valuation.js +292 -0
- package/dist/apps/data/routes/valuation.js.map +1 -0
- package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
- package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/verified-tokens.js +7 -5
- package/dist/apps/data/routes/verified-tokens.js.map +1 -1
- package/dist/apps/data/routes/webhooks.d.ts +103 -123
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +124 -25
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/index.d.ts +2 -1
- package/dist/apps/index.d.ts.map +1 -1
- package/dist/apps/index.js +2 -1
- package/dist/apps/index.js.map +1 -1
- package/dist/apps/management/App.d.ts +8554 -1692
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/App.js +12 -1
- package/dist/apps/management/App.js.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +7 -0
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +17 -3
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +157 -162
- package/dist/apps/management/routes/billing.d.ts.map +1 -1
- package/dist/apps/management/routes/billing.js +31 -23
- package/dist/apps/management/routes/billing.js.map +1 -1
- package/dist/apps/management/routes/faucet.d.ts +232 -0
- package/dist/apps/management/routes/faucet.d.ts.map +1 -0
- package/dist/apps/management/routes/faucet.js +154 -0
- package/dist/apps/management/routes/faucet.js.map +1 -0
- package/dist/apps/management/routes/invitations.d.ts +60 -60
- package/dist/apps/management/routes/invite-links.d.ts +49 -20
- package/dist/apps/management/routes/invite-links.d.ts.map +1 -1
- package/dist/apps/management/routes/invite-links.js +26 -1
- package/dist/apps/management/routes/invite-links.js.map +1 -1
- package/dist/apps/management/routes/me.d.ts +6 -6
- package/dist/apps/management/routes/members.d.ts +6 -6
- package/dist/apps/management/routes/orgs.d.ts +42 -42
- package/dist/apps/management/routes/orgs.d.ts.map +1 -1
- package/dist/apps/management/routes/orgs.js +5 -0
- package/dist/apps/management/routes/orgs.js.map +1 -1
- package/dist/apps/management/routes/projects.d.ts +44 -44
- package/dist/apps/management/routes/usage.d.ts +3 -0
- package/dist/apps/management/routes/usage.d.ts.map +1 -1
- package/dist/apps/management/routes/usage.js +4 -0
- package/dist/apps/management/routes/usage.js.map +1 -1
- package/dist/apps/management/routes/verified-token-requests.d.ts +697 -0
- package/dist/apps/management/routes/verified-token-requests.d.ts.map +1 -0
- package/dist/apps/management/routes/verified-token-requests.js +271 -0
- package/dist/apps/management/routes/verified-token-requests.js.map +1 -0
- package/dist/apps/management/routes/webhooks.d.ts +6977 -0
- package/dist/apps/management/routes/webhooks.d.ts.map +1 -0
- package/dist/apps/management/routes/webhooks.js +573 -0
- package/dist/apps/management/routes/webhooks.js.map +1 -0
- package/dist/apps/mpp/App.d.ts +363 -0
- package/dist/apps/mpp/App.d.ts.map +1 -0
- package/dist/apps/mpp/App.js +72 -0
- package/dist/apps/mpp/App.js.map +1 -0
- package/dist/apps/mpp/Mpp.d.ts +178 -0
- package/dist/apps/mpp/Mpp.d.ts.map +1 -0
- package/dist/apps/mpp/Mpp.js +183 -0
- package/dist/apps/mpp/Mpp.js.map +1 -0
- package/dist/apps/mpp/index.d.ts +3 -0
- package/dist/apps/mpp/index.d.ts.map +1 -0
- package/dist/apps/mpp/index.js +3 -0
- package/dist/apps/mpp/index.js.map +1 -0
- package/dist/apps/relay/App.d.ts.map +1 -1
- package/dist/apps/relay/App.js +56 -9
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/db/Db.d.ts +6 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +110 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +53 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +56 -0
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/earlyAccess.d.ts +50 -0
- package/dist/db/tables/earlyAccess.d.ts.map +1 -0
- package/dist/db/tables/earlyAccess.js +63 -0
- package/dist/db/tables/earlyAccess.js.map +1 -0
- package/dist/db/tables/enabledBillingSources.d.ts +73 -0
- package/dist/db/tables/enabledBillingSources.d.ts.map +1 -0
- package/dist/db/tables/enabledBillingSources.js +69 -0
- package/dist/db/tables/enabledBillingSources.js.map +1 -0
- package/dist/db/tables/invitations.d.ts.map +1 -1
- package/dist/db/tables/invitations.js +3 -0
- package/dist/db/tables/invitations.js.map +1 -1
- package/dist/db/tables/inviteLinks.d.ts +10 -2
- package/dist/db/tables/inviteLinks.d.ts.map +1 -1
- package/dist/db/tables/inviteLinks.js +10 -1
- package/dist/db/tables/inviteLinks.js.map +1 -1
- package/dist/db/tables/organizations.d.ts +5 -2
- package/dist/db/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +13 -2
- package/dist/db/tables/organizations.js.map +1 -1
- package/dist/db/tables/sponsoredTransactions.d.ts +2 -0
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
- package/dist/db/tables/sponsoredTransactions.js +2 -0
- package/dist/db/tables/sponsoredTransactions.js.map +1 -1
- package/dist/db/tables/verifiedTokenRequests.d.ts +94 -0
- package/dist/db/tables/verifiedTokenRequests.d.ts.map +1 -0
- package/dist/db/tables/verifiedTokenRequests.js +120 -0
- package/dist/db/tables/verifiedTokenRequests.js.map +1 -0
- package/dist/db/tables/webhookSubscriptions.d.ts +14 -0
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
- package/dist/db/tables/webhookSubscriptions.js +16 -0
- package/dist/db/tables/webhookSubscriptions.js.map +1 -1
- package/dist/handlers/internal/utils.d.ts +1 -16
- package/dist/handlers/internal/utils.d.ts.map +1 -1
- package/dist/handlers/internal/utils.js +17 -6
- package/dist/handlers/internal/utils.js.map +1 -1
- package/dist/handlers/relay.d.ts.map +1 -1
- package/dist/handlers/relay.js +37 -5
- package/dist/handlers/relay.js.map +1 -1
- package/dist/internal/Auth.d.ts +11 -2
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +14 -47
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/FundingQuotes.d.ts +147 -47
- package/dist/internal/FundingQuotes.d.ts.map +1 -1
- package/dist/internal/FundingQuotes.js +119 -33
- package/dist/internal/FundingQuotes.js.map +1 -1
- package/dist/internal/Log.d.ts +37 -5
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +52 -3
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +24 -0
- package/dist/internal/MetricSink.js.map +1 -1
- package/dist/internal/OpenApi.d.ts +2 -2
- package/dist/internal/OpenApi.d.ts.map +1 -1
- package/dist/internal/OpenApi.js +3 -0
- package/dist/internal/OpenApi.js.map +1 -1
- package/dist/internal/Response.d.ts +2 -2
- package/dist/internal/Response.d.ts.map +1 -1
- package/dist/internal/Response.js +12 -4
- package/dist/internal/Response.js.map +1 -1
- package/dist/internal/Schema.d.ts +14 -5
- package/dist/internal/Schema.d.ts.map +1 -1
- package/dist/internal/Schema.js +23 -5
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/Value.d.ts +18 -0
- package/dist/internal/Value.d.ts.map +1 -1
- package/dist/internal/Value.js +11 -1
- package/dist/internal/Value.js.map +1 -1
- package/dist/internal/VerifiedTokens.d.ts.map +1 -1
- package/dist/internal/VerifiedTokens.js +10 -1
- package/dist/internal/VerifiedTokens.js.map +1 -1
- package/dist/internal/Webhooks.d.ts +24 -3
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +34 -8
- package/dist/internal/Webhooks.js.map +1 -1
- package/package.json +7 -2
- package/src/App.test.ts +9 -4
- package/src/App.ts +23 -3
- package/src/Client.test-d.ts +733 -20
- package/src/Scope.ts +5 -0
- package/src/admin/App.test.ts +4 -0
- package/src/admin/App.ts +4 -0
- package/src/admin/apps/early-access.test.ts +125 -0
- package/src/admin/apps/early-access.ts +176 -0
- package/src/admin/apps/organizations.test.ts +86 -2
- package/src/admin/apps/organizations.ts +155 -31
- package/src/admin/apps/scopes.test.ts +5 -0
- package/src/admin/apps/verified-tokens.test.ts +201 -0
- package/src/admin/apps/verified-tokens.ts +532 -286
- package/src/admin/ui/plugin.ts +2 -1
- package/src/admin/ui/src/lib/early-access.ts +63 -0
- package/src/admin/ui/src/lib/organizations.test.ts +81 -1
- package/src/admin/ui/src/lib/organizations.ts +92 -0
- package/src/admin/ui/src/lib/verified-tokens.ts +79 -0
- package/src/admin/ui/src/routeTree.gen.ts +33 -2
- package/src/admin/ui/src/routes/__root.tsx +2 -1
- package/src/admin/ui/src/routes/api-keys.tsx +1 -1
- package/src/admin/ui/src/routes/early-access.tsx +162 -0
- package/src/admin/ui/src/routes/organizations_.$orgId.tsx +103 -0
- package/src/admin/ui/src/routes/verified-tokens.tsx +230 -19
- package/src/admin/ui.generated.ts +95 -94
- package/src/analytics/Analytics.test.ts +83 -0
- package/src/analytics/Analytics.ts +20 -0
- package/src/analytics/tables/requestEvents.ts +60 -7
- package/src/apps/composition.test.ts +11 -2
- package/src/apps/data/App.test.ts +1 -1
- package/src/apps/data/App.ts +22 -14
- package/src/apps/data/FundingQuoteProviders.test.ts +297 -4
- package/src/apps/data/FundingQuoteProviders.ts +255 -29
- package/src/apps/data/FxOracle.test.ts +118 -0
- package/src/apps/data/FxOracle.ts +153 -0
- package/src/apps/data/index.ts +1 -0
- package/src/apps/data/routes/activities.test.ts +177 -3
- package/src/apps/data/routes/activities.ts +441 -132
- package/src/apps/data/routes/addresses.ts +19 -9
- package/src/apps/data/routes/balances.test.ts +148 -11
- package/src/apps/data/routes/balances.ts +140 -51
- package/src/apps/data/routes/exchanges.test.ts +544 -9
- package/src/apps/data/routes/exchanges.ts +690 -103
- package/src/apps/data/routes/fee-amm.test.ts +47 -23
- package/src/apps/data/routes/fee-amm.ts +98 -119
- package/src/apps/data/routes/funding.test.ts +113 -8
- package/src/apps/data/routes/funding.ts +77 -51
- package/src/apps/data/routes/receipts.test.ts +129 -23
- package/src/apps/data/routes/receipts.ts +205 -100
- package/src/apps/data/routes/tokens.test.ts +43 -0
- package/src/apps/data/routes/tokens.ts +28 -13
- package/src/apps/data/routes/transactions.test.ts +72 -19
- package/src/apps/data/routes/transactions.ts +162 -92
- package/src/apps/data/routes/transfers.test.ts +341 -31
- package/src/apps/data/routes/transfers.ts +358 -158
- package/src/apps/data/routes/valuation.test.ts +437 -0
- package/src/apps/data/routes/valuation.ts +423 -0
- package/src/apps/data/routes/verified-tokens.ts +14 -2
- package/src/apps/data/routes/webhooks.test.ts +56 -8
- package/src/apps/data/routes/webhooks.ts +144 -27
- package/src/apps/index.ts +2 -1
- package/src/apps/management/App.ts +13 -0
- package/src/apps/management/routes/api-keys.test.ts +14 -0
- package/src/apps/management/routes/api-keys.ts +27 -3
- package/src/apps/management/routes/billing.test.ts +152 -20
- package/src/apps/management/routes/billing.ts +34 -18
- package/src/apps/management/routes/faucet.test.ts +87 -0
- package/src/apps/management/routes/faucet.ts +201 -0
- package/src/apps/management/routes/invite-links.test.ts +117 -0
- package/src/apps/management/routes/invite-links.ts +43 -1
- package/src/apps/management/routes/me.test.ts +9 -99
- package/src/apps/management/routes/orgs.test.ts +45 -2
- package/src/apps/management/routes/orgs.ts +6 -0
- package/src/apps/management/routes/scopes.test.ts +1 -0
- package/src/apps/management/routes/usage.test.ts +6 -0
- package/src/apps/management/routes/usage.ts +7 -0
- package/src/apps/management/routes/verified-token-requests.test.ts +377 -0
- package/src/apps/management/routes/verified-token-requests.ts +397 -0
- package/src/apps/management/routes/webhooks.test.ts +426 -0
- package/src/apps/management/routes/webhooks.ts +672 -0
- package/src/apps/mpp/App.ts +86 -0
- package/src/apps/mpp/Mpp.test.ts +193 -0
- package/src/apps/mpp/Mpp.ts +353 -0
- package/src/apps/mpp/index.ts +2 -0
- package/src/apps/mpp/internal/Adapter.ts +11 -0
- package/src/apps/mpp/internal/Error.ts +9 -0
- package/src/apps/mpp/internal/Idempotency.ts +7 -0
- package/src/apps/mpp/internal/Screening.ts +10 -0
- package/src/apps/relay/App.test.ts +88 -6
- package/src/apps/relay/App.ts +61 -9
- package/src/db/Db.test.ts +19 -0
- package/src/db/Db.ts +125 -0
- package/src/db/Schema.ts +68 -0
- package/src/db/tables/earlyAccess.ts +86 -0
- package/src/db/tables/enabledBillingSources.test.ts +303 -0
- package/src/db/tables/enabledBillingSources.ts +118 -0
- package/src/db/tables/invitations.ts +3 -0
- package/src/db/tables/inviteLinks.ts +22 -3
- package/src/db/tables/organizations.test.ts +103 -0
- package/src/db/tables/organizations.ts +18 -2
- package/src/db/tables/sponsoredTransactions.ts +4 -0
- package/src/db/tables/verifiedTokenRequests.test.ts +200 -0
- package/src/db/tables/verifiedTokenRequests.ts +182 -0
- package/src/db/tables/webhookSubscriptions.ts +31 -0
- package/src/handlers/internal/utils.ts +22 -6
- package/src/handlers/relay.errors.test.ts +153 -0
- package/src/handlers/relay.ts +42 -7
- package/src/internal/Auth.test.ts +3 -3
- package/src/internal/Auth.ts +15 -47
- package/src/internal/FundingQuotes.test.ts +161 -6
- package/src/internal/FundingQuotes.ts +141 -39
- package/src/internal/Log.test-d.ts +21 -0
- package/src/internal/Log.test.ts +141 -0
- package/src/internal/Log.ts +93 -6
- package/src/internal/MetricSink.test.ts +215 -0
- package/src/internal/MetricSink.ts +24 -0
- package/src/internal/OpenApi.ts +3 -0
- package/src/internal/Response.test.ts +28 -0
- package/src/internal/Response.ts +12 -4
- package/src/internal/Schema.ts +50 -5
- package/src/internal/Value.test.ts +20 -0
- package/src/internal/Value.ts +24 -1
- package/src/internal/VerifiedTokens.ts +9 -1
- package/src/internal/Webhooks.test.ts +140 -0
- package/src/internal/Webhooks.ts +53 -9
|
@@ -2,8 +2,10 @@ import { nanoid } from 'nanoid'
|
|
|
2
2
|
import Stripe from 'stripe'
|
|
3
3
|
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
|
|
4
4
|
|
|
5
|
+
import * as ApiKeys from '../../../ApiKeys.js'
|
|
5
6
|
import * as TestApp from '../../../../test/App.js'
|
|
6
7
|
import * as TestStripe from '../../../../test/Stripe.js'
|
|
8
|
+
import * as EnabledBillingSources from '../../../db/tables/enabledBillingSources.js'
|
|
7
9
|
import * as Memberships from '../../../db/tables/memberships.js'
|
|
8
10
|
import * as SponsoredTransactions from '../../../db/tables/sponsoredTransactions.js'
|
|
9
11
|
import * as StripeCustomers from '../../../db/tables/stripeCustomers.js'
|
|
@@ -59,13 +61,18 @@ function billingWithSandbox(): App.Billing {
|
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
function createApp(
|
|
62
|
-
options: {
|
|
64
|
+
options: {
|
|
65
|
+
billing?: App.Billing | undefined
|
|
66
|
+
db?: TestApp.create.Options['db']
|
|
67
|
+
kv?: { store: ReturnType<typeof TestApp.kvStore> }
|
|
68
|
+
} = {},
|
|
63
69
|
) {
|
|
64
70
|
// prettier-ignore
|
|
65
71
|
return TestApp.create({
|
|
66
72
|
auth: { superAdmin: { secret } },
|
|
67
73
|
billing: options.billing ?? billing(),
|
|
68
74
|
db: options.db ?? TestApp.database(),
|
|
75
|
+
...(options.kv ? { kv: options.kv } : {}),
|
|
69
76
|
session: { wallet: { origin } },
|
|
70
77
|
})
|
|
71
78
|
}
|
|
@@ -98,6 +105,20 @@ async function seeded(app: ReturnType<typeof createApp>) {
|
|
|
98
105
|
return { cookie, orgId: id }
|
|
99
106
|
}
|
|
100
107
|
|
|
108
|
+
/** Seeds an owned organization with Stripe setup enabled. */
|
|
109
|
+
async function seededWithStripe(
|
|
110
|
+
app: ReturnType<typeof createApp>,
|
|
111
|
+
db: ReturnType<typeof TestApp.database>,
|
|
112
|
+
) {
|
|
113
|
+
const result = await seeded(app)
|
|
114
|
+
await EnabledBillingSources.add(db, {
|
|
115
|
+
createdBy: 'test',
|
|
116
|
+
orgId: result.orgId,
|
|
117
|
+
source: 'stripe',
|
|
118
|
+
})
|
|
119
|
+
return result
|
|
120
|
+
}
|
|
121
|
+
|
|
101
122
|
describe('GET /orgs/:orgId/billing', () => {
|
|
102
123
|
test('behavior: synthesizes none for orgs without a billing source', async () => {
|
|
103
124
|
const app = createApp()
|
|
@@ -106,6 +127,7 @@ describe('GET /orgs/:orgId/billing', () => {
|
|
|
106
127
|
expect(response.status).toBe(200)
|
|
107
128
|
expect(await response.json()).toMatchInlineSnapshot(`
|
|
108
129
|
{
|
|
130
|
+
"enabledSources": [],
|
|
109
131
|
"spend": {
|
|
110
132
|
"amount": "0",
|
|
111
133
|
"currency": "usd",
|
|
@@ -129,6 +151,28 @@ describe('GET /orgs/:orgId/billing', () => {
|
|
|
129
151
|
expect(body.updatedAt).toBeDefined()
|
|
130
152
|
})
|
|
131
153
|
|
|
154
|
+
test('behavior: lists organization-wide sources in canonical order', async () => {
|
|
155
|
+
const db = TestApp.database()
|
|
156
|
+
const app = createApp({ billing: billingWithSandbox(), db })
|
|
157
|
+
const { cookie, orgId } = await seeded(app)
|
|
158
|
+
await EnabledBillingSources.add(db, { createdBy: 'test', orgId, source: 'tempo' })
|
|
159
|
+
await EnabledBillingSources.add(db, { createdBy: 'test', orgId, source: 'stripe' })
|
|
160
|
+
|
|
161
|
+
const production = await app.request(`/v1/orgs/${orgId}/billing`, { headers: { cookie } })
|
|
162
|
+
const sandbox = await app.request(`/v1/orgs/${orgId}/billing?environment=sandbox`, {
|
|
163
|
+
headers: { cookie },
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
expect(await production.json()).toMatchObject({
|
|
167
|
+
enabledSources: ['stripe', 'tempo'],
|
|
168
|
+
status: 'none',
|
|
169
|
+
})
|
|
170
|
+
expect(await sandbox.json()).toMatchObject({
|
|
171
|
+
enabledSources: ['stripe', 'tempo'],
|
|
172
|
+
status: 'none',
|
|
173
|
+
})
|
|
174
|
+
})
|
|
175
|
+
|
|
132
176
|
test('behavior: the super admin reads any org', async () => {
|
|
133
177
|
const db = TestApp.database()
|
|
134
178
|
const app = createApp({ db })
|
|
@@ -155,7 +199,7 @@ describe('GET /orgs/:orgId/billing', () => {
|
|
|
155
199
|
expect(response.status).toBe(401)
|
|
156
200
|
})
|
|
157
201
|
|
|
158
|
-
test('behavior:
|
|
202
|
+
test('behavior: reads database-backed state when Stripe is unconfigured', async () => {
|
|
159
203
|
const app = TestApp.create({
|
|
160
204
|
auth: { superAdmin: { secret } },
|
|
161
205
|
db: TestApp.database(),
|
|
@@ -163,9 +207,18 @@ describe('GET /orgs/:orgId/billing', () => {
|
|
|
163
207
|
})
|
|
164
208
|
const { cookie, orgId } = await seeded(app)
|
|
165
209
|
const response = await app.request(`/v1/orgs/${orgId}/billing`, { headers: { cookie } })
|
|
166
|
-
expect(response.status).toBe(
|
|
167
|
-
|
|
168
|
-
|
|
210
|
+
expect(response.status).toBe(200)
|
|
211
|
+
expect(await response.json()).toMatchInlineSnapshot(`
|
|
212
|
+
{
|
|
213
|
+
"enabledSources": [],
|
|
214
|
+
"spend": {
|
|
215
|
+
"amount": "0",
|
|
216
|
+
"currency": "usd",
|
|
217
|
+
"period": "month",
|
|
218
|
+
},
|
|
219
|
+
"status": "none",
|
|
220
|
+
}
|
|
221
|
+
`)
|
|
169
222
|
})
|
|
170
223
|
})
|
|
171
224
|
|
|
@@ -192,6 +245,7 @@ describe('PATCH /orgs/:orgId/billing', () => {
|
|
|
192
245
|
expect(response.status).toBe(200)
|
|
193
246
|
expect(await response.json()).toMatchInlineSnapshot(`
|
|
194
247
|
{
|
|
248
|
+
"enabledSources": [],
|
|
195
249
|
"spend": {
|
|
196
250
|
"amount": "0",
|
|
197
251
|
"currency": "usd",
|
|
@@ -284,7 +338,7 @@ describe('PATCH /orgs/:orgId/billing', () => {
|
|
|
284
338
|
expect(response.status).toBe(404)
|
|
285
339
|
})
|
|
286
340
|
|
|
287
|
-
test('behavior:
|
|
341
|
+
test('behavior: updates database-backed state when Stripe is unconfigured', async () => {
|
|
288
342
|
const app = TestApp.create({
|
|
289
343
|
auth: { superAdmin: { secret } },
|
|
290
344
|
db: TestApp.database(),
|
|
@@ -293,9 +347,12 @@ describe('PATCH /orgs/:orgId/billing', () => {
|
|
|
293
347
|
const { cookie, orgId } = await seeded(app)
|
|
294
348
|
const response = await app.request(
|
|
295
349
|
`/v1/orgs/${orgId}/billing`,
|
|
296
|
-
patch(cookie, { spendLimit:
|
|
350
|
+
patch(cookie, { spendLimit: { amount: '250' } }),
|
|
297
351
|
)
|
|
298
|
-
expect(response.status).toBe(
|
|
352
|
+
expect(response.status).toBe(200)
|
|
353
|
+
expect(
|
|
354
|
+
((await response.json()) as { spendLimit?: { amount: string } }).spendLimit?.amount,
|
|
355
|
+
).toBe('250')
|
|
299
356
|
})
|
|
300
357
|
})
|
|
301
358
|
|
|
@@ -345,8 +402,9 @@ describe('POST /orgs/:orgId/billing/stripe/checkout', () => {
|
|
|
345
402
|
})
|
|
346
403
|
|
|
347
404
|
test('behavior: an unreachable Stripe maps to the upstream error envelope', async () => {
|
|
348
|
-
const
|
|
349
|
-
const {
|
|
405
|
+
const db = TestApp.database()
|
|
406
|
+
const app = createApp({ db })
|
|
407
|
+
const { cookie, orgId } = await seededWithStripe(app, db)
|
|
350
408
|
// First Stripe call the route makes is the customer create; the connection
|
|
351
409
|
// is refused for real (no transport fake).
|
|
352
410
|
const response = await app.request(`/v1/orgs/${orgId}/billing/stripe/checkout`, post(cookie))
|
|
@@ -356,6 +414,66 @@ describe('POST /orgs/:orgId/billing/stripe/checkout', () => {
|
|
|
356
414
|
})
|
|
357
415
|
})
|
|
358
416
|
|
|
417
|
+
describe('billing source access', () => {
|
|
418
|
+
test('behavior: checkout requires Stripe to be enabled', async () => {
|
|
419
|
+
const app = TestApp.create({
|
|
420
|
+
auth: { superAdmin: { secret } },
|
|
421
|
+
db: TestApp.database(),
|
|
422
|
+
session: { wallet: { origin } },
|
|
423
|
+
})
|
|
424
|
+
const { cookie, orgId } = await seeded(app)
|
|
425
|
+
const response = await app.request(`/v1/orgs/${orgId}/billing/stripe/checkout`, post(cookie))
|
|
426
|
+
expect(response.status).toBe(403)
|
|
427
|
+
const { requestId, ...body } = (await response.json()) as { requestId?: string }
|
|
428
|
+
expect(requestId).toBeDefined()
|
|
429
|
+
expect(body).toMatchInlineSnapshot(`
|
|
430
|
+
{
|
|
431
|
+
"error": {
|
|
432
|
+
"code": "billing_source_disabled",
|
|
433
|
+
"message": "Stripe billing is not enabled for this organization",
|
|
434
|
+
},
|
|
435
|
+
}
|
|
436
|
+
`)
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
test('behavior: the source gate replaces temporary billing early access', async () => {
|
|
440
|
+
const db = TestApp.database()
|
|
441
|
+
const app = createApp({ db })
|
|
442
|
+
const { cookie, orgId } = await seededWithStripe(app, db)
|
|
443
|
+
|
|
444
|
+
// The unreachable Stripe upstream proves source access, not user early access, is the gate.
|
|
445
|
+
const response = await app.request(`/v1/orgs/${orgId}/billing/stripe/checkout`, post(cookie))
|
|
446
|
+
expect(response.status).toBe(502)
|
|
447
|
+
expect(((await response.json()) as { error: { code: string } }).error.code).toBe('upstream_error') // prettier-ignore
|
|
448
|
+
})
|
|
449
|
+
|
|
450
|
+
test('behavior: an enabled management key reaches Stripe', async () => {
|
|
451
|
+
const db = TestApp.database()
|
|
452
|
+
const store = TestApp.kvStore({ keys: [] })
|
|
453
|
+
const app = createApp({ db, kv: { store } })
|
|
454
|
+
const { orgId } = await seededWithStripe(app, db)
|
|
455
|
+
const { token } = await ApiKeys.mint(store, { orgId, scopes: ['management:write'] })
|
|
456
|
+
|
|
457
|
+
const response = await app.request(`/v1/orgs/${orgId}/billing/stripe/checkout`, {
|
|
458
|
+
headers: { 'tempo-api-key': token },
|
|
459
|
+
method: 'POST',
|
|
460
|
+
})
|
|
461
|
+
expect(response.status).toBe(502)
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
test('behavior: an enabled super admin reaches Stripe', async () => {
|
|
465
|
+
const db = TestApp.database()
|
|
466
|
+
const app = createApp({ db })
|
|
467
|
+
const { orgId } = await seededWithStripe(app, db)
|
|
468
|
+
|
|
469
|
+
const response = await app.request(`/v1/orgs/${orgId}/billing/stripe/checkout`, {
|
|
470
|
+
...as(secret),
|
|
471
|
+
method: 'POST',
|
|
472
|
+
})
|
|
473
|
+
expect(response.status).toBe(502)
|
|
474
|
+
})
|
|
475
|
+
})
|
|
476
|
+
|
|
359
477
|
describe('POST /orgs/:orgId/billing/stripe/manage', () => {
|
|
360
478
|
test('behavior: 404 before any billing exists', async () => {
|
|
361
479
|
const app = createApp()
|
|
@@ -440,6 +558,7 @@ describe('DELETE /orgs/:orgId', () => {
|
|
|
440
558
|
const db = TestApp.database()
|
|
441
559
|
const app = createApp({ db })
|
|
442
560
|
const { cookie, orgId } = await seeded(app)
|
|
561
|
+
await EnabledBillingSources.add(db, { createdBy: 'test', orgId, source: 'stripe' })
|
|
443
562
|
await StripeCustomers.create(db, { orgId, stripeCustomerId: 'cus_cascade' })
|
|
444
563
|
|
|
445
564
|
const response = await app.request(`/v1/orgs/${orgId}`, {
|
|
@@ -447,19 +566,30 @@ describe('DELETE /orgs/:orgId', () => {
|
|
|
447
566
|
method: 'DELETE',
|
|
448
567
|
})
|
|
449
568
|
expect(response.status).toBe(200)
|
|
569
|
+
expect(await EnabledBillingSources.listByOrg(db, orgId)).toEqual([])
|
|
450
570
|
expect(await StripeCustomers.get(db, orgId)).toBeUndefined()
|
|
451
571
|
})
|
|
452
572
|
})
|
|
453
573
|
|
|
454
574
|
describe('environment scoping', () => {
|
|
455
|
-
test('behavior: sandbox
|
|
575
|
+
test('behavior: sandbox billing state remains readable without a Stripe source', async () => {
|
|
456
576
|
const app = createApp()
|
|
457
577
|
const { cookie, orgId } = await seeded(app)
|
|
458
578
|
const response = await app.request(`/v1/orgs/${orgId}/billing?environment=sandbox`, {
|
|
459
579
|
headers: { cookie },
|
|
460
580
|
})
|
|
461
|
-
expect(response.status).toBe(
|
|
462
|
-
expect(
|
|
581
|
+
expect(response.status).toBe(200)
|
|
582
|
+
expect(await response.json()).toMatchInlineSnapshot(`
|
|
583
|
+
{
|
|
584
|
+
"enabledSources": [],
|
|
585
|
+
"spend": {
|
|
586
|
+
"amount": "0",
|
|
587
|
+
"currency": "usd",
|
|
588
|
+
"period": "month",
|
|
589
|
+
},
|
|
590
|
+
"status": "none",
|
|
591
|
+
}
|
|
592
|
+
`)
|
|
463
593
|
})
|
|
464
594
|
|
|
465
595
|
test('behavior: rejects an unknown environment', async () => {
|
|
@@ -645,7 +775,7 @@ describe.runIf(TestStripe.secretKey)('stripe sandbox (direct)', () => {
|
|
|
645
775
|
test('checkout provisions distinct customers per environment', { timeout: 30_000 }, async () => {
|
|
646
776
|
const db = TestApp.database()
|
|
647
777
|
const app = createApp({ billing: sandboxBillingBoth(), db })
|
|
648
|
-
const { cookie, orgId } = await
|
|
778
|
+
const { cookie, orgId } = await seededWithStripe(app, db)
|
|
649
779
|
|
|
650
780
|
const prod = await app.request(`/v1/orgs/${orgId}/billing/stripe/checkout`, post(cookie))
|
|
651
781
|
const sbx = await app.request(`/v1/orgs/${orgId}/billing/stripe/checkout?environment=sandbox`, post(cookie)) // prettier-ignore
|
|
@@ -672,7 +802,7 @@ describe.runIf(TestStripe.secretKey)('stripe sandbox (direct)', () => {
|
|
|
672
802
|
test('checkout provisions a real customer and setup session', { timeout: 30_000 }, async () => {
|
|
673
803
|
const db = TestApp.database()
|
|
674
804
|
const app = createApp({ billing: sandboxBilling(), db })
|
|
675
|
-
const { cookie, orgId } = await
|
|
805
|
+
const { cookie, orgId } = await seededWithStripe(app, db)
|
|
676
806
|
|
|
677
807
|
const response = await app.request(`/v1/orgs/${orgId}/billing/stripe/checkout`, post(cookie))
|
|
678
808
|
// Track for teardown before asserting: a failed assertion must not leak
|
|
@@ -720,7 +850,7 @@ describe.runIf(TestStripe.secretKey)('stripe sandbox (direct)', () => {
|
|
|
720
850
|
// prettier-ignore
|
|
721
851
|
const db = TestApp.database()
|
|
722
852
|
const app = createApp({ billing: sandboxBilling(), db })
|
|
723
|
-
const { cookie, orgId } = await
|
|
853
|
+
const { cookie, orgId } = await seededWithStripe(app, db)
|
|
724
854
|
const checkout = await app.request(`/v1/orgs/${orgId}/billing/stripe/checkout`, post(cookie))
|
|
725
855
|
const customerId = (await StripeCustomers.get(db, orgId))?.stripeCustomerId
|
|
726
856
|
if (customerId) TestStripe.track(customerId)
|
|
@@ -733,8 +863,9 @@ describe.runIf(TestStripe.secretKey)('stripe sandbox (direct)', () => {
|
|
|
733
863
|
invoice_settings: { default_payment_method: attached.paymentMethod },
|
|
734
864
|
})
|
|
735
865
|
await StripeCustomers.setStatus(db, orgId, 'active')
|
|
866
|
+
await EnabledBillingSources.remove(db, { orgId, source: 'stripe' })
|
|
736
867
|
|
|
737
|
-
//
|
|
868
|
+
// Disabling setup does not block management of the existing Stripe source.
|
|
738
869
|
const list = await app.request(`/v1/orgs/${orgId}/billing/payment-methods`, {
|
|
739
870
|
headers: { cookie },
|
|
740
871
|
})
|
|
@@ -789,7 +920,7 @@ describe.runIf(TestStripe.secretKey)('stripe sandbox (direct)', () => {
|
|
|
789
920
|
// prettier-ignore
|
|
790
921
|
const db = TestApp.database()
|
|
791
922
|
const app = createApp({ billing: sandboxBilling(), db })
|
|
792
|
-
const { cookie, orgId } = await
|
|
923
|
+
const { cookie, orgId } = await seededWithStripe(app, db)
|
|
793
924
|
const checkout = await app.request(`/v1/orgs/${orgId}/billing/stripe/checkout`, post(cookie))
|
|
794
925
|
const customerId = (await StripeCustomers.get(db, orgId))?.stripeCustomerId
|
|
795
926
|
if (customerId) TestStripe.track(customerId)
|
|
@@ -819,11 +950,12 @@ describe.runIf(TestStripe.secretKey)('stripe sandbox (direct)', () => {
|
|
|
819
950
|
|
|
820
951
|
const db = TestApp.database()
|
|
821
952
|
const app = createApp({ billing: sandboxBilling(), db })
|
|
822
|
-
const { cookie, orgId } = await
|
|
953
|
+
const { cookie, orgId } = await seededWithStripe(app, db)
|
|
823
954
|
const checkout = await app.request(`/v1/orgs/${orgId}/billing/stripe/checkout`, post(cookie))
|
|
824
955
|
const customerId = (await StripeCustomers.get(db, orgId))?.stripeCustomerId
|
|
825
956
|
if (customerId) TestStripe.track(customerId)
|
|
826
957
|
expect(checkout.status).toBe(200)
|
|
958
|
+
await EnabledBillingSources.remove(db, { orgId, source: 'stripe' })
|
|
827
959
|
|
|
828
960
|
const response = await app.request(`/v1/orgs/${orgId}/billing/stripe/manage`, post(cookie))
|
|
829
961
|
expect(response.status).toBe(200)
|
|
@@ -852,7 +984,7 @@ describe.runIf(TestStripe.secretKey)('stripe sandbox (live delivery)', () => {
|
|
|
852
984
|
})
|
|
853
985
|
|
|
854
986
|
test('attach → active, detach → none, delete → canceled', { timeout: 300_000 }, async () => {
|
|
855
|
-
const { cookie, orgId } = await
|
|
987
|
+
const { cookie, orgId } = await seededWithStripe(app, db)
|
|
856
988
|
const checkout = await app.request(`/v1/orgs/${orgId}/billing/stripe/checkout`, post(cookie))
|
|
857
989
|
const customerId = (await StripeCustomers.get(db, orgId))?.stripeCustomerId
|
|
858
990
|
if (customerId) TestStripe.track(customerId)
|
|
@@ -6,6 +6,7 @@ import * as ApiKeys from '../../../ApiKeys.js'
|
|
|
6
6
|
import * as Auth from '../../../internal/Auth.js'
|
|
7
7
|
import * as BillingSettings from '../../../db/tables/billingSettings.js'
|
|
8
8
|
import * as Db from '../../../db/Db.js'
|
|
9
|
+
import * as EnabledBillingSources from '../../../db/tables/enabledBillingSources.js'
|
|
9
10
|
import * as OpenApi from '../../../internal/OpenApi.js'
|
|
10
11
|
import * as Response from '../../../internal/Response.js'
|
|
11
12
|
import * as Schema from '../../../internal/Schema.js'
|
|
@@ -113,6 +114,12 @@ export namespace schema {
|
|
|
113
114
|
/** An organization's billing state. */
|
|
114
115
|
export const Billing = Schema.describe(
|
|
115
116
|
z.object({
|
|
117
|
+
enabledSources: z
|
|
118
|
+
.array(z.enum(EnabledBillingSources.sources))
|
|
119
|
+
.check(
|
|
120
|
+
z.describe('Billing sources this organization may set up.'),
|
|
121
|
+
z.meta({ examples: [['stripe']] }),
|
|
122
|
+
),
|
|
116
123
|
spend: z
|
|
117
124
|
.optional(
|
|
118
125
|
z.object({
|
|
@@ -234,7 +241,7 @@ export namespace schema {
|
|
|
234
241
|
}
|
|
235
242
|
|
|
236
243
|
/**
|
|
237
|
-
* Mounts billing routes. Reads require membership or scoped API key access; organizations without
|
|
244
|
+
* Mounts billing routes. Reads require membership or scoped API key access; organizations without provider state return `none`.
|
|
238
245
|
*/
|
|
239
246
|
export function billing() {
|
|
240
247
|
return new Hono<Environment>()
|
|
@@ -255,7 +262,6 @@ export function billing() {
|
|
|
255
262
|
responses: OpenApi.responses({
|
|
256
263
|
errors: {
|
|
257
264
|
404: 'No accessible organization was found for this id.',
|
|
258
|
-
501: 'Billing is not configured on this deployment.',
|
|
259
265
|
},
|
|
260
266
|
success: { description: "The organization's billing state.", schema: schema.Billing },
|
|
261
267
|
}),
|
|
@@ -276,10 +282,7 @@ export function billing() {
|
|
|
276
282
|
message: 'Check the query parameters and try again.',
|
|
277
283
|
})
|
|
278
284
|
|
|
279
|
-
const billing = c.get('billing')
|
|
280
|
-
if (!billing) return billingUnconfigured(c)
|
|
281
285
|
const environment = c.req.valid('query').environment
|
|
282
|
-
if (!stripeFor(billing, environment)) return billingUnconfigured(c)
|
|
283
286
|
|
|
284
287
|
try {
|
|
285
288
|
return c.json(
|
|
@@ -313,7 +316,6 @@ export function billing() {
|
|
|
313
316
|
errors: {
|
|
314
317
|
403: 'Requires the owner role.',
|
|
315
318
|
404: 'No accessible organization was found for this id.',
|
|
316
|
-
501: 'Billing is not configured on this deployment.',
|
|
317
319
|
},
|
|
318
320
|
success: {
|
|
319
321
|
description: "The organization's updated billing state.",
|
|
@@ -342,10 +344,7 @@ export function billing() {
|
|
|
342
344
|
message: 'Check the request body and try again.',
|
|
343
345
|
})
|
|
344
346
|
|
|
345
|
-
const billing = c.get('billing')
|
|
346
|
-
if (!billing) return billingUnconfigured(c)
|
|
347
347
|
const environment = c.req.valid('query').environment
|
|
348
|
-
if (!stripeFor(billing, environment)) return billingUnconfigured(c)
|
|
349
348
|
|
|
350
349
|
const body = c.req.valid('json')
|
|
351
350
|
const org = Auth.org(c)
|
|
@@ -394,7 +393,10 @@ export function billing() {
|
|
|
394
393
|
operationId: 'createStripeCheckout',
|
|
395
394
|
responses: OpenApi.responses({
|
|
396
395
|
errors: {
|
|
397
|
-
403:
|
|
396
|
+
403: {
|
|
397
|
+
codes: ['api_key_forbidden', 'billing_source_disabled', 'forbidden'],
|
|
398
|
+
description: 'Requires the owner role and Stripe billing enabled.',
|
|
399
|
+
},
|
|
398
400
|
404: 'No accessible organization was found for this id.',
|
|
399
401
|
501: 'Billing is not configured on this deployment.',
|
|
400
402
|
},
|
|
@@ -409,6 +411,7 @@ export function billing() {
|
|
|
409
411
|
async (c) => {
|
|
410
412
|
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
411
413
|
if (Auth.narrowScope) return Auth.ensureOrgError(c)
|
|
414
|
+
if (Auth.narrowOrgRole) return Auth.ensureOrgRoleError(c)
|
|
412
415
|
if (OpenApi.narrowValidation)
|
|
413
416
|
return OpenApi.validationError(c, {
|
|
414
417
|
code: 'param_invalid',
|
|
@@ -420,16 +423,19 @@ export function billing() {
|
|
|
420
423
|
message: 'Check the query parameters and try again.',
|
|
421
424
|
})
|
|
422
425
|
|
|
423
|
-
const billing = c.get('billing')
|
|
424
|
-
if (!billing) return billingUnconfigured(c)
|
|
425
|
-
const environment = c.req.valid('query').environment
|
|
426
|
-
const source = stripeFor(billing, environment)
|
|
427
|
-
if (!source) return billingUnconfigured(c)
|
|
428
|
-
|
|
429
|
-
const { client } = source
|
|
430
426
|
const org = Auth.org(c)
|
|
431
427
|
const db = Db.get(c.get('db'))
|
|
432
428
|
try {
|
|
429
|
+
if (!(await EnabledBillingSources.isEnabled(db, { orgId: org.id, source: 'stripe' })))
|
|
430
|
+
return billingSourceDisabled(c)
|
|
431
|
+
|
|
432
|
+
const billing = c.get('billing')
|
|
433
|
+
if (!billing) return billingUnconfigured(c)
|
|
434
|
+
const environment = c.req.valid('query').environment
|
|
435
|
+
const source = stripeFor(billing, environment)
|
|
436
|
+
if (!source) return billingUnconfigured(c)
|
|
437
|
+
|
|
438
|
+
const { client } = source
|
|
433
439
|
const existing = await StripeCustomers.get(db, org.id, environment)
|
|
434
440
|
const customerId = existing
|
|
435
441
|
? existing.stripeCustomerId
|
|
@@ -880,7 +886,8 @@ async function billingBody(
|
|
|
880
886
|
environment: ResourceEnvironment,
|
|
881
887
|
) {
|
|
882
888
|
const db = Db.get(c.get('db'))
|
|
883
|
-
const [record, settings] = await Promise.all([
|
|
889
|
+
const [enabledSources, record, settings] = await Promise.all([
|
|
890
|
+
EnabledBillingSources.listByOrg(db, orgId),
|
|
884
891
|
StripeCustomers.get(db, orgId, environment),
|
|
885
892
|
BillingSettings.get(db, orgId, environment),
|
|
886
893
|
])
|
|
@@ -895,6 +902,7 @@ async function billingBody(
|
|
|
895
902
|
since: core_Billing.periodStart(period),
|
|
896
903
|
})
|
|
897
904
|
return {
|
|
905
|
+
enabledSources: enabledSources.map((record) => record.source),
|
|
898
906
|
spend: { amount: core_Billing.fromBaseUnits(spend), currency, period },
|
|
899
907
|
...(settings?.spendLimit != null
|
|
900
908
|
? { spendLimit: { amount: settings.spendLimit, currency, period } }
|
|
@@ -954,6 +962,14 @@ function billingNotFound(c: Context<Environment>) {
|
|
|
954
962
|
})
|
|
955
963
|
}
|
|
956
964
|
|
|
965
|
+
function billingSourceDisabled(c: Context<Environment>) {
|
|
966
|
+
return Response.error(c, {
|
|
967
|
+
code: 'billing_source_disabled',
|
|
968
|
+
message: 'Stripe billing is not enabled for this organization',
|
|
969
|
+
status: 403,
|
|
970
|
+
})
|
|
971
|
+
}
|
|
972
|
+
|
|
957
973
|
function billingUnconfigured(c: Context<Environment>) {
|
|
958
974
|
return Response.error(c, {
|
|
959
975
|
code: 'billing_unconfigured',
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
|
|
2
|
+
|
|
3
|
+
import * as TestApp from '../../../../test/App.js'
|
|
4
|
+
import * as Faucet from './faucet.js'
|
|
5
|
+
import * as Orgs from './orgs.js'
|
|
6
|
+
|
|
7
|
+
const origin = 'http://localhost'
|
|
8
|
+
|
|
9
|
+
function createApp() {
|
|
10
|
+
return TestApp.create({
|
|
11
|
+
rateLimit: { now: () => new Date('2026-01-01T00:00:00.000Z') },
|
|
12
|
+
session: { wallet: { origin } },
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function session(app: TestApp.signIn.App) {
|
|
17
|
+
const account = privateKeyToAccount(generatePrivateKey())
|
|
18
|
+
const { cookie } = await TestApp.signIn(app, account)
|
|
19
|
+
return cookie!
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function organization(app: ReturnType<typeof createApp>, cookie: string) {
|
|
23
|
+
const response = await app.request('/v1/orgs', {
|
|
24
|
+
body: JSON.stringify({ name: 'Acme' }),
|
|
25
|
+
headers: { 'content-type': 'application/json', cookie },
|
|
26
|
+
method: 'POST',
|
|
27
|
+
})
|
|
28
|
+
return TestApp.json(response, Orgs.schema.Organization)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('faucet', () => {
|
|
32
|
+
test('funds a member account and rate limits repeated funding', async () => {
|
|
33
|
+
const app = createApp()
|
|
34
|
+
const cookie = await session(app)
|
|
35
|
+
const org = await organization(app, cookie)
|
|
36
|
+
const recipient = privateKeyToAccount(generatePrivateKey()).address
|
|
37
|
+
|
|
38
|
+
const funded = await app.request(`/v1/orgs/${org.id}/faucet`, {
|
|
39
|
+
body: JSON.stringify({ address: recipient }),
|
|
40
|
+
headers: { 'content-type': 'application/json', cookie },
|
|
41
|
+
method: 'POST',
|
|
42
|
+
})
|
|
43
|
+
expect(funded.status).toBe(200)
|
|
44
|
+
const result = await TestApp.json(funded, Faucet.schema.FundResponse)
|
|
45
|
+
expect(result.tokens.length).toBeGreaterThan(0)
|
|
46
|
+
expect(
|
|
47
|
+
result.tokens.every(
|
|
48
|
+
(entry) =>
|
|
49
|
+
entry.token.symbol.length > 0 &&
|
|
50
|
+
BigInt(entry.amount.baseUnits) > 0n &&
|
|
51
|
+
entry.amount.currency === entry.token.currency &&
|
|
52
|
+
entry.amount.decimals === entry.token.decimals &&
|
|
53
|
+
/^\d+(\.\d+)?$/.test(entry.amount.formatted),
|
|
54
|
+
),
|
|
55
|
+
).toBe(true)
|
|
56
|
+
expect(result.transactionHashes).toEqual(result.tokens.map((entry) => entry.transactionHash))
|
|
57
|
+
|
|
58
|
+
const repeated = await app.request(`/v1/orgs/${org.id}/faucet`, {
|
|
59
|
+
body: JSON.stringify({ address: recipient }),
|
|
60
|
+
headers: { 'content-type': 'application/json', cookie },
|
|
61
|
+
method: 'POST',
|
|
62
|
+
})
|
|
63
|
+
expect(repeated.status).toBe(429)
|
|
64
|
+
expect(repeated.headers.get('ratelimit-scope')).toBe('faucet-address')
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('requires membership and a valid address', async () => {
|
|
68
|
+
const app = createApp()
|
|
69
|
+
const owner = await session(app)
|
|
70
|
+
const org = await organization(app, owner)
|
|
71
|
+
const stranger = await session(app)
|
|
72
|
+
|
|
73
|
+
const inaccessible = await app.request(`/v1/orgs/${org.id}/faucet`, {
|
|
74
|
+
body: JSON.stringify({ address: privateKeyToAccount(generatePrivateKey()).address }),
|
|
75
|
+
headers: { 'content-type': 'application/json', cookie: stranger },
|
|
76
|
+
method: 'POST',
|
|
77
|
+
})
|
|
78
|
+
expect(inaccessible.status).toBe(404)
|
|
79
|
+
|
|
80
|
+
const invalid = await app.request(`/v1/orgs/${org.id}/faucet`, {
|
|
81
|
+
body: JSON.stringify({ address: 'not-an-address' }),
|
|
82
|
+
headers: { 'content-type': 'application/json', cookie: owner },
|
|
83
|
+
method: 'POST',
|
|
84
|
+
})
|
|
85
|
+
expect(invalid.status).toBe(400)
|
|
86
|
+
})
|
|
87
|
+
})
|