tapimo 0.2.0 → 0.4.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.
Files changed (362) hide show
  1. package/README.md +36 -8
  2. package/dist/ApiKey.d.ts +1 -0
  3. package/dist/ApiKey.d.ts.map +1 -1
  4. package/dist/ApiKey.js +5 -2
  5. package/dist/ApiKey.js.map +1 -1
  6. package/dist/ApiKeys.d.ts +28 -1
  7. package/dist/ApiKeys.d.ts.map +1 -1
  8. package/dist/ApiKeys.js +34 -1
  9. package/dist/ApiKeys.js.map +1 -1
  10. package/dist/App.d.ts +7532 -4850
  11. package/dist/App.d.ts.map +1 -1
  12. package/dist/App.js +11 -3
  13. package/dist/App.js.map +1 -1
  14. package/dist/Scope.d.ts +11 -2
  15. package/dist/Scope.d.ts.map +1 -1
  16. package/dist/Scope.js +14 -3
  17. package/dist/Scope.js.map +1 -1
  18. package/dist/admin/App.d.ts +63 -53
  19. package/dist/admin/App.d.ts.map +1 -1
  20. package/dist/admin/apps/api-keys.d.ts +7 -0
  21. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  22. package/dist/admin/apps/scopes.d.ts +8 -0
  23. package/dist/admin/apps/scopes.d.ts.map +1 -1
  24. package/dist/admin/apps/scopes.js +3 -0
  25. package/dist/admin/apps/scopes.js.map +1 -1
  26. package/dist/admin/apps/verified-tokens.d.ts +57 -57
  27. package/dist/admin/ui.generated.d.ts +1 -1
  28. package/dist/admin/ui.generated.js +13 -13
  29. package/dist/admin/ui.generated.js.map +1 -1
  30. package/dist/analytics/Analytics.d.ts +151 -0
  31. package/dist/analytics/Analytics.d.ts.map +1 -0
  32. package/dist/analytics/Analytics.js +212 -0
  33. package/dist/analytics/Analytics.js.map +1 -0
  34. package/dist/analytics/tables/requestEvents.d.ts +215 -0
  35. package/dist/analytics/tables/requestEvents.d.ts.map +1 -0
  36. package/dist/analytics/tables/requestEvents.js +264 -0
  37. package/dist/analytics/tables/requestEvents.js.map +1 -0
  38. package/dist/apps/data/App.d.ts +1049 -624
  39. package/dist/apps/data/App.d.ts.map +1 -1
  40. package/dist/apps/data/App.js +36 -0
  41. package/dist/apps/data/App.js.map +1 -1
  42. package/dist/apps/data/FundingRouteProviders.d.ts +54 -0
  43. package/dist/apps/data/FundingRouteProviders.d.ts.map +1 -0
  44. package/dist/apps/data/FundingRouteProviders.js +355 -0
  45. package/dist/apps/data/FundingRouteProviders.js.map +1 -0
  46. package/dist/apps/data/index.d.ts +1 -0
  47. package/dist/apps/data/index.d.ts.map +1 -1
  48. package/dist/apps/data/index.js +1 -0
  49. package/dist/apps/data/index.js.map +1 -1
  50. package/dist/apps/data/routes/balances.d.ts +45 -45
  51. package/dist/apps/data/routes/coingecko.d.ts +176 -176
  52. package/dist/apps/data/routes/exchanges.d.ts +455 -455
  53. package/dist/apps/data/routes/fee-amm.d.ts +101 -101
  54. package/dist/apps/data/routes/funding.d.ts +843 -0
  55. package/dist/apps/data/routes/funding.d.ts.map +1 -0
  56. package/dist/apps/data/routes/funding.js +443 -0
  57. package/dist/apps/data/routes/funding.js.map +1 -0
  58. package/dist/apps/data/routes/indexer.d.ts +5 -5
  59. package/dist/apps/data/routes/receipts.d.ts.map +1 -1
  60. package/dist/apps/data/routes/receipts.js +2 -8
  61. package/dist/apps/data/routes/receipts.js.map +1 -1
  62. package/dist/apps/data/routes/rpc.d.ts +29 -36
  63. package/dist/apps/data/routes/rpc.d.ts.map +1 -1
  64. package/dist/apps/data/routes/rpc.js +29 -1
  65. package/dist/apps/data/routes/rpc.js.map +1 -1
  66. package/dist/apps/data/routes/tokenlist.d.ts +36 -36
  67. package/dist/apps/data/routes/transfers.d.ts +56 -56
  68. package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
  69. package/dist/apps/data/routes/webhooks.d.ts +45 -45
  70. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  71. package/dist/apps/data/routes/webhooks.js +8 -8
  72. package/dist/apps/data/routes/webhooks.js.map +1 -1
  73. package/dist/apps/docs/App.d.ts +5 -3
  74. package/dist/apps/docs/App.d.ts.map +1 -1
  75. package/dist/apps/docs/App.js +28 -96
  76. package/dist/apps/docs/App.js.map +1 -1
  77. package/dist/apps/docs/reference.d.ts +21 -0
  78. package/dist/apps/docs/reference.d.ts.map +1 -0
  79. package/dist/apps/docs/reference.js +112 -0
  80. package/dist/apps/docs/reference.js.map +1 -0
  81. package/dist/apps/index.d.ts +3 -2
  82. package/dist/apps/index.d.ts.map +1 -1
  83. package/dist/apps/index.js +3 -2
  84. package/dist/apps/index.js.map +1 -1
  85. package/dist/apps/{platform → management}/App.d.ts +3015 -738
  86. package/dist/apps/management/App.d.ts.map +1 -0
  87. package/dist/apps/{platform → management}/App.js +20 -10
  88. package/dist/apps/management/App.js.map +1 -0
  89. package/dist/apps/management/Billing.d.ts +279 -0
  90. package/dist/apps/management/Billing.d.ts.map +1 -0
  91. package/dist/apps/management/Billing.js +477 -0
  92. package/dist/apps/management/Billing.js.map +1 -0
  93. package/dist/apps/management/index.d.ts +3 -0
  94. package/dist/apps/management/index.d.ts.map +1 -0
  95. package/dist/apps/management/index.js +3 -0
  96. package/dist/apps/management/index.js.map +1 -0
  97. package/dist/apps/{platform → management}/routes/api-keys.d.ts +341 -49
  98. package/dist/apps/management/routes/api-keys.d.ts.map +1 -0
  99. package/dist/apps/{platform → management}/routes/api-keys.js +116 -14
  100. package/dist/apps/management/routes/api-keys.js.map +1 -0
  101. package/dist/apps/management/routes/billing.d.ts +1520 -0
  102. package/dist/apps/management/routes/billing.d.ts.map +1 -0
  103. package/dist/apps/management/routes/billing.js +795 -0
  104. package/dist/apps/management/routes/billing.js.map +1 -0
  105. package/dist/apps/{platform → management}/routes/invitations.d.ts +202 -84
  106. package/dist/apps/management/routes/invitations.d.ts.map +1 -0
  107. package/dist/apps/{platform → management}/routes/invitations.js +59 -1
  108. package/dist/apps/management/routes/invitations.js.map +1 -0
  109. package/dist/apps/{platform → management}/routes/me.d.ts +17 -17
  110. package/dist/apps/management/routes/me.d.ts.map +1 -0
  111. package/dist/apps/management/routes/me.js.map +1 -0
  112. package/dist/apps/management/routes/members.d.ts.map +1 -0
  113. package/dist/apps/management/routes/members.js.map +1 -0
  114. package/dist/apps/{platform → management}/routes/orgs.d.ts +50 -36
  115. package/dist/apps/management/routes/orgs.d.ts.map +1 -0
  116. package/dist/apps/{platform → management}/routes/orgs.js +14 -3
  117. package/dist/apps/management/routes/orgs.js.map +1 -0
  118. package/dist/apps/{platform → management}/routes/projects.d.ts +97 -97
  119. package/dist/apps/{platform → management}/routes/projects.d.ts.map +1 -1
  120. package/dist/apps/management/routes/projects.js.map +1 -0
  121. package/dist/apps/management/routes/scopes.d.ts +107 -0
  122. package/dist/apps/management/routes/scopes.d.ts.map +1 -0
  123. package/dist/apps/management/routes/scopes.js +66 -0
  124. package/dist/apps/management/routes/scopes.js.map +1 -0
  125. package/dist/apps/management/routes/usage.d.ts +588 -0
  126. package/dist/apps/management/routes/usage.d.ts.map +1 -0
  127. package/dist/apps/management/routes/usage.js +391 -0
  128. package/dist/apps/management/routes/usage.js.map +1 -0
  129. package/dist/apps/mcp.d.ts +76 -0
  130. package/dist/apps/mcp.d.ts.map +1 -0
  131. package/dist/apps/mcp.js +188 -0
  132. package/dist/apps/mcp.js.map +1 -0
  133. package/dist/apps/relay/App.d.ts +4 -1
  134. package/dist/apps/relay/App.d.ts.map +1 -1
  135. package/dist/apps/relay/App.js +118 -10
  136. package/dist/apps/relay/App.js.map +1 -1
  137. package/dist/apps/relay/Sponsorships.d.ts.map +1 -1
  138. package/dist/apps/relay/Sponsorships.js +2 -1
  139. package/dist/apps/relay/Sponsorships.js.map +1 -1
  140. package/dist/cli/Cli.d.ts +8 -1
  141. package/dist/cli/Cli.d.ts.map +1 -1
  142. package/dist/cli/Cli.js +11 -3
  143. package/dist/cli/Cli.js.map +1 -1
  144. package/dist/cli/commands/admin.d.ts +4 -0
  145. package/dist/cli/commands/admin.d.ts.map +1 -1
  146. package/dist/cli/commands/admin.js +80 -1
  147. package/dist/cli/commands/admin.js.map +1 -1
  148. package/dist/db/Db.d.ts +7 -0
  149. package/dist/db/Db.d.ts.map +1 -1
  150. package/dist/db/Db.js +126 -0
  151. package/dist/db/Db.js.map +1 -1
  152. package/dist/db/Schema.d.ts +79 -0
  153. package/dist/db/Schema.d.ts.map +1 -1
  154. package/dist/db/Schema.js +88 -2
  155. package/dist/db/Schema.js.map +1 -1
  156. package/dist/db/tables/billingSettings.d.ts +55 -0
  157. package/dist/db/tables/billingSettings.d.ts.map +1 -0
  158. package/dist/db/tables/billingSettings.js +70 -0
  159. package/dist/db/tables/billingSettings.js.map +1 -0
  160. package/dist/db/tables/invitations.js +2 -2
  161. package/dist/db/tables/invitations.js.map +1 -1
  162. package/dist/db/tables/organizations.d.ts +6 -2
  163. package/dist/db/tables/organizations.d.ts.map +1 -1
  164. package/dist/db/tables/organizations.js +10 -4
  165. package/dist/db/tables/organizations.js.map +1 -1
  166. package/dist/db/tables/projects.js +2 -2
  167. package/dist/db/tables/projects.js.map +1 -1
  168. package/dist/db/tables/requestUsage.d.ts +121 -0
  169. package/dist/db/tables/requestUsage.d.ts.map +1 -0
  170. package/dist/db/tables/requestUsage.js +150 -0
  171. package/dist/db/tables/requestUsage.js.map +1 -0
  172. package/dist/db/tables/sponsoredTransactions.d.ts +119 -0
  173. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  174. package/dist/db/tables/sponsoredTransactions.js +155 -2
  175. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  176. package/dist/db/tables/stripeCustomers.d.ts +81 -0
  177. package/dist/db/tables/stripeCustomers.d.ts.map +1 -0
  178. package/dist/db/tables/stripeCustomers.js +120 -0
  179. package/dist/db/tables/stripeCustomers.js.map +1 -0
  180. package/dist/db/tables/users.js +2 -2
  181. package/dist/db/tables/users.js.map +1 -1
  182. package/dist/handlers/internal/sponsorship.d.ts +5 -2
  183. package/dist/handlers/internal/sponsorship.d.ts.map +1 -1
  184. package/dist/handlers/internal/sponsorship.js +19 -5
  185. package/dist/handlers/internal/sponsorship.js.map +1 -1
  186. package/dist/handlers/internal/utils.d.ts +10 -3
  187. package/dist/handlers/internal/utils.d.ts.map +1 -1
  188. package/dist/handlers/internal/utils.js +14 -6
  189. package/dist/handlers/internal/utils.js.map +1 -1
  190. package/dist/index.d.ts +1 -0
  191. package/dist/index.d.ts.map +1 -1
  192. package/dist/index.js +1 -0
  193. package/dist/index.js.map +1 -1
  194. package/dist/internal/Auth.d.ts +18 -4
  195. package/dist/internal/Auth.d.ts.map +1 -1
  196. package/dist/internal/Auth.js +149 -77
  197. package/dist/internal/Auth.js.map +1 -1
  198. package/dist/internal/Cache.d.ts.map +1 -1
  199. package/dist/internal/Cache.js +4 -1
  200. package/dist/internal/Cache.js.map +1 -1
  201. package/dist/internal/EdgeCache.d.ts +10 -0
  202. package/dist/internal/EdgeCache.d.ts.map +1 -1
  203. package/dist/internal/EdgeCache.js +39 -8
  204. package/dist/internal/EdgeCache.js.map +1 -1
  205. package/dist/internal/Fees.d.ts +27 -0
  206. package/dist/internal/Fees.d.ts.map +1 -0
  207. package/dist/internal/Fees.js +48 -0
  208. package/dist/internal/Fees.js.map +1 -0
  209. package/dist/internal/FundingRoutes.d.ts +915 -0
  210. package/dist/internal/FundingRoutes.d.ts.map +1 -0
  211. package/dist/internal/FundingRoutes.js +625 -0
  212. package/dist/internal/FundingRoutes.js.map +1 -0
  213. package/dist/internal/Id.d.ts +15 -0
  214. package/dist/internal/Id.d.ts.map +1 -0
  215. package/dist/internal/Id.js +22 -0
  216. package/dist/internal/Id.js.map +1 -0
  217. package/dist/internal/Log.d.ts +2 -0
  218. package/dist/internal/Log.d.ts.map +1 -1
  219. package/dist/internal/Log.js +3 -0
  220. package/dist/internal/Log.js.map +1 -1
  221. package/dist/internal/OpenApi.d.ts +10 -1
  222. package/dist/internal/OpenApi.d.ts.map +1 -1
  223. package/dist/internal/OpenApi.js +8 -1
  224. package/dist/internal/OpenApi.js.map +1 -1
  225. package/dist/internal/RateLimit.d.ts +25 -0
  226. package/dist/internal/RateLimit.d.ts.map +1 -1
  227. package/dist/internal/RateLimit.js +51 -0
  228. package/dist/internal/RateLimit.js.map +1 -1
  229. package/dist/internal/Response.d.ts +1 -1
  230. package/dist/internal/Response.d.ts.map +1 -1
  231. package/dist/internal/Schema.d.ts +8 -0
  232. package/dist/internal/Schema.d.ts.map +1 -1
  233. package/dist/internal/Schema.js +7 -0
  234. package/dist/internal/Schema.js.map +1 -1
  235. package/dist/internal/Webhooks.d.ts.map +1 -1
  236. package/dist/internal/Webhooks.js +3 -3
  237. package/dist/internal/Webhooks.js.map +1 -1
  238. package/package.json +8 -7
  239. package/src/ApiKey.ts +9 -2
  240. package/src/ApiKeys.test.ts +51 -0
  241. package/src/ApiKeys.ts +50 -1
  242. package/src/App.test.ts +33 -86718
  243. package/src/App.ts +26 -8
  244. package/src/Client.test-d.ts +353 -2
  245. package/src/Scope.ts +16 -3
  246. package/src/admin/apps/scopes.test.ts +6 -0
  247. package/src/admin/apps/scopes.ts +3 -0
  248. package/src/admin/ui/src/components/Page.tsx +2 -3
  249. package/src/admin/ui/src/routes/__root.tsx +2 -2
  250. package/src/admin/ui/src/routes/api-keys.tsx +2 -2
  251. package/src/admin/ui.generated.ts +13 -13
  252. package/src/analytics/Analytics.test.ts +527 -0
  253. package/src/analytics/Analytics.ts +318 -0
  254. package/src/analytics/tables/requestEvents.ts +523 -0
  255. package/src/apps/composition.test.ts +26 -13
  256. package/src/apps/data/App.test.ts +72 -0
  257. package/src/apps/data/App.ts +37 -0
  258. package/src/apps/data/FundingRouteProviders.test.ts +281 -0
  259. package/src/apps/data/FundingRouteProviders.ts +459 -0
  260. package/src/apps/data/index.ts +1 -0
  261. package/src/apps/data/routes/funding.test.ts +314 -0
  262. package/src/apps/data/routes/funding.ts +698 -0
  263. package/src/apps/data/routes/receipts.ts +2 -9
  264. package/src/apps/data/routes/rpc.test.ts +78 -2
  265. package/src/apps/data/routes/rpc.ts +31 -1
  266. package/src/apps/data/routes/webhooks.test.ts +1 -1
  267. package/src/apps/data/routes/webhooks.ts +11 -8
  268. package/src/apps/docs/App.ts +30 -101
  269. package/src/apps/docs/reference.ts +132 -0
  270. package/src/apps/index.ts +3 -2
  271. package/src/apps/{platform → management}/App.ts +56 -17
  272. package/src/apps/management/Billing.test.ts +530 -0
  273. package/src/apps/management/Billing.ts +682 -0
  274. package/src/apps/management/index.ts +2 -0
  275. package/src/apps/{platform → management}/routes/api-keys.test.ts +172 -9
  276. package/src/apps/{platform → management}/routes/api-keys.ts +137 -21
  277. package/src/apps/management/routes/billing.test.ts +1100 -0
  278. package/src/apps/management/routes/billing.ts +966 -0
  279. package/src/apps/{platform → management}/routes/invitations.test.ts +55 -7
  280. package/src/apps/{platform → management}/routes/invitations.ts +69 -1
  281. package/src/apps/management/routes/me.test.ts +351 -0
  282. package/src/apps/{platform → management}/routes/orgs.ts +26 -4
  283. package/src/apps/management/routes/scopes.test.ts +29 -0
  284. package/src/apps/management/routes/scopes.ts +85 -0
  285. package/src/apps/management/routes/usage.test.ts +422 -0
  286. package/src/apps/management/routes/usage.ts +527 -0
  287. package/src/apps/mcp.test.ts +206 -0
  288. package/src/apps/mcp.ts +250 -0
  289. package/src/apps/relay/App.test.ts +457 -8
  290. package/src/apps/relay/App.ts +120 -12
  291. package/src/apps/relay/Sponsorships.test.ts +1 -1
  292. package/src/apps/relay/Sponsorships.ts +2 -1
  293. package/src/cli/Cli.test.ts +25 -2
  294. package/src/cli/Cli.ts +17 -2
  295. package/src/cli/commands/admin.test.ts +83 -0
  296. package/src/cli/commands/admin.ts +89 -1
  297. package/src/db/Db.ts +137 -0
  298. package/src/db/Schema.ts +104 -2
  299. package/src/db/tables/billingSettings.test.ts +87 -0
  300. package/src/db/tables/billingSettings.ts +108 -0
  301. package/src/db/tables/invitations.ts +2 -2
  302. package/src/db/tables/organizations.test.ts +1 -1
  303. package/src/db/tables/organizations.ts +15 -5
  304. package/src/db/tables/projects.ts +2 -2
  305. package/src/db/tables/requestUsage.ts +231 -0
  306. package/src/db/tables/sponsoredTransactions.test.ts +108 -1
  307. package/src/db/tables/sponsoredTransactions.ts +230 -2
  308. package/src/db/tables/stripeCustomers.test.ts +109 -0
  309. package/src/db/tables/stripeCustomers.ts +162 -0
  310. package/src/db/tables/users.ts +2 -2
  311. package/src/handlers/internal/sponsorship.ts +34 -7
  312. package/src/handlers/internal/utils.ts +19 -7
  313. package/src/index.ts +1 -0
  314. package/src/internal/Auth.test.ts +175 -25
  315. package/src/internal/Auth.ts +176 -87
  316. package/src/internal/Cache.ts +5 -1
  317. package/src/internal/EdgeCache.test.ts +27 -4
  318. package/src/internal/EdgeCache.ts +47 -9
  319. package/src/internal/Fees.test.ts +32 -0
  320. package/src/internal/Fees.ts +51 -0
  321. package/src/internal/FundingRoutes.test.ts +213 -0
  322. package/src/internal/FundingRoutes.ts +898 -0
  323. package/src/internal/Id.test.ts +17 -0
  324. package/src/internal/Id.ts +24 -0
  325. package/src/internal/Log.test.ts +1 -0
  326. package/src/internal/Log.ts +5 -0
  327. package/src/internal/OpenApi.ts +12 -1
  328. package/src/internal/RateLimit.test.ts +84 -0
  329. package/src/internal/RateLimit.ts +70 -0
  330. package/src/internal/Response.ts +1 -1
  331. package/src/internal/Schema.ts +8 -0
  332. package/src/internal/Webhooks.test.ts +3 -3
  333. package/src/internal/Webhooks.ts +3 -3
  334. package/dist/apps/platform/App.d.ts.map +0 -1
  335. package/dist/apps/platform/App.js.map +0 -1
  336. package/dist/apps/platform/index.d.ts +0 -2
  337. package/dist/apps/platform/index.d.ts.map +0 -1
  338. package/dist/apps/platform/index.js +0 -2
  339. package/dist/apps/platform/index.js.map +0 -1
  340. package/dist/apps/platform/routes/api-keys.d.ts.map +0 -1
  341. package/dist/apps/platform/routes/api-keys.js.map +0 -1
  342. package/dist/apps/platform/routes/invitations.d.ts.map +0 -1
  343. package/dist/apps/platform/routes/invitations.js.map +0 -1
  344. package/dist/apps/platform/routes/me.d.ts.map +0 -1
  345. package/dist/apps/platform/routes/me.js.map +0 -1
  346. package/dist/apps/platform/routes/members.d.ts.map +0 -1
  347. package/dist/apps/platform/routes/members.js.map +0 -1
  348. package/dist/apps/platform/routes/orgs.d.ts.map +0 -1
  349. package/dist/apps/platform/routes/orgs.js.map +0 -1
  350. package/dist/apps/platform/routes/projects.js.map +0 -1
  351. package/src/apps/platform/index.ts +0 -1
  352. package/src/apps/platform/routes/me.test.ts +0 -121
  353. /package/dist/apps/{platform → management}/routes/me.js +0 -0
  354. /package/dist/apps/{platform → management}/routes/members.d.ts +0 -0
  355. /package/dist/apps/{platform → management}/routes/members.js +0 -0
  356. /package/dist/apps/{platform → management}/routes/projects.js +0 -0
  357. /package/src/apps/{platform → management}/routes/me.ts +0 -0
  358. /package/src/apps/{platform → management}/routes/members.test.ts +0 -0
  359. /package/src/apps/{platform → management}/routes/members.ts +0 -0
  360. /package/src/apps/{platform → management}/routes/orgs.test.ts +0 -0
  361. /package/src/apps/{platform → management}/routes/projects.test.ts +0 -0
  362. /package/src/apps/{platform → management}/routes/projects.ts +0 -0
@@ -0,0 +1,795 @@
1
+ import { Hono } from 'hono';
2
+ import Stripe from 'stripe';
3
+ import * as z from 'zod/mini';
4
+ import * as ApiKeys from '../../../ApiKeys.js';
5
+ import * as Auth from '../../../internal/Auth.js';
6
+ import * as BillingSettings from '../../../db/tables/billingSettings.js';
7
+ import * as Db from '../../../db/Db.js';
8
+ import * as OpenApi from '../../../internal/OpenApi.js';
9
+ import * as Response from '../../../internal/Response.js';
10
+ import * as Schema from '../../../internal/Schema.js';
11
+ import * as SponsoredTransactions from '../../../db/tables/sponsoredTransactions.js';
12
+ import * as StripeCustomers from '../../../db/tables/stripeCustomers.js';
13
+ import * as Viem from '../../../internal/Viem.js';
14
+ import * as core_Billing from '../Billing.js';
15
+ /** Zod schemas owned by the billing resource. */
16
+ export var schema;
17
+ (function (schema) {
18
+ const OrgId = z
19
+ .string()
20
+ .check(z.describe('The organization id (`org_…`).'), z.meta({ examples: ['org_1a2b3c4d5e6f7g8h9j0k1m2n'] }));
21
+ /** Path parameters addressing one organization's billing. */
22
+ schema.Params = z
23
+ .object({ orgId: OrgId })
24
+ .check(z.describe("Path parameters for one organization's billing."));
25
+ /** Query parameters selecting the billing environment. */
26
+ schema.Query = z
27
+ .strictObject({ environment: Schema.Environment })
28
+ .check(z.describe("Query parameters for one organization's billing."));
29
+ /** Path parameters addressing one payment method. */
30
+ schema.MethodParams = z
31
+ .object({
32
+ methodId: z
33
+ .string()
34
+ .check(z.describe('The payment method id (`pm_…`).'), z.meta({ examples: ['pm_1NVChw2eZvKYlo2CHxiM5E2N'] })),
35
+ orgId: OrgId,
36
+ })
37
+ .check(z.describe('Path parameters addressing one payment method.'));
38
+ /** A per-period spend limit. */
39
+ schema.SpendLimit = Schema.describe(z.object({
40
+ amount: z.string().check(
41
+ // The lookahead rejects zero: a zero limit would refuse all
42
+ // sponsorship, which `null` (clear) already expresses.
43
+ z.regex(/^(?!0+(\.0+)?$)\d{1,9}(\.\d{1,2})?$/), z.describe('Limit as a positive decimal string in `currency` units.'), z.meta({ examples: ['250'] })),
44
+ currency: z
45
+ ._default(z.enum(BillingSettings.currencies), 'usd')
46
+ .check(z.describe('Currency the limit is denominated in; `usd` only today.'), z.meta({ examples: ['usd'] })),
47
+ period: z
48
+ ._default(z.enum(BillingSettings.periods), 'month')
49
+ .check(z.describe('Window the limit applies over; `month` (UTC calendar) only today.'), z.meta({ examples: ['month'] })),
50
+ }), 'A per-period spend limit.');
51
+ /** A per-transaction fee cap. */
52
+ schema.TxFeeLimit = Schema.describe(z.object({
53
+ amount: z.string().check(
54
+ // The lookahead rejects zero: a zero cap would refuse every
55
+ // transaction, which is not a supported configuration.
56
+ z.regex(/^(?!0+(\.0+)?$)\d{1,9}(\.\d{1,2})?$/), z.describe('Cap as a positive decimal string in `currency` units.'), z.meta({ examples: ['0.50'] })),
57
+ currency: z
58
+ ._default(z.enum(BillingSettings.currencies), 'usd')
59
+ .check(z.describe('Currency the cap is denominated in; `usd` only today.'), z.meta({ examples: ['usd'] })),
60
+ }), 'A per-transaction fee cap.');
61
+ /** PATCH body; absent fields stay unchanged, null clears a limit. */
62
+ schema.Patch = Schema.describe(z.object({
63
+ spendLimit: z
64
+ .optional(z.nullable(schema.SpendLimit))
65
+ .check(z.describe('Spend limit per period; absent leaves it unchanged, null removes it.'), z.meta({ examples: [{ amount: '250', currency: 'usd', period: 'month' }] })),
66
+ txFeeLimit: z.optional(z.nullable(schema.TxFeeLimit)).check(z.describe('Per-transaction fee cap; absent leaves it unchanged, null restores the platform default.'), // prettier-ignore
67
+ z.meta({ examples: [{ amount: '0.50', currency: 'usd' }] })),
68
+ }), 'Billing settings to apply.');
69
+ /** An organization's billing state. */
70
+ schema.Billing = Schema.describe(z.object({
71
+ spend: z
72
+ .optional(z.object({
73
+ amount: z.string().check(z.describe('Committed spend as a decimal string: finalized fees plus in-flight fee caps.'), // prettier-ignore
74
+ z.meta({ examples: ['12.34'] })),
75
+ currency: z
76
+ .enum(BillingSettings.currencies)
77
+ .check(z.describe('Currency of the spend figure.'), z.meta({ examples: ['usd'] })),
78
+ period: z
79
+ .enum(BillingSettings.periods)
80
+ .check(z.describe('Window the figure covers.'), z.meta({ examples: ['month'] })),
81
+ }))
82
+ .check(z.describe('Billable production spend committed in the current window.'), z.meta({ examples: [{ amount: '12.34', currency: 'usd', period: 'month' }] })),
83
+ spendLimit: z
84
+ .optional(schema.SpendLimit)
85
+ .check(z.describe('Configured spend limit; absent when the organization has none.'), z.meta({ examples: [{ amount: '250', currency: 'usd', period: 'month' }] })),
86
+ status: z.enum(StripeCustomers.statuses).check(z.describe("Billing status derived from the organization's billing source; `active` opens production fee sponsorship."), // prettier-ignore
87
+ z.meta({ examples: ['active'] })),
88
+ txFeeLimit: z
89
+ .optional(schema.TxFeeLimit)
90
+ .check(z.describe('Configured per-transaction fee cap; absent when the platform default applies.'), z.meta({ examples: [{ amount: '0.50', currency: 'usd' }] })),
91
+ updatedAt: z.optional(z.string()).check(z.describe('When the billing status last changed (ISO 8601); absent before first checkout.'), // prettier-ignore
92
+ z.meta({ examples: ['2026-01-01T00:00:00.000Z'] })),
93
+ }), "An organization's billing state.");
94
+ /** A hosted Stripe session link. */
95
+ schema.Session = Schema.describe(z.object({
96
+ url: z
97
+ .url()
98
+ .check(z.describe('Hosted Stripe session URL; navigate the browser here.'), z.meta({ examples: ['https://checkout.stripe.com/c/pay/cs_test_a1b2c3'] })),
99
+ }), 'A hosted Stripe session link.');
100
+ /** A payment method on file. */
101
+ schema.PaymentMethod = Schema.describe(z.object({
102
+ card: z
103
+ .optional(z.object({
104
+ brand: z
105
+ .string()
106
+ .check(z.describe('Card brand (e.g. `visa`).'), z.meta({ examples: ['visa'] })),
107
+ expMonth: z
108
+ .number()
109
+ .check(z.describe('Expiry month (1-12).'), z.meta({ examples: [12] })),
110
+ expYear: z.number().check(z.describe('Expiry year.'), z.meta({ examples: [2030] })),
111
+ last4: z
112
+ .string()
113
+ .check(z.describe('Last four digits.'), z.meta({ examples: ['4242'] })),
114
+ }))
115
+ .check(z.describe('Card details; present when `type` is `card`.'), z.meta({ examples: [{ brand: 'visa', expMonth: 12, expYear: 2030, last4: '4242' }] })),
116
+ createdAt: z
117
+ .string()
118
+ .check(z.describe('When the method was attached (ISO 8601).'), z.meta({ examples: ['2026-01-01T00:00:00.000Z'] })),
119
+ default: z
120
+ .boolean()
121
+ .check(z.describe('Whether invoices charge this method.'), z.meta({ examples: [true] })),
122
+ id: z
123
+ .string()
124
+ .check(z.describe('The payment method id (`pm_…`).'), z.meta({ examples: ['pm_1NVChw2eZvKYlo2CHxiM5E2N'] })),
125
+ provider: z
126
+ .literal('stripe')
127
+ .check(z.describe('Payment provider; `stripe` only today.'), z.meta({ examples: ['stripe'] })),
128
+ type: z
129
+ .string()
130
+ .check(z.describe('Method type (e.g. `card`, `us_bank_account`).'), z.meta({ examples: ['card'] })),
131
+ }), 'A payment method on file.');
132
+ /** An organization's payment methods. */
133
+ schema.PaymentMethods = Schema.describe(z.object({
134
+ data: z.array(schema.PaymentMethod).check(z.describe('Payment methods on file, newest first.')),
135
+ }), "An organization's payment methods.");
136
+ })(schema || (schema = {}));
137
+ /**
138
+ * Mounts the billing surface: an organization's billing status. Orgs without
139
+ * a Stripe billing source read as `none`. Session lane; reads for members.
140
+ */
141
+ export function billing() {
142
+ return new Hono()
143
+ .get('/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/billing', Auth.policy({ session: true }), Auth.ensureOrg(), OpenApi.validate('param', schema.Params, {
144
+ code: 'param_invalid',
145
+ message: 'Check the path parameters and try again.',
146
+ }), OpenApi.validate('query', schema.Query, {
147
+ code: 'query_invalid',
148
+ message: 'Check the query parameters and try again.',
149
+ }), OpenApi.describeRoute({
150
+ operationId: 'getBilling',
151
+ responses: OpenApi.responses({
152
+ errors: {
153
+ 404: 'No accessible organization was found for this id.',
154
+ 501: 'Billing is not configured on this deployment.',
155
+ },
156
+ success: { description: "The organization's billing state.", schema: schema.Billing },
157
+ }),
158
+ summary: 'Get billing',
159
+ tags: ['Billing'],
160
+ }), async (c) => {
161
+ if (Auth.narrowAccess)
162
+ return Auth.superAdminAccessError(c);
163
+ if (Auth.narrowScope)
164
+ return Auth.ensureOrgError(c);
165
+ if (OpenApi.narrowValidation)
166
+ return OpenApi.validationError(c, {
167
+ code: 'param_invalid',
168
+ message: 'Check the path parameters and try again.',
169
+ });
170
+ if (OpenApi.narrowValidation)
171
+ return OpenApi.validationError(c, {
172
+ code: 'query_invalid',
173
+ message: 'Check the query parameters and try again.',
174
+ });
175
+ const billing = c.get('billing');
176
+ if (!billing)
177
+ return billingUnconfigured(c);
178
+ const environment = c.req.valid('query').environment;
179
+ if (!stripeFor(billing, environment))
180
+ return billingUnconfigured(c);
181
+ try {
182
+ return c.json(Response.validated(schema.Billing, await billingBody(c, Auth.org(c).id, environment)), 200);
183
+ }
184
+ catch (cause) {
185
+ return Response.upstream(c, cause);
186
+ }
187
+ })
188
+ .patch('/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/billing', Auth.policy({ session: true }), Auth.ensureOrg({ role: 'owner' }), OpenApi.validate('param', schema.Params, {
189
+ code: 'param_invalid',
190
+ message: 'Check the path parameters and try again.',
191
+ }), OpenApi.validate('query', schema.Query, {
192
+ code: 'query_invalid',
193
+ message: 'Check the query parameters and try again.',
194
+ }), OpenApi.validate('json', schema.Patch, {
195
+ code: 'body_invalid',
196
+ message: 'Check the request body and try again.',
197
+ }), OpenApi.describeRoute({
198
+ operationId: 'updateBilling',
199
+ responses: OpenApi.responses({
200
+ errors: {
201
+ 403: 'Requires the owner role.',
202
+ 404: 'No accessible organization was found for this id.',
203
+ 501: 'Billing is not configured on this deployment.',
204
+ },
205
+ success: {
206
+ description: "The organization's updated billing state.",
207
+ schema: schema.Billing,
208
+ },
209
+ }),
210
+ summary: 'Update billing',
211
+ tags: ['Billing'],
212
+ }), async (c) => {
213
+ if (Auth.narrowAccess)
214
+ return Auth.superAdminAccessError(c);
215
+ if (Auth.narrowScope)
216
+ return Auth.ensureOrgError(c);
217
+ if (OpenApi.narrowValidation)
218
+ return OpenApi.validationError(c, {
219
+ code: 'param_invalid',
220
+ message: 'Check the path parameters and try again.',
221
+ });
222
+ if (OpenApi.narrowValidation)
223
+ return OpenApi.validationError(c, {
224
+ code: 'query_invalid',
225
+ message: 'Check the query parameters and try again.',
226
+ });
227
+ if (OpenApi.narrowValidation)
228
+ return OpenApi.validationError(c, {
229
+ code: 'body_invalid',
230
+ message: 'Check the request body and try again.',
231
+ });
232
+ const billing = c.get('billing');
233
+ if (!billing)
234
+ return billingUnconfigured(c);
235
+ const environment = c.req.valid('query').environment;
236
+ if (!stripeFor(billing, environment))
237
+ return billingUnconfigured(c);
238
+ const body = c.req.valid('json');
239
+ const org = Auth.org(c);
240
+ const db = Db.get(c.get('db'));
241
+ try {
242
+ await BillingSettings.upsert(db, {
243
+ environment,
244
+ orgId: org.id,
245
+ ...(body.spendLimit !== undefined
246
+ ? body.spendLimit === null
247
+ ? { spendLimit: null }
248
+ : {
249
+ currency: body.spendLimit.currency,
250
+ period: body.spendLimit.period,
251
+ spendLimit: body.spendLimit.amount,
252
+ }
253
+ : {}),
254
+ ...(body.txFeeLimit !== undefined
255
+ ? body.txFeeLimit === null
256
+ ? { txFeeLimit: null }
257
+ : { currency: body.txFeeLimit.currency, txFeeLimit: body.txFeeLimit.amount }
258
+ : {}),
259
+ });
260
+ return c.json(Response.validated(schema.Billing, await billingBody(c, org.id, environment)), 200);
261
+ }
262
+ catch (cause) {
263
+ return Response.upstream(c, cause);
264
+ }
265
+ })
266
+ .post('/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/billing/stripe/checkout', Auth.policy({ session: true }), Auth.ensureOrg({ role: 'owner' }), OpenApi.validate('param', schema.Params, {
267
+ code: 'param_invalid',
268
+ message: 'Check the path parameters and try again.',
269
+ }), OpenApi.validate('query', schema.Query, {
270
+ code: 'query_invalid',
271
+ message: 'Check the query parameters and try again.',
272
+ }), OpenApi.describeRoute({
273
+ operationId: 'createStripeCheckout',
274
+ responses: OpenApi.responses({
275
+ errors: {
276
+ 403: 'Requires the owner role.',
277
+ 404: 'No accessible organization was found for this id.',
278
+ 501: 'Billing is not configured on this deployment.',
279
+ },
280
+ success: {
281
+ description: 'A Stripe Checkout (setup mode) session for attaching a payment method.',
282
+ schema: schema.Session,
283
+ },
284
+ }),
285
+ summary: 'Create checkout session',
286
+ tags: ['Billing'],
287
+ }), async (c) => {
288
+ if (Auth.narrowAccess)
289
+ return Auth.superAdminAccessError(c);
290
+ if (Auth.narrowScope)
291
+ return Auth.ensureOrgError(c);
292
+ if (OpenApi.narrowValidation)
293
+ return OpenApi.validationError(c, {
294
+ code: 'param_invalid',
295
+ message: 'Check the path parameters and try again.',
296
+ });
297
+ if (OpenApi.narrowValidation)
298
+ return OpenApi.validationError(c, {
299
+ code: 'query_invalid',
300
+ message: 'Check the query parameters and try again.',
301
+ });
302
+ const billing = c.get('billing');
303
+ if (!billing)
304
+ return billingUnconfigured(c);
305
+ const environment = c.req.valid('query').environment;
306
+ const source = stripeFor(billing, environment);
307
+ if (!source)
308
+ return billingUnconfigured(c);
309
+ const { client } = source;
310
+ const org = Auth.org(c);
311
+ const db = Db.get(c.get('db'));
312
+ try {
313
+ const existing = await StripeCustomers.get(db, org.id, environment);
314
+ const customerId = existing
315
+ ? existing.stripeCustomerId
316
+ : await (async () => {
317
+ const customer = await client.customers.create({
318
+ metadata: { orgId: org.id },
319
+ name: org.name,
320
+ });
321
+ // The insert resolves a concurrent first-checkout race to the
322
+ // winning row; always use its customer, not the one just made.
323
+ const record = await StripeCustomers.create(db, {
324
+ environment,
325
+ orgId: org.id,
326
+ stripeCustomerId: customer.id,
327
+ });
328
+ return record.stripeCustomerId;
329
+ })();
330
+ const session = await client.checkout.sessions.create({
331
+ cancel_url: returnUrl(c, { environment, orgId: org.id, status: 'canceled', stripe: source }), // prettier-ignore
332
+ // Setup mode with dynamic payment methods requires a currency;
333
+ // fee-payer spend bills in USD.
334
+ currency: 'usd',
335
+ customer: customerId,
336
+ mode: 'setup',
337
+ success_url: returnUrl(c, { environment, orgId: org.id, status: 'success', stripe: source }), // prettier-ignore
338
+ });
339
+ if (!session.url)
340
+ return Response.upstream(c, new Error('checkout session has no url'));
341
+ return c.json(Response.validated(schema.Session, { url: session.url }), 200);
342
+ }
343
+ catch (cause) {
344
+ return Response.upstream(c, cause);
345
+ }
346
+ })
347
+ .post('/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/billing/stripe/manage', Auth.policy({ session: true }), Auth.ensureOrg({ role: 'owner' }), OpenApi.validate('param', schema.Params, {
348
+ code: 'param_invalid',
349
+ message: 'Check the path parameters and try again.',
350
+ }), OpenApi.validate('query', schema.Query, {
351
+ code: 'query_invalid',
352
+ message: 'Check the query parameters and try again.',
353
+ }), OpenApi.describeRoute({
354
+ operationId: 'createStripeManage',
355
+ responses: OpenApi.responses({
356
+ errors: {
357
+ 403: 'Requires the owner role.',
358
+ 404: 'No accessible organization, or no billing to manage yet.',
359
+ 501: 'Billing is not configured on this deployment.',
360
+ },
361
+ success: {
362
+ description: 'A Stripe billing-portal session for managing payment methods and invoices.', // prettier-ignore
363
+ schema: schema.Session,
364
+ },
365
+ }),
366
+ summary: 'Create manage session',
367
+ tags: ['Billing'],
368
+ }), async (c) => {
369
+ if (Auth.narrowAccess)
370
+ return Auth.superAdminAccessError(c);
371
+ if (Auth.narrowScope)
372
+ return Auth.ensureOrgError(c);
373
+ if (OpenApi.narrowValidation)
374
+ return OpenApi.validationError(c, {
375
+ code: 'param_invalid',
376
+ message: 'Check the path parameters and try again.',
377
+ });
378
+ if (OpenApi.narrowValidation)
379
+ return OpenApi.validationError(c, {
380
+ code: 'query_invalid',
381
+ message: 'Check the query parameters and try again.',
382
+ });
383
+ const billing = c.get('billing');
384
+ if (!billing)
385
+ return billingUnconfigured(c);
386
+ const environment = c.req.valid('query').environment;
387
+ const source = stripeFor(billing, environment);
388
+ if (!source)
389
+ return billingUnconfigured(c);
390
+ const org = Auth.org(c);
391
+ const db = Db.get(c.get('db'));
392
+ try {
393
+ const record = await StripeCustomers.get(db, org.id, environment);
394
+ if (!record)
395
+ return billingNotFound(c);
396
+ const session = await source.client.billingPortal.sessions.create({
397
+ customer: record.stripeCustomerId,
398
+ return_url: returnUrl(c, { environment, orgId: org.id, stripe: source }),
399
+ });
400
+ return c.json(Response.validated(schema.Session, { url: session.url }), 200);
401
+ }
402
+ catch (cause) {
403
+ return Response.upstream(c, cause);
404
+ }
405
+ })
406
+ .get('/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/billing/payment-methods', Auth.policy({ session: true }), Auth.ensureOrg(), OpenApi.validate('param', schema.Params, {
407
+ code: 'param_invalid',
408
+ message: 'Check the path parameters and try again.',
409
+ }), OpenApi.validate('query', schema.Query, {
410
+ code: 'query_invalid',
411
+ message: 'Check the query parameters and try again.',
412
+ }), OpenApi.describeRoute({
413
+ operationId: 'getBillingPaymentMethods',
414
+ responses: OpenApi.responses({
415
+ errors: {
416
+ 404: 'No accessible organization was found for this id.',
417
+ 501: 'Billing is not configured on this deployment.',
418
+ },
419
+ success: {
420
+ description: "The organization's payment methods.",
421
+ schema: schema.PaymentMethods,
422
+ },
423
+ }),
424
+ summary: 'Get payment methods',
425
+ tags: ['Billing'],
426
+ }), async (c) => {
427
+ if (Auth.narrowAccess)
428
+ return Auth.superAdminAccessError(c);
429
+ if (Auth.narrowScope)
430
+ return Auth.ensureOrgError(c);
431
+ if (OpenApi.narrowValidation)
432
+ return OpenApi.validationError(c, {
433
+ code: 'param_invalid',
434
+ message: 'Check the path parameters and try again.',
435
+ });
436
+ if (OpenApi.narrowValidation)
437
+ return OpenApi.validationError(c, {
438
+ code: 'query_invalid',
439
+ message: 'Check the query parameters and try again.',
440
+ });
441
+ const billing = c.get('billing');
442
+ if (!billing)
443
+ return billingUnconfigured(c);
444
+ const environment = c.req.valid('query').environment;
445
+ const source = stripeFor(billing, environment);
446
+ if (!source)
447
+ return billingUnconfigured(c);
448
+ const { client } = source;
449
+ const org = Auth.org(c);
450
+ const db = Db.get(c.get('db'));
451
+ try {
452
+ // Orgs that never checked out simply have no methods yet.
453
+ const record = await StripeCustomers.get(db, org.id, environment);
454
+ if (!record)
455
+ return c.json(Response.validated(schema.PaymentMethods, { data: [] }), 200);
456
+ const customer = await client.customers.retrieve(record.stripeCustomerId);
457
+ if (customer.deleted)
458
+ return c.json(Response.validated(schema.PaymentMethods, { data: [] }), 200);
459
+ const fallback = customer.invoice_settings.default_payment_method;
460
+ const methods = await client.customers.listPaymentMethods(record.stripeCustomerId, { limit: 100 }); // prettier-ignore
461
+ return c.json(Response.validated(schema.PaymentMethods, {
462
+ data: methods.data.map((method) => ({
463
+ ...(method.card
464
+ ? {
465
+ card: {
466
+ brand: method.card.brand,
467
+ expMonth: method.card.exp_month,
468
+ expYear: method.card.exp_year,
469
+ last4: method.card.last4,
470
+ },
471
+ }
472
+ : {}),
473
+ createdAt: new Date(method.created * 1000).toISOString(),
474
+ default: method.id === fallback,
475
+ id: method.id,
476
+ provider: 'stripe',
477
+ type: method.type,
478
+ })),
479
+ }), 200);
480
+ }
481
+ catch (cause) {
482
+ return Response.upstream(c, cause);
483
+ }
484
+ })
485
+ .delete('/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/billing/payment-methods/:methodId{pm_[A-Za-z0-9_-]+}', Auth.policy({ session: true }), Auth.ensureOrg({ role: 'owner' }), OpenApi.validate('param', schema.MethodParams, {
486
+ code: 'param_invalid',
487
+ message: 'Check the path parameters and try again.',
488
+ }), OpenApi.validate('query', schema.Query, {
489
+ code: 'query_invalid',
490
+ message: 'Check the query parameters and try again.',
491
+ }), OpenApi.describeRoute({
492
+ operationId: 'deleteBillingPaymentMethod',
493
+ responses: OpenApi.responses({
494
+ errors: {
495
+ 403: 'Requires the owner role.',
496
+ 404: 'No accessible organization, billing, or payment method for this id.',
497
+ 501: 'Billing is not configured on this deployment.',
498
+ },
499
+ success: {
500
+ description: "The organization's updated billing state.",
501
+ schema: schema.Billing,
502
+ },
503
+ }),
504
+ summary: 'Remove payment method',
505
+ tags: ['Billing'],
506
+ }), async (c) => {
507
+ if (Auth.narrowAccess)
508
+ return Auth.superAdminAccessError(c);
509
+ if (Auth.narrowScope)
510
+ return Auth.ensureOrgError(c);
511
+ if (OpenApi.narrowValidation)
512
+ return OpenApi.validationError(c, {
513
+ code: 'param_invalid',
514
+ message: 'Check the path parameters and try again.',
515
+ });
516
+ if (OpenApi.narrowValidation)
517
+ return OpenApi.validationError(c, {
518
+ code: 'query_invalid',
519
+ message: 'Check the query parameters and try again.',
520
+ });
521
+ const billing = c.get('billing');
522
+ if (!billing)
523
+ return billingUnconfigured(c);
524
+ const environment = c.req.valid('query').environment;
525
+ const source = stripeFor(billing, environment);
526
+ if (!source)
527
+ return billingUnconfigured(c);
528
+ const { client } = source;
529
+ const org = Auth.org(c);
530
+ const db = Db.get(c.get('db'));
531
+ try {
532
+ const record = await StripeCustomers.get(db, org.id, environment);
533
+ if (!record)
534
+ return billingNotFound(c);
535
+ // Scoping the lookup to the org's own list is the ownership check;
536
+ // ids belonging to other customers read as absent.
537
+ const methods = await client.customers.listPaymentMethods(record.stripeCustomerId, { limit: 100 }); // prettier-ignore
538
+ const method = methods.data.find((m) => m.id === c.req.valid('param').methodId);
539
+ if (!method)
540
+ return paymentMethodNotFound(c);
541
+ await client.paymentMethods.detach(method.id);
542
+ // The metered subscription stays: accrued usage still invoices at
543
+ // period end, and re-adding a method reuses it.
544
+ const status = await deriveStatus(client, record.stripeCustomerId);
545
+ await StripeCustomers.setStatus(db, record.orgId, status, environment);
546
+ await syncKeyBilling(c, { environment, orgId: record.orgId, status });
547
+ return c.json(Response.validated(schema.Billing, await billingBody(c, org.id, environment)), 200);
548
+ }
549
+ catch (cause) {
550
+ return Response.upstream(c, cause);
551
+ }
552
+ })
553
+ .post('/stripe/webhook',
554
+ // Inbound Stripe callback: the signature is the gate; the public quota
555
+ // only absorbs delivery bursts. Never a partner-facing API route.
556
+ Auth.policy({
557
+ apiKey: false,
558
+ mpp: false,
559
+ public: { rateLimit: { perMinute: 600 } },
560
+ session: false,
561
+ }), OpenApi.describeRoute({ hide: true, summary: 'Stripe webhook' }), async (c) => {
562
+ const billing = c.get('billing');
563
+ if (!billing)
564
+ return billingUnconfigured(c);
565
+ const signature = c.req.header('stripe-signature');
566
+ if (!signature)
567
+ return signatureInvalid(c);
568
+ const payload = await c.req.text();
569
+ // One endpoint, two secrets: verify against the live secret, then the
570
+ // sandbox secret. Test and live events are signed by different endpoint
571
+ // secrets, so try-both is unambiguous; the customer row's environment
572
+ // (below) picks the client for follow-up calls.
573
+ const event = await verifyEvent(billing, payload, signature);
574
+ if (!event)
575
+ return signatureInvalid(c);
576
+ // Events are triggers, not state carriers: on every handled event the
577
+ // status re-derives from fetched Stripe truth, so duplicates and
578
+ // out-of-order delivery are harmless. Unhandled types ack immediately.
579
+ if (!handledEvents.has(event.type))
580
+ return c.json({ received: true }, 200);
581
+ const customer = eventCustomer(event);
582
+ if (!customer)
583
+ return c.json({ received: true }, 200);
584
+ const db = Db.get(c.get('db'));
585
+ try {
586
+ // Unknown customers ack with 200: other environments may share the
587
+ // Stripe account, and their events are not ours to fail.
588
+ const record = await StripeCustomers.getByCustomer(db, customer);
589
+ if (!record)
590
+ return c.json({ received: true }, 200);
591
+ // The row's environment picks the mode's client; a sandbox row with
592
+ // no sandbox source configured acks untouched.
593
+ const source = stripeFor(billing, record.environment);
594
+ if (!source)
595
+ return c.json({ received: true }, 200);
596
+ const { client } = source;
597
+ // A completed setup Checkout promotes its payment method to the
598
+ // customer default; invoicing charges the default.
599
+ if (event.type === 'checkout.session.completed') {
600
+ const session = event.data.object;
601
+ if (session.mode === 'setup' && typeof session.setup_intent === 'string') {
602
+ const intent = await client.setupIntents.retrieve(session.setup_intent);
603
+ if (typeof intent.payment_method === 'string')
604
+ await client.customers.update(customer, {
605
+ invoice_settings: { default_payment_method: intent.payment_method },
606
+ });
607
+ }
608
+ }
609
+ const status = await deriveStatus(client, customer);
610
+ // Provision the metered subscription before persisting `active`, so
611
+ // the gate never opens (`Billing.status` reads the stored status)
612
+ // without a subscription to invoice the spend against. A failure
613
+ // here surfaces as 5xx and Stripe redelivers.
614
+ if (status === 'active')
615
+ await core_Billing.ensureSubscription(client, customer);
616
+ await StripeCustomers.setStatus(db, record.orgId, status, record.environment);
617
+ await syncKeyBilling(c, { environment: record.environment, orgId: record.orgId, status });
618
+ return c.json({ received: true }, 200);
619
+ }
620
+ catch (cause) {
621
+ return Response.upstream(c, cause);
622
+ }
623
+ });
624
+ }
625
+ /** Web Crypto signature verification; Workers have no Node `crypto` module. */
626
+ const cryptoProvider = Stripe.createSubtleCryptoProvider();
627
+ /**
628
+ * Resolves the Stripe source for an environment: the live bag for `production`,
629
+ * the test-mode `sandbox` bag otherwise. Returns undefined when sandbox billing
630
+ * is unconfigured, which the caller maps to `billing_unconfigured` (501).
631
+ */
632
+ function stripeFor(billing, environment) {
633
+ return environment === 'production' ? billing.stripe : billing.stripe.sandbox;
634
+ }
635
+ /**
636
+ * Re-stamps `billingActive` onto the org's sandbox key records after a status
637
+ * change, so the auth throttle reflects the new state without a per-request
638
+ * billing read. Only sandbox is gated, and re-stamping is a no-op when the KV
639
+ * store is unconfigured.
640
+ */
641
+ async function syncKeyBilling(c, options) {
642
+ if (options.environment !== 'sandbox')
643
+ return;
644
+ const kv = c.get('kv');
645
+ if (!kv)
646
+ return;
647
+ await ApiKeys.setBillingActive(kv.store, {
648
+ active: options.status === 'active',
649
+ environment: 'sandbox',
650
+ orgId: options.orgId,
651
+ });
652
+ }
653
+ /**
654
+ * Verifies a webhook signature against the live secret, then the sandbox
655
+ * secret; returns the parsed event on the first match, or undefined. The
656
+ * client instance is irrelevant to verification (the secret is the gate), so
657
+ * both attempts reuse the live client.
658
+ */
659
+ async function verifyEvent(billing, payload, signature) {
660
+ const secrets = [
661
+ billing.stripe.webhookSecret,
662
+ ...(billing.stripe.sandbox ? [billing.stripe.sandbox.webhookSecret] : []),
663
+ ];
664
+ for (const secret of secrets) {
665
+ try {
666
+ return await billing.stripe.client.webhooks.constructEventAsync(payload, signature, secret, undefined, cryptoProvider);
667
+ }
668
+ catch { }
669
+ }
670
+ return undefined;
671
+ }
672
+ /** Event types that funnel into status derivation; everything else acks untouched. */
673
+ const handledEvents = new Set([
674
+ 'checkout.session.completed',
675
+ 'customer.deleted',
676
+ 'customer.subscription.deleted',
677
+ 'customer.subscription.updated',
678
+ 'customer.updated',
679
+ 'invoice.paid',
680
+ 'invoice.payment_failed',
681
+ 'payment_method.attached',
682
+ 'payment_method.detached',
683
+ 'setup_intent.succeeded',
684
+ ]);
685
+ /** Stripe customer id an event concerns; detached payment methods only carry it in `previous_attributes`. */
686
+ function eventCustomer(event) {
687
+ const object = event.data.object;
688
+ if (object.object === 'customer')
689
+ return object.id;
690
+ if (typeof object.customer === 'string')
691
+ return object.customer;
692
+ const previous = event.data.previous_attributes;
693
+ return typeof previous?.customer === 'string' ? previous.customer : undefined;
694
+ }
695
+ /**
696
+ * Billing status from live Stripe state: deleted customers are `canceled`; an
697
+ * unpaid metered subscription parks the org at `past_due`; a default payment
698
+ * method or any attached card is `active`; else `none`.
699
+ */
700
+ async function deriveStatus(stripe, customerId) {
701
+ const customer = await stripe.customers.retrieve(customerId);
702
+ if (customer.deleted)
703
+ return 'canceled';
704
+ const subscriptions = await stripe.subscriptions.list({ customer: customerId, limit: 100 });
705
+ if (subscriptions.data.some((s) => s.status === 'past_due' || s.status === 'unpaid'))
706
+ return 'past_due';
707
+ if (customer.invoice_settings.default_payment_method)
708
+ return 'active';
709
+ const methods = await stripe.customers.listPaymentMethods(customerId, { limit: 1, type: 'card' }); // prettier-ignore
710
+ return methods.data.length > 0 ? 'active' : 'none';
711
+ }
712
+ /** Builds the billing response body: source status, configured limits, current-window spend. */
713
+ async function billingBody(c, orgId, environment) {
714
+ const db = Db.get(c.get('db'));
715
+ const [record, settings] = await Promise.all([
716
+ StripeCustomers.get(db, orgId, environment),
717
+ BillingSettings.get(db, orgId, environment),
718
+ ]);
719
+ const currency = settings?.currency ?? 'usd';
720
+ const period = settings?.period ?? 'month';
721
+ // Mirrors the sponsorship gate's scope so the console shows the figure the
722
+ // limit is enforced against; sandbox spend is testnet, display-only.
723
+ const spend = await SponsoredTransactions.spend(Db.get(c.get('dbCached')), {
724
+ chainIds: [environment === 'production' ? Viem.chainId.mainnet : Viem.chainId.testnet],
725
+ environment,
726
+ orgId,
727
+ since: core_Billing.periodStart(period),
728
+ });
729
+ return {
730
+ spend: { amount: core_Billing.fromBaseUnits(spend), currency, period },
731
+ ...(settings?.spendLimit != null
732
+ ? { spendLimit: { amount: settings.spendLimit, currency, period } }
733
+ : {}),
734
+ status: record?.status ?? 'none',
735
+ ...(settings?.txFeeLimit != null
736
+ ? { txFeeLimit: { amount: settings.txFeeLimit, currency } }
737
+ : {}),
738
+ ...(record ? { updatedAt: record.updatedAt } : {}),
739
+ };
740
+ }
741
+ /**
742
+ * Console billing page a Stripe session returns to: the caller's `Origin` when
743
+ * present, else the configured console URL. Sandbox sessions carry `env=sandbox`
744
+ * so the full-page Stripe round-trip returns to the same console mode (the
745
+ * client-side `retainSearchParams` can't survive it); `status` sets `billing`.
746
+ */
747
+ function returnUrl(c, options) {
748
+ const { environment, orgId, status, stripe } = options;
749
+ // Origin is safe to trust: SameSite=Lax session cookies never authenticate
750
+ // cross-site POSTs, so a forged Origin only redirects the caller themselves.
751
+ const origin = c.req.header('origin');
752
+ const base = (() => {
753
+ try {
754
+ if (origin)
755
+ return new URL(origin).origin;
756
+ }
757
+ catch { }
758
+ return (stripe.consoleUrl ?? 'https://console.tempo.xyz').replace(/\/$/, '');
759
+ })();
760
+ const url = new URL(`${base}/${orgId}/billing`);
761
+ if (environment === 'sandbox')
762
+ url.searchParams.set('env', 'sandbox');
763
+ if (status)
764
+ url.searchParams.set('billing', status);
765
+ return url.toString();
766
+ }
767
+ function signatureInvalid(c) {
768
+ return Response.error(c, {
769
+ code: 'signature_invalid',
770
+ message: 'Missing or invalid Stripe signature',
771
+ status: 400,
772
+ });
773
+ }
774
+ function billingNotFound(c) {
775
+ return Response.error(c, {
776
+ code: 'billing_not_found',
777
+ message: 'No billing to manage; attach a payment method first',
778
+ status: 404,
779
+ });
780
+ }
781
+ function billingUnconfigured(c) {
782
+ return Response.error(c, {
783
+ code: 'billing_unconfigured',
784
+ message: 'Billing is not configured on this deployment',
785
+ status: 501,
786
+ });
787
+ }
788
+ function paymentMethodNotFound(c) {
789
+ return Response.error(c, {
790
+ code: 'payment_method_not_found',
791
+ message: 'No payment method was found for this id',
792
+ status: 404,
793
+ });
794
+ }
795
+ //# sourceMappingURL=billing.js.map