@wopr-network/platform-core 0.1.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 (694) hide show
  1. package/biome.json +61 -0
  2. package/dist/admin/admin-audit-log-repository.d.ts +33 -0
  3. package/dist/admin/admin-audit-log-repository.js +102 -0
  4. package/dist/admin/audit-log.d.ts +49 -0
  5. package/dist/admin/audit-log.js +63 -0
  6. package/dist/admin/index.d.ts +6 -0
  7. package/dist/admin/index.js +3 -0
  8. package/dist/admin/role-store.d.ts +37 -0
  9. package/dist/admin/role-store.js +106 -0
  10. package/dist/auth/api-key-repository.d.ts +11 -0
  11. package/dist/auth/api-key-repository.js +33 -0
  12. package/dist/auth/api-key-repository.test.d.ts +1 -0
  13. package/dist/auth/api-key-repository.test.js +46 -0
  14. package/dist/auth/auth.test.d.ts +1 -0
  15. package/dist/auth/auth.test.js +140 -0
  16. package/dist/auth/better-auth.d.ts +42 -0
  17. package/dist/auth/better-auth.js +196 -0
  18. package/dist/auth/index.d.ts +186 -0
  19. package/dist/auth/index.js +422 -0
  20. package/dist/auth/login-history-repository.d.ts +14 -0
  21. package/dist/auth/login-history-repository.js +15 -0
  22. package/dist/auth/login-history-repository.test.d.ts +1 -0
  23. package/dist/auth/login-history-repository.test.js +47 -0
  24. package/dist/auth/middleware.d.ts +55 -0
  25. package/dist/auth/middleware.js +101 -0
  26. package/dist/auth/middleware.test.d.ts +1 -0
  27. package/dist/auth/middleware.test.js +213 -0
  28. package/dist/auth/scoped-tokens.test.d.ts +1 -0
  29. package/dist/auth/scoped-tokens.test.js +306 -0
  30. package/dist/auth/tenant-access.test.d.ts +1 -0
  31. package/dist/auth/tenant-access.test.js +62 -0
  32. package/dist/auth/user-creator.d.ts +9 -0
  33. package/dist/auth/user-creator.js +47 -0
  34. package/dist/auth/user-creator.test.d.ts +1 -0
  35. package/dist/auth/user-creator.test.js +78 -0
  36. package/dist/auth/user-role-repository.d.ts +31 -0
  37. package/dist/auth/user-role-repository.js +53 -0
  38. package/dist/auth/user-role-repository.test.d.ts +1 -0
  39. package/dist/auth/user-role-repository.test.js +122 -0
  40. package/dist/billing/drizzle-webhook-seen-repository.d.ts +10 -0
  41. package/dist/billing/drizzle-webhook-seen-repository.js +28 -0
  42. package/dist/billing/index.d.ts +7 -0
  43. package/dist/billing/index.js +7 -0
  44. package/dist/billing/payment-processor.d.ts +127 -0
  45. package/dist/billing/payment-processor.js +8 -0
  46. package/dist/billing/payment-processor.test.d.ts +1 -0
  47. package/dist/billing/payment-processor.test.js +71 -0
  48. package/dist/billing/payram/cents-credits-boundary.test.d.ts +1 -0
  49. package/dist/billing/payram/cents-credits-boundary.test.js +75 -0
  50. package/dist/billing/payram/charge-store.d.ts +41 -0
  51. package/dist/billing/payram/charge-store.js +72 -0
  52. package/dist/billing/payram/charge-store.test.d.ts +1 -0
  53. package/dist/billing/payram/charge-store.test.js +64 -0
  54. package/dist/billing/payram/checkout.d.ts +15 -0
  55. package/dist/billing/payram/checkout.js +24 -0
  56. package/dist/billing/payram/checkout.test.d.ts +1 -0
  57. package/dist/billing/payram/checkout.test.js +74 -0
  58. package/dist/billing/payram/client.d.ts +7 -0
  59. package/dist/billing/payram/client.js +15 -0
  60. package/dist/billing/payram/client.test.d.ts +1 -0
  61. package/dist/billing/payram/client.test.js +52 -0
  62. package/dist/billing/payram/index.d.ts +8 -0
  63. package/dist/billing/payram/index.js +4 -0
  64. package/dist/billing/payram/types.d.ts +40 -0
  65. package/dist/billing/payram/types.js +1 -0
  66. package/dist/billing/payram/webhook.d.ts +19 -0
  67. package/dist/billing/payram/webhook.js +67 -0
  68. package/dist/billing/payram/webhook.test.d.ts +7 -0
  69. package/dist/billing/payram/webhook.test.js +248 -0
  70. package/dist/billing/stripe/cents-credits-boundary.test.d.ts +1 -0
  71. package/dist/billing/stripe/cents-credits-boundary.test.js +62 -0
  72. package/dist/billing/stripe/checkout.d.ts +20 -0
  73. package/dist/billing/stripe/checkout.js +63 -0
  74. package/dist/billing/stripe/checkout.test.d.ts +1 -0
  75. package/dist/billing/stripe/checkout.test.js +148 -0
  76. package/dist/billing/stripe/client.d.ts +14 -0
  77. package/dist/billing/stripe/client.js +33 -0
  78. package/dist/billing/stripe/client.test.d.ts +1 -0
  79. package/dist/billing/stripe/client.test.js +58 -0
  80. package/dist/billing/stripe/credit-prices.d.ts +63 -0
  81. package/dist/billing/stripe/credit-prices.js +81 -0
  82. package/dist/billing/stripe/credit-prices.test.d.ts +1 -0
  83. package/dist/billing/stripe/credit-prices.test.js +87 -0
  84. package/dist/billing/stripe/index.d.ts +14 -0
  85. package/dist/billing/stripe/index.js +8 -0
  86. package/dist/billing/stripe/payment-methods-detach-all.test.d.ts +1 -0
  87. package/dist/billing/stripe/payment-methods-detach-all.test.js +40 -0
  88. package/dist/billing/stripe/payment-methods.d.ts +25 -0
  89. package/dist/billing/stripe/payment-methods.js +53 -0
  90. package/dist/billing/stripe/payment-methods.test.d.ts +1 -0
  91. package/dist/billing/stripe/payment-methods.test.js +122 -0
  92. package/dist/billing/stripe/portal.d.ts +10 -0
  93. package/dist/billing/stripe/portal.js +16 -0
  94. package/dist/billing/stripe/portal.test.d.ts +1 -0
  95. package/dist/billing/stripe/portal.test.js +48 -0
  96. package/dist/billing/stripe/setup-intent.d.ts +16 -0
  97. package/dist/billing/stripe/setup-intent.js +22 -0
  98. package/dist/billing/stripe/setup-intent.test.d.ts +1 -0
  99. package/dist/billing/stripe/setup-intent.test.js +58 -0
  100. package/dist/billing/stripe/stripe-payment-processor.d.ts +49 -0
  101. package/dist/billing/stripe/stripe-payment-processor.js +166 -0
  102. package/dist/billing/stripe/stripe-payment-processor.test.d.ts +1 -0
  103. package/dist/billing/stripe/stripe-payment-processor.test.js +413 -0
  104. package/dist/billing/stripe/tenant-store.d.ts +56 -0
  105. package/dist/billing/stripe/tenant-store.js +119 -0
  106. package/dist/billing/stripe/tenant-store.test.d.ts +1 -0
  107. package/dist/billing/stripe/tenant-store.test.js +97 -0
  108. package/dist/billing/stripe/types.d.ts +49 -0
  109. package/dist/billing/stripe/types.js +1 -0
  110. package/dist/billing/webhook-seen-repository.d.ts +14 -0
  111. package/dist/billing/webhook-seen-repository.js +13 -0
  112. package/dist/config/billing-env.test.d.ts +1 -0
  113. package/dist/config/billing-env.test.js +48 -0
  114. package/dist/config/index.d.ts +46 -0
  115. package/dist/config/index.js +38 -0
  116. package/dist/config/logger.d.ts +2 -0
  117. package/dist/config/logger.js +11 -0
  118. package/dist/config/provider-endpoints.d.ts +6 -0
  119. package/dist/config/provider-endpoints.js +12 -0
  120. package/dist/credits/auto-topup-charge.d.ts +27 -0
  121. package/dist/credits/auto-topup-charge.js +139 -0
  122. package/dist/credits/auto-topup-charge.test.d.ts +1 -0
  123. package/dist/credits/auto-topup-charge.test.js +242 -0
  124. package/dist/credits/auto-topup-event-log-repository.d.ts +16 -0
  125. package/dist/credits/auto-topup-event-log-repository.js +18 -0
  126. package/dist/credits/auto-topup-event-log-repository.test.d.ts +1 -0
  127. package/dist/credits/auto-topup-event-log-repository.test.js +83 -0
  128. package/dist/credits/auto-topup-schedule.d.ts +27 -0
  129. package/dist/credits/auto-topup-schedule.js +66 -0
  130. package/dist/credits/auto-topup-schedule.test.d.ts +1 -0
  131. package/dist/credits/auto-topup-schedule.test.js +145 -0
  132. package/dist/credits/auto-topup-settings-repository.d.ts +54 -0
  133. package/dist/credits/auto-topup-settings-repository.js +184 -0
  134. package/dist/credits/auto-topup-settings-repository.test.d.ts +1 -0
  135. package/dist/credits/auto-topup-settings-repository.test.js +104 -0
  136. package/dist/credits/auto-topup-usage.d.ts +22 -0
  137. package/dist/credits/auto-topup-usage.js +56 -0
  138. package/dist/credits/auto-topup-usage.test.d.ts +1 -0
  139. package/dist/credits/auto-topup-usage.test.js +181 -0
  140. package/dist/credits/credit-expiry-cron.d.ts +19 -0
  141. package/dist/credits/credit-expiry-cron.js +50 -0
  142. package/dist/credits/credit-expiry-cron.test.d.ts +1 -0
  143. package/dist/credits/credit-expiry-cron.test.js +67 -0
  144. package/dist/credits/credit-ledger-extra.test.d.ts +1 -0
  145. package/dist/credits/credit-ledger-extra.test.js +40 -0
  146. package/dist/credits/credit-ledger.bench.d.ts +1 -0
  147. package/dist/credits/credit-ledger.bench.js +33 -0
  148. package/dist/credits/credit-ledger.d.ts +130 -0
  149. package/dist/credits/credit-ledger.js +293 -0
  150. package/dist/credits/credit-ledger.test.d.ts +4 -0
  151. package/dist/credits/credit-ledger.test.js +203 -0
  152. package/dist/credits/credit-transaction-repository.d.ts +17 -0
  153. package/dist/credits/credit-transaction-repository.js +35 -0
  154. package/dist/credits/credit-transaction-repository.test.d.ts +1 -0
  155. package/dist/credits/credit-transaction-repository.test.js +232 -0
  156. package/dist/credits/credit.d.ts +75 -0
  157. package/dist/credits/credit.js +139 -0
  158. package/dist/credits/credit.test.d.ts +1 -0
  159. package/dist/credits/credit.test.js +196 -0
  160. package/dist/credits/dividend-cron.d.ts +29 -0
  161. package/dist/credits/dividend-cron.js +88 -0
  162. package/dist/credits/dividend-cron.test.d.ts +1 -0
  163. package/dist/credits/dividend-cron.test.js +128 -0
  164. package/dist/credits/dividend-repository.d.ts +29 -0
  165. package/dist/credits/dividend-repository.js +126 -0
  166. package/dist/credits/dividend-repository.test.d.ts +1 -0
  167. package/dist/credits/dividend-repository.test.js +176 -0
  168. package/dist/credits/index.d.ts +9 -0
  169. package/dist/credits/index.js +5 -0
  170. package/dist/credits/repository-types.d.ts +29 -0
  171. package/dist/credits/repository-types.js +1 -0
  172. package/dist/credits/signup-grant.d.ts +12 -0
  173. package/dist/credits/signup-grant.js +35 -0
  174. package/dist/credits/signup-grant.test.d.ts +1 -0
  175. package/dist/credits/signup-grant.test.js +51 -0
  176. package/dist/credits/tenant-customer-repository.d.ts +30 -0
  177. package/dist/credits/tenant-customer-repository.js +5 -0
  178. package/dist/db/auth-user-repository.d.ts +46 -0
  179. package/dist/db/auth-user-repository.js +90 -0
  180. package/dist/db/credit-column.d.ts +27 -0
  181. package/dist/db/credit-column.js +13 -0
  182. package/dist/db/index.d.ts +14 -0
  183. package/dist/db/index.js +8 -0
  184. package/dist/db/schema/account-deletion-requests.d.ts +203 -0
  185. package/dist/db/schema/account-deletion-requests.js +36 -0
  186. package/dist/db/schema/account-export-requests.d.ts +148 -0
  187. package/dist/db/schema/account-export-requests.js +19 -0
  188. package/dist/db/schema/admin-audit.d.ts +194 -0
  189. package/dist/db/schema/admin-audit.js +21 -0
  190. package/dist/db/schema/admin-users.d.ts +177 -0
  191. package/dist/db/schema/admin-users.js +23 -0
  192. package/dist/db/schema/affiliate-fraud.d.ts +160 -0
  193. package/dist/db/schema/affiliate-fraud.js +18 -0
  194. package/dist/db/schema/affiliate.d.ts +277 -0
  195. package/dist/db/schema/affiliate.js +32 -0
  196. package/dist/db/schema/coupon-codes.d.ts +143 -0
  197. package/dist/db/schema/coupon-codes.js +17 -0
  198. package/dist/db/schema/credit-auto-topup-settings.d.ts +232 -0
  199. package/dist/db/schema/credit-auto-topup-settings.js +27 -0
  200. package/dist/db/schema/credit-auto-topup.d.ts +130 -0
  201. package/dist/db/schema/credit-auto-topup.js +21 -0
  202. package/dist/db/schema/credits.d.ts +283 -0
  203. package/dist/db/schema/credits.js +38 -0
  204. package/dist/db/schema/dividend-distributions.d.ts +130 -0
  205. package/dist/db/schema/dividend-distributions.js +19 -0
  206. package/dist/db/schema/email-notifications.d.ts +99 -0
  207. package/dist/db/schema/email-notifications.js +21 -0
  208. package/dist/db/schema/index.d.ts +33 -0
  209. package/dist/db/schema/index.js +33 -0
  210. package/dist/db/schema/meter-events.d.ts +599 -0
  211. package/dist/db/schema/meter-events.js +55 -0
  212. package/dist/db/schema/notification-preferences.d.ts +165 -0
  213. package/dist/db/schema/notification-preferences.js +18 -0
  214. package/dist/db/schema/notification-queue.d.ts +236 -0
  215. package/dist/db/schema/notification-queue.js +40 -0
  216. package/dist/db/schema/org-memberships.d.ts +63 -0
  217. package/dist/db/schema/org-memberships.js +15 -0
  218. package/dist/db/schema/organization-members.d.ts +235 -0
  219. package/dist/db/schema/organization-members.js +27 -0
  220. package/dist/db/schema/payram.d.ts +164 -0
  221. package/dist/db/schema/payram.js +21 -0
  222. package/dist/db/schema/platform-api-keys.d.ts +143 -0
  223. package/dist/db/schema/platform-api-keys.js +20 -0
  224. package/dist/db/schema/promotion-redemptions.d.ts +143 -0
  225. package/dist/db/schema/promotion-redemptions.js +18 -0
  226. package/dist/db/schema/promotions.d.ts +445 -0
  227. package/dist/db/schema/promotions.js +48 -0
  228. package/dist/db/schema/provider-credentials.d.ts +201 -0
  229. package/dist/db/schema/provider-credentials.js +36 -0
  230. package/dist/db/schema/rate-limit-entries.d.ts +75 -0
  231. package/dist/db/schema/rate-limit-entries.js +7 -0
  232. package/dist/db/schema/secret-audit-log.d.ts +109 -0
  233. package/dist/db/schema/secret-audit-log.js +15 -0
  234. package/dist/db/schema/session-usage.d.ts +194 -0
  235. package/dist/db/schema/session-usage.js +19 -0
  236. package/dist/db/schema/spending-limits.d.ts +92 -0
  237. package/dist/db/schema/spending-limits.js +8 -0
  238. package/dist/db/schema/tenant-addons.d.ts +58 -0
  239. package/dist/db/schema/tenant-addons.js +9 -0
  240. package/dist/db/schema/tenant-api-keys.d.ts +131 -0
  241. package/dist/db/schema/tenant-api-keys.js +21 -0
  242. package/dist/db/schema/tenant-capability-settings.d.ts +79 -0
  243. package/dist/db/schema/tenant-capability-settings.js +12 -0
  244. package/dist/db/schema/tenant-customers.d.ts +303 -0
  245. package/dist/db/schema/tenant-customers.js +25 -0
  246. package/dist/db/schema/tenants.d.ts +126 -0
  247. package/dist/db/schema/tenants.js +18 -0
  248. package/dist/db/schema/user-roles.d.ts +98 -0
  249. package/dist/db/schema/user-roles.js +18 -0
  250. package/dist/db/schema/webhook-seen-events.d.ts +58 -0
  251. package/dist/db/schema/webhook-seen-events.js +9 -0
  252. package/dist/email/billing-emails.d.ts +51 -0
  253. package/dist/email/billing-emails.js +163 -0
  254. package/dist/email/billing-emails.test.d.ts +1 -0
  255. package/dist/email/billing-emails.test.js +162 -0
  256. package/dist/email/client.d.ts +51 -0
  257. package/dist/email/client.js +102 -0
  258. package/dist/email/client.test.d.ts +1 -0
  259. package/dist/email/client.test.js +120 -0
  260. package/dist/email/drizzle-billing-email-repository.d.ts +21 -0
  261. package/dist/email/drizzle-billing-email-repository.js +36 -0
  262. package/dist/email/drizzle-billing-email-repository.test.d.ts +1 -0
  263. package/dist/email/drizzle-billing-email-repository.test.js +42 -0
  264. package/dist/email/index.d.ts +33 -0
  265. package/dist/email/index.js +22 -0
  266. package/dist/email/notification-preferences-store.d.ts +12 -0
  267. package/dist/email/notification-preferences-store.js +82 -0
  268. package/dist/email/notification-preferences-store.test.d.ts +1 -0
  269. package/dist/email/notification-preferences-store.test.js +86 -0
  270. package/dist/email/notification-queue-store.d.ts +25 -0
  271. package/dist/email/notification-queue-store.js +97 -0
  272. package/dist/email/notification-queue-store.test.d.ts +1 -0
  273. package/dist/email/notification-queue-store.test.js +177 -0
  274. package/dist/email/notification-repository-types.d.ts +70 -0
  275. package/dist/email/notification-repository-types.js +6 -0
  276. package/dist/email/notification-service.d.ts +41 -0
  277. package/dist/email/notification-service.js +196 -0
  278. package/dist/email/notification-service.test.d.ts +1 -0
  279. package/dist/email/notification-service.test.js +160 -0
  280. package/dist/email/notification-templates.d.ts +18 -0
  281. package/dist/email/notification-templates.js +574 -0
  282. package/dist/email/notification-templates.test.d.ts +1 -0
  283. package/dist/email/notification-templates.test.js +238 -0
  284. package/dist/email/notification-worker.d.ts +24 -0
  285. package/dist/email/notification-worker.js +109 -0
  286. package/dist/email/notification-worker.test.d.ts +1 -0
  287. package/dist/email/notification-worker.test.js +153 -0
  288. package/dist/email/require-verified.d.ts +25 -0
  289. package/dist/email/require-verified.js +52 -0
  290. package/dist/email/require-verified.test.d.ts +1 -0
  291. package/dist/email/require-verified.test.js +62 -0
  292. package/dist/email/resend-adapter.d.ts +47 -0
  293. package/dist/email/resend-adapter.js +137 -0
  294. package/dist/email/resend-adapter.test.d.ts +1 -0
  295. package/dist/email/resend-adapter.test.js +190 -0
  296. package/dist/email/templates.d.ts +22 -0
  297. package/dist/email/templates.js +359 -0
  298. package/dist/email/templates.test.d.ts +1 -0
  299. package/dist/email/templates.test.js +170 -0
  300. package/dist/email/verification.d.ts +42 -0
  301. package/dist/email/verification.js +83 -0
  302. package/dist/email/verification.test.d.ts +1 -0
  303. package/dist/email/verification.test.js +141 -0
  304. package/dist/index.d.ts +13 -0
  305. package/dist/index.js +23 -0
  306. package/dist/metering/aggregator.d.ts +54 -0
  307. package/dist/metering/aggregator.js +123 -0
  308. package/dist/metering/aggregator.test.d.ts +1 -0
  309. package/dist/metering/aggregator.test.js +179 -0
  310. package/dist/metering/dlq.d.ts +31 -0
  311. package/dist/metering/dlq.js +82 -0
  312. package/dist/metering/dlq.test.d.ts +1 -0
  313. package/dist/metering/dlq.test.js +117 -0
  314. package/dist/metering/drizzle-usage-summary-repository.d.ts +67 -0
  315. package/dist/metering/drizzle-usage-summary-repository.js +98 -0
  316. package/dist/metering/emitter.d.ts +66 -0
  317. package/dist/metering/emitter.js +185 -0
  318. package/dist/metering/emitter.test.d.ts +1 -0
  319. package/dist/metering/emitter.test.js +171 -0
  320. package/dist/metering/index.d.ts +11 -0
  321. package/dist/metering/index.js +5 -0
  322. package/dist/metering/load-test.bench.d.ts +1 -0
  323. package/dist/metering/load-test.bench.js +103 -0
  324. package/dist/metering/meter-event-repository.d.ts +33 -0
  325. package/dist/metering/meter-event-repository.js +58 -0
  326. package/dist/metering/meter-repositories.test.d.ts +1 -0
  327. package/dist/metering/meter-repositories.test.js +419 -0
  328. package/dist/metering/metering.test.d.ts +1 -0
  329. package/dist/metering/metering.test.js +1046 -0
  330. package/dist/metering/reconciliation-cron.d.ts +37 -0
  331. package/dist/metering/reconciliation-cron.js +85 -0
  332. package/dist/metering/reconciliation-cron.test.d.ts +1 -0
  333. package/dist/metering/reconciliation-cron.test.js +162 -0
  334. package/dist/metering/reconciliation-repository.d.ts +27 -0
  335. package/dist/metering/reconciliation-repository.js +43 -0
  336. package/dist/metering/reconciliation-repository.test.d.ts +1 -0
  337. package/dist/metering/reconciliation-repository.test.js +160 -0
  338. package/dist/metering/types.d.ts +88 -0
  339. package/dist/metering/types.js +1 -0
  340. package/dist/metering/wal.d.ts +49 -0
  341. package/dist/metering/wal.js +124 -0
  342. package/dist/metering/wal.test.d.ts +1 -0
  343. package/dist/metering/wal.test.js +175 -0
  344. package/dist/middleware/csrf.d.ts +24 -0
  345. package/dist/middleware/csrf.js +80 -0
  346. package/dist/middleware/csrf.test.d.ts +1 -0
  347. package/dist/middleware/csrf.test.js +152 -0
  348. package/dist/middleware/drizzle-rate-limit-repository.d.ts +9 -0
  349. package/dist/middleware/drizzle-rate-limit-repository.js +52 -0
  350. package/dist/middleware/drizzle-rate-limit-repository.test.d.ts +1 -0
  351. package/dist/middleware/drizzle-rate-limit-repository.test.js +74 -0
  352. package/dist/middleware/get-client-ip.d.ts +22 -0
  353. package/dist/middleware/get-client-ip.js +51 -0
  354. package/dist/middleware/get-client-ip.test.d.ts +1 -0
  355. package/dist/middleware/get-client-ip.test.js +40 -0
  356. package/dist/middleware/index.d.ts +5 -0
  357. package/dist/middleware/index.js +4 -0
  358. package/dist/middleware/rate-limit-repository.d.ts +19 -0
  359. package/dist/middleware/rate-limit-repository.js +1 -0
  360. package/dist/middleware/rate-limit.d.ts +57 -0
  361. package/dist/middleware/rate-limit.js +109 -0
  362. package/dist/middleware/rate-limit.test.d.ts +1 -0
  363. package/dist/middleware/rate-limit.test.js +247 -0
  364. package/dist/security/credential-vault/audit-repository.d.ts +27 -0
  365. package/dist/security/credential-vault/audit-repository.js +42 -0
  366. package/dist/security/credential-vault/audit-repository.test.d.ts +1 -0
  367. package/dist/security/credential-vault/audit-repository.test.js +78 -0
  368. package/dist/security/credential-vault/credential-repository.d.ts +94 -0
  369. package/dist/security/credential-vault/credential-repository.js +145 -0
  370. package/dist/security/credential-vault/credential-repository.test.d.ts +1 -0
  371. package/dist/security/credential-vault/credential-repository.test.js +206 -0
  372. package/dist/security/credential-vault/index.d.ts +12 -0
  373. package/dist/security/credential-vault/index.js +6 -0
  374. package/dist/security/credential-vault/key-rotation.d.ts +18 -0
  375. package/dist/security/credential-vault/key-rotation.js +52 -0
  376. package/dist/security/credential-vault/key-rotation.test.d.ts +1 -0
  377. package/dist/security/credential-vault/key-rotation.test.js +95 -0
  378. package/dist/security/credential-vault/migrate-plaintext.d.ts +15 -0
  379. package/dist/security/credential-vault/migrate-plaintext.js +80 -0
  380. package/dist/security/credential-vault/migrate-plaintext.test.d.ts +1 -0
  381. package/dist/security/credential-vault/migrate-plaintext.test.js +111 -0
  382. package/dist/security/credential-vault/migration-check.d.ts +15 -0
  383. package/dist/security/credential-vault/migration-check.js +71 -0
  384. package/dist/security/credential-vault/migration-check.test.d.ts +1 -0
  385. package/dist/security/credential-vault/migration-check.test.js +457 -0
  386. package/dist/security/credential-vault/store.d.ts +106 -0
  387. package/dist/security/credential-vault/store.js +181 -0
  388. package/dist/security/credential-vault/store.test.d.ts +1 -0
  389. package/dist/security/credential-vault/store.test.js +482 -0
  390. package/dist/security/encryption.d.ts +22 -0
  391. package/dist/security/encryption.js +53 -0
  392. package/dist/security/encryption.test.d.ts +1 -0
  393. package/dist/security/encryption.test.js +95 -0
  394. package/dist/security/host-validation.d.ts +11 -0
  395. package/dist/security/host-validation.js +108 -0
  396. package/dist/security/host-validation.test.d.ts +1 -0
  397. package/dist/security/host-validation.test.js +106 -0
  398. package/dist/security/index.d.ts +11 -0
  399. package/dist/security/index.js +11 -0
  400. package/dist/security/key-audit.d.ts +16 -0
  401. package/dist/security/key-audit.js +35 -0
  402. package/dist/security/key-audit.test.d.ts +1 -0
  403. package/dist/security/key-audit.test.js +50 -0
  404. package/dist/security/key-injection.d.ts +28 -0
  405. package/dist/security/key-injection.js +57 -0
  406. package/dist/security/key-injection.test.d.ts +1 -0
  407. package/dist/security/key-injection.test.js +97 -0
  408. package/dist/security/key-validation.d.ts +16 -0
  409. package/dist/security/key-validation.js +78 -0
  410. package/dist/security/key-validation.test.d.ts +1 -0
  411. package/dist/security/key-validation.test.js +87 -0
  412. package/dist/security/redirect-allowlist.d.ts +6 -0
  413. package/dist/security/redirect-allowlist.js +36 -0
  414. package/dist/security/redirect-allowlist.test.d.ts +1 -0
  415. package/dist/security/redirect-allowlist.test.js +55 -0
  416. package/dist/security/tenant-keys/capability-settings-store.d.ts +22 -0
  417. package/dist/security/tenant-keys/capability-settings-store.js +33 -0
  418. package/dist/security/tenant-keys/capability-settings-store.test.d.ts +1 -0
  419. package/dist/security/tenant-keys/capability-settings-store.test.js +77 -0
  420. package/dist/security/tenant-keys/index.d.ts +10 -0
  421. package/dist/security/tenant-keys/index.js +5 -0
  422. package/dist/security/tenant-keys/key-resolution-repository.d.ts +15 -0
  423. package/dist/security/tenant-keys/key-resolution-repository.js +18 -0
  424. package/dist/security/tenant-keys/key-resolution-repository.test.d.ts +1 -0
  425. package/dist/security/tenant-keys/key-resolution-repository.test.js +72 -0
  426. package/dist/security/tenant-keys/key-resolution.d.ts +39 -0
  427. package/dist/security/tenant-keys/key-resolution.js +59 -0
  428. package/dist/security/tenant-keys/key-resolution.test.d.ts +1 -0
  429. package/dist/security/tenant-keys/key-resolution.test.js +97 -0
  430. package/dist/security/tenant-keys/org-key-resolution.d.ts +30 -0
  431. package/dist/security/tenant-keys/org-key-resolution.js +50 -0
  432. package/dist/security/tenant-keys/org-key-resolution.test.d.ts +1 -0
  433. package/dist/security/tenant-keys/org-key-resolution.test.js +103 -0
  434. package/dist/security/tenant-keys/tenant-key-repository.d.ts +36 -0
  435. package/dist/security/tenant-keys/tenant-key-repository.js +96 -0
  436. package/dist/security/tenant-keys/tenant-key-repository.test.d.ts +1 -0
  437. package/dist/security/tenant-keys/tenant-key-repository.test.js +114 -0
  438. package/dist/security/types.d.ts +35 -0
  439. package/dist/security/types.js +15 -0
  440. package/dist/tenancy/drizzle-org-repository.d.ts +40 -0
  441. package/dist/tenancy/drizzle-org-repository.js +126 -0
  442. package/dist/tenancy/index.d.ts +6 -0
  443. package/dist/tenancy/index.js +3 -0
  444. package/dist/tenancy/org-member-repository.d.ts +57 -0
  445. package/dist/tenancy/org-member-repository.js +99 -0
  446. package/dist/tenancy/org-repository.test.d.ts +1 -0
  447. package/dist/tenancy/org-repository.test.js +143 -0
  448. package/dist/tenancy/org-service.d.ts +70 -0
  449. package/dist/tenancy/org-service.js +223 -0
  450. package/dist/tenancy/org-service.test.d.ts +1 -0
  451. package/dist/tenancy/org-service.test.js +550 -0
  452. package/dist/test/db.d.ts +33 -0
  453. package/dist/test/db.js +65 -0
  454. package/dist/trpc/index.d.ts +1 -0
  455. package/dist/trpc/index.js +1 -0
  456. package/dist/trpc/init.d.ts +49 -0
  457. package/dist/trpc/init.js +108 -0
  458. package/dist/trpc/init.test.d.ts +1 -0
  459. package/dist/trpc/init.test.js +154 -0
  460. package/drizzle/migrations/0000_slippery_mandrill.sql +559 -0
  461. package/drizzle/migrations/meta/0000_snapshot.json +4374 -0
  462. package/drizzle/migrations/meta/_journal.json +13 -0
  463. package/drizzle.config.ts +41 -0
  464. package/package.json +64 -0
  465. package/src/admin/admin-audit-log-repository.ts +135 -0
  466. package/src/admin/audit-log.ts +111 -0
  467. package/src/admin/index.ts +6 -0
  468. package/src/admin/role-store.ts +134 -0
  469. package/src/auth/api-key-repository.test.ts +63 -0
  470. package/src/auth/api-key-repository.ts +46 -0
  471. package/src/auth/auth.test.ts +166 -0
  472. package/src/auth/better-auth.ts +216 -0
  473. package/src/auth/index.ts +520 -0
  474. package/src/auth/login-history-repository.test.ts +54 -0
  475. package/src/auth/login-history-repository.ts +28 -0
  476. package/src/auth/middleware.test.ts +264 -0
  477. package/src/auth/middleware.ts +117 -0
  478. package/src/auth/scoped-tokens.test.ts +362 -0
  479. package/src/auth/tenant-access.test.ts +69 -0
  480. package/src/auth/user-creator.test.ts +98 -0
  481. package/src/auth/user-creator.ts +54 -0
  482. package/src/auth/user-role-repository.test.ts +149 -0
  483. package/src/auth/user-role-repository.ts +67 -0
  484. package/src/billing/drizzle-webhook-seen-repository.ts +34 -0
  485. package/src/billing/index.ts +22 -0
  486. package/src/billing/payment-processor.test.ts +93 -0
  487. package/src/billing/payment-processor.ts +150 -0
  488. package/src/billing/payram/cents-credits-boundary.test.ts +84 -0
  489. package/src/billing/payram/charge-store.test.ts +84 -0
  490. package/src/billing/payram/charge-store.ts +109 -0
  491. package/src/billing/payram/checkout.test.ts +99 -0
  492. package/src/billing/payram/checkout.ts +40 -0
  493. package/src/billing/payram/client.test.ts +62 -0
  494. package/src/billing/payram/client.ts +21 -0
  495. package/src/billing/payram/index.ts +14 -0
  496. package/src/billing/payram/types.ts +44 -0
  497. package/src/billing/payram/webhook.test.ts +318 -0
  498. package/src/billing/payram/webhook.ts +97 -0
  499. package/src/billing/stripe/cents-credits-boundary.test.ts +70 -0
  500. package/src/billing/stripe/checkout.test.ts +186 -0
  501. package/src/billing/stripe/checkout.ts +82 -0
  502. package/src/billing/stripe/client.test.ts +64 -0
  503. package/src/billing/stripe/client.ts +39 -0
  504. package/src/billing/stripe/credit-prices.test.ts +114 -0
  505. package/src/billing/stripe/credit-prices.ts +113 -0
  506. package/src/billing/stripe/index.ts +14 -0
  507. package/src/billing/stripe/payment-methods-detach-all.test.ts +53 -0
  508. package/src/billing/stripe/payment-methods.test.ts +157 -0
  509. package/src/billing/stripe/payment-methods.ts +76 -0
  510. package/src/billing/stripe/portal.test.ts +63 -0
  511. package/src/billing/stripe/portal.ts +25 -0
  512. package/src/billing/stripe/setup-intent.test.ts +78 -0
  513. package/src/billing/stripe/setup-intent.ts +34 -0
  514. package/src/billing/stripe/stripe-payment-processor.test.ts +517 -0
  515. package/src/billing/stripe/stripe-payment-processor.ts +255 -0
  516. package/src/billing/stripe/tenant-store.test.ts +124 -0
  517. package/src/billing/stripe/tenant-store.ts +151 -0
  518. package/src/billing/stripe/types.ts +53 -0
  519. package/src/billing/webhook-seen-repository.ts +24 -0
  520. package/src/config/billing-env.test.ts +54 -0
  521. package/src/config/index.ts +44 -0
  522. package/src/config/logger.ts +12 -0
  523. package/src/config/provider-endpoints.ts +14 -0
  524. package/src/credits/auto-topup-charge.test.ts +292 -0
  525. package/src/credits/auto-topup-charge.ts +171 -0
  526. package/src/credits/auto-topup-event-log-repository.test.ts +99 -0
  527. package/src/credits/auto-topup-event-log-repository.ts +30 -0
  528. package/src/credits/auto-topup-schedule.test.ts +179 -0
  529. package/src/credits/auto-topup-schedule.ts +93 -0
  530. package/src/credits/auto-topup-settings-repository.test.ts +123 -0
  531. package/src/credits/auto-topup-settings-repository.ts +245 -0
  532. package/src/credits/auto-topup-usage.test.ts +220 -0
  533. package/src/credits/auto-topup-usage.ts +68 -0
  534. package/src/credits/credit-expiry-cron.test.ts +125 -0
  535. package/src/credits/credit-expiry-cron.ts +76 -0
  536. package/src/credits/credit-ledger-extra.test.ts +57 -0
  537. package/src/credits/credit-ledger.bench.ts +56 -0
  538. package/src/credits/credit-ledger.test.ts +276 -0
  539. package/src/credits/credit-ledger.ts +450 -0
  540. package/src/credits/credit-transaction-repository.test.ts +274 -0
  541. package/src/credits/credit-transaction-repository.ts +62 -0
  542. package/src/credits/credit.test.ts +234 -0
  543. package/src/credits/credit.ts +160 -0
  544. package/src/credits/dividend-cron.test.ts +158 -0
  545. package/src/credits/dividend-cron.ts +127 -0
  546. package/src/credits/dividend-repository.test.ts +223 -0
  547. package/src/credits/dividend-repository.ts +182 -0
  548. package/src/credits/index.ts +25 -0
  549. package/src/credits/repository-types.ts +33 -0
  550. package/src/credits/signup-grant.test.ts +63 -0
  551. package/src/credits/signup-grant.ts +44 -0
  552. package/src/credits/tenant-customer-repository.ts +28 -0
  553. package/src/db/auth-user-repository.ts +124 -0
  554. package/src/db/credit-column.ts +17 -0
  555. package/src/db/index.ts +21 -0
  556. package/src/db/schema/account-deletion-requests.ts +41 -0
  557. package/src/db/schema/account-export-requests.ts +24 -0
  558. package/src/db/schema/admin-audit.ts +26 -0
  559. package/src/db/schema/admin-users.ts +31 -0
  560. package/src/db/schema/affiliate-fraud.ts +23 -0
  561. package/src/db/schema/affiliate.ts +38 -0
  562. package/src/db/schema/coupon-codes.ts +22 -0
  563. package/src/db/schema/credit-auto-topup-settings.ts +32 -0
  564. package/src/db/schema/credit-auto-topup.ts +26 -0
  565. package/src/db/schema/credits.ts +44 -0
  566. package/src/db/schema/dividend-distributions.ts +24 -0
  567. package/src/db/schema/email-notifications.ts +26 -0
  568. package/src/db/schema/index.ts +33 -0
  569. package/src/db/schema/meter-events.ts +70 -0
  570. package/src/db/schema/notification-preferences.ts +19 -0
  571. package/src/db/schema/notification-queue.ts +45 -0
  572. package/src/db/schema/org-memberships.ts +20 -0
  573. package/src/db/schema/organization-members.ts +37 -0
  574. package/src/db/schema/payram.ts +26 -0
  575. package/src/db/schema/platform-api-keys.ts +25 -0
  576. package/src/db/schema/promotion-redemptions.ts +23 -0
  577. package/src/db/schema/promotions.ts +57 -0
  578. package/src/db/schema/provider-credentials.ts +41 -0
  579. package/src/db/schema/rate-limit-entries.ts +12 -0
  580. package/src/db/schema/secret-audit-log.ts +20 -0
  581. package/src/db/schema/session-usage.ts +24 -0
  582. package/src/db/schema/spending-limits.ts +9 -0
  583. package/src/db/schema/tenant-addons.ts +14 -0
  584. package/src/db/schema/tenant-api-keys.ts +26 -0
  585. package/src/db/schema/tenant-capability-settings.ts +17 -0
  586. package/src/db/schema/tenant-customers.ts +35 -0
  587. package/src/db/schema/tenants.ts +23 -0
  588. package/src/db/schema/user-roles.ts +23 -0
  589. package/src/db/schema/webhook-seen-events.ts +14 -0
  590. package/src/email/billing-emails.test.ts +198 -0
  591. package/src/email/billing-emails.ts +211 -0
  592. package/src/email/client.test.ts +149 -0
  593. package/src/email/client.ts +137 -0
  594. package/src/email/drizzle-billing-email-repository.test.ts +52 -0
  595. package/src/email/drizzle-billing-email-repository.ts +59 -0
  596. package/src/email/index.ts +57 -0
  597. package/src/email/notification-preferences-store.test.ts +102 -0
  598. package/src/email/notification-preferences-store.ts +90 -0
  599. package/src/email/notification-queue-store.test.ts +215 -0
  600. package/src/email/notification-queue-store.ts +127 -0
  601. package/src/email/notification-repository-types.ts +101 -0
  602. package/src/email/notification-service.test.ts +178 -0
  603. package/src/email/notification-service.ts +265 -0
  604. package/src/email/notification-templates.test.ts +261 -0
  605. package/src/email/notification-templates.ts +727 -0
  606. package/src/email/notification-worker.test.ts +189 -0
  607. package/src/email/notification-worker.ts +133 -0
  608. package/src/email/require-verified.ts +65 -0
  609. package/src/email/resend-adapter.test.ts +253 -0
  610. package/src/email/resend-adapter.ts +157 -0
  611. package/src/email/templates.test.ts +217 -0
  612. package/src/email/templates.ts +469 -0
  613. package/src/email/verification.test.ts +185 -0
  614. package/src/email/verification.ts +110 -0
  615. package/src/index.ts +51 -0
  616. package/src/metering/aggregator.test.ts +239 -0
  617. package/src/metering/aggregator.ts +160 -0
  618. package/src/metering/dlq.test.ts +134 -0
  619. package/src/metering/dlq.ts +102 -0
  620. package/src/metering/drizzle-usage-summary-repository.ts +167 -0
  621. package/src/metering/emitter.test.ts +202 -0
  622. package/src/metering/emitter.ts +227 -0
  623. package/src/metering/index.ts +21 -0
  624. package/src/metering/load-test.bench.ts +130 -0
  625. package/src/metering/meter-event-repository.ts +87 -0
  626. package/src/metering/meter-repositories.test.ts +491 -0
  627. package/src/metering/metering.test.ts +1317 -0
  628. package/src/metering/reconciliation-cron.test.ts +202 -0
  629. package/src/metering/reconciliation-cron.ts +134 -0
  630. package/src/metering/reconciliation-repository.test.ts +196 -0
  631. package/src/metering/reconciliation-repository.ts +83 -0
  632. package/src/metering/types.ts +93 -0
  633. package/src/metering/wal.test.ts +222 -0
  634. package/src/metering/wal.ts +139 -0
  635. package/src/middleware/csrf.test.ts +178 -0
  636. package/src/middleware/csrf.ts +101 -0
  637. package/src/middleware/drizzle-rate-limit-repository.test.ts +97 -0
  638. package/src/middleware/drizzle-rate-limit-repository.ts +57 -0
  639. package/src/middleware/get-client-ip.test.ts +49 -0
  640. package/src/middleware/get-client-ip.ts +62 -0
  641. package/src/middleware/index.ts +12 -0
  642. package/src/middleware/rate-limit-repository.ts +22 -0
  643. package/src/middleware/rate-limit.test.ts +338 -0
  644. package/src/middleware/rate-limit.ts +169 -0
  645. package/src/security/credential-vault/audit-repository.test.ts +91 -0
  646. package/src/security/credential-vault/audit-repository.ts +64 -0
  647. package/src/security/credential-vault/credential-repository.test.ts +264 -0
  648. package/src/security/credential-vault/credential-repository.ts +233 -0
  649. package/src/security/credential-vault/index.ts +26 -0
  650. package/src/security/credential-vault/key-rotation.test.ts +139 -0
  651. package/src/security/credential-vault/key-rotation.ts +70 -0
  652. package/src/security/credential-vault/migrate-plaintext.test.ts +138 -0
  653. package/src/security/credential-vault/migrate-plaintext.ts +101 -0
  654. package/src/security/credential-vault/migration-check.test.ts +533 -0
  655. package/src/security/credential-vault/migration-check.ts +88 -0
  656. package/src/security/credential-vault/store.test.ts +569 -0
  657. package/src/security/credential-vault/store.ts +284 -0
  658. package/src/security/encryption.test.ts +114 -0
  659. package/src/security/encryption.ts +65 -0
  660. package/src/security/host-validation.test.ts +136 -0
  661. package/src/security/host-validation.ts +116 -0
  662. package/src/security/index.ts +59 -0
  663. package/src/security/key-audit.test.ts +57 -0
  664. package/src/security/key-audit.ts +45 -0
  665. package/src/security/key-injection.test.ts +131 -0
  666. package/src/security/key-injection.ts +71 -0
  667. package/src/security/key-validation.test.ts +111 -0
  668. package/src/security/key-validation.ts +84 -0
  669. package/src/security/redirect-allowlist.test.ts +70 -0
  670. package/src/security/redirect-allowlist.ts +35 -0
  671. package/src/security/tenant-keys/capability-settings-store.test.ts +98 -0
  672. package/src/security/tenant-keys/capability-settings-store.ts +53 -0
  673. package/src/security/tenant-keys/index.ts +10 -0
  674. package/src/security/tenant-keys/key-resolution-repository.test.ts +95 -0
  675. package/src/security/tenant-keys/key-resolution-repository.ts +31 -0
  676. package/src/security/tenant-keys/key-resolution.test.ts +173 -0
  677. package/src/security/tenant-keys/key-resolution.ts +87 -0
  678. package/src/security/tenant-keys/org-key-resolution.test.ts +217 -0
  679. package/src/security/tenant-keys/org-key-resolution.ts +76 -0
  680. package/src/security/tenant-keys/tenant-key-repository.test.ts +143 -0
  681. package/src/security/tenant-keys/tenant-key-repository.ts +130 -0
  682. package/src/security/types.ts +43 -0
  683. package/src/tenancy/drizzle-org-repository.ts +169 -0
  684. package/src/tenancy/index.ts +6 -0
  685. package/src/tenancy/org-member-repository.ts +159 -0
  686. package/src/tenancy/org-repository.test.ts +172 -0
  687. package/src/tenancy/org-service.test.ts +634 -0
  688. package/src/tenancy/org-service.ts +290 -0
  689. package/src/test/db.ts +97 -0
  690. package/src/trpc/index.ts +11 -0
  691. package/src/trpc/init.test.ts +196 -0
  692. package/src/trpc/init.ts +138 -0
  693. package/tsconfig.json +20 -0
  694. package/vitest.config.ts +8 -0
@@ -0,0 +1,44 @@
1
+ import { z } from "zod";
2
+
3
+ const platformConfigSchema = z.object({
4
+ port: z.coerce.number().default(3100),
5
+ nodeEnv: z.enum(["development", "production", "test"]).default("development"),
6
+ logLevel: z.enum(["error", "warn", "info", "debug"]).default("info"),
7
+
8
+ /** Billing / affiliate / metering numeric env vars — validated at startup. */
9
+ billing: z
10
+ .object({
11
+ affiliateMatchRate: z.coerce.number().min(0).max(10).default(1.0),
12
+ affiliateMaxReferrals30d: z.coerce.number().int().min(0).default(20),
13
+ affiliateMaxMatchCredits30d: z.coerce.number().int().min(0).default(20000),
14
+ affiliateNewUserBonusRate: z.coerce.number().min(0).max(1).default(0.2),
15
+ dividendMatchRate: z.coerce.number().min(0).max(10).default(1.0),
16
+ meterMaxRetries: z.coerce.number().int().min(0).max(100).default(3),
17
+ })
18
+ .default({
19
+ affiliateMatchRate: 1.0,
20
+ affiliateMaxReferrals30d: 20,
21
+ affiliateMaxMatchCredits30d: 20000,
22
+ affiliateNewUserBonusRate: 0.2,
23
+ dividendMatchRate: 1.0,
24
+ meterMaxRetries: 3,
25
+ }),
26
+ });
27
+
28
+ export const billingConfigSchema = platformConfigSchema.shape.billing;
29
+
30
+ export const config = platformConfigSchema.parse({
31
+ port: process.env.PORT,
32
+ nodeEnv: process.env.NODE_ENV,
33
+ logLevel: process.env.LOG_LEVEL,
34
+ billing: {
35
+ affiliateMatchRate: process.env.AFFILIATE_MATCH_RATE,
36
+ affiliateMaxReferrals30d: process.env.AFFILIATE_MAX_REFERRALS_30D,
37
+ affiliateMaxMatchCredits30d: process.env.AFFILIATE_MAX_MATCH_CREDITS_30D,
38
+ affiliateNewUserBonusRate: process.env.AFFILIATE_NEW_USER_BONUS_RATE,
39
+ dividendMatchRate: process.env.DIVIDEND_MATCH_RATE,
40
+ meterMaxRetries: process.env.METER_MAX_RETRIES,
41
+ },
42
+ });
43
+
44
+ export type PlatformConfig = z.infer<typeof platformConfigSchema>;
@@ -0,0 +1,12 @@
1
+ import winston from "winston";
2
+ import { config } from "./index.js";
3
+
4
+ export const logger = winston.createLogger({
5
+ level: config.logLevel,
6
+ format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
7
+ transports: [
8
+ new winston.transports.Console({
9
+ format: winston.format.combine(winston.format.colorize(), winston.format.simple()),
10
+ }),
11
+ ],
12
+ });
@@ -0,0 +1,14 @@
1
+ import type { Provider } from "../security/types.js";
2
+
3
+ /**
4
+ * Base API URLs used to validate provider keys.
5
+ * Centralised here so every consumer references one source of truth.
6
+ */
7
+ export const PROVIDER_API_URLS: Record<Provider, string> = {
8
+ anthropic: "https://api.anthropic.com/v1/models",
9
+ openai: "https://api.openai.com/v1/models",
10
+ google: "https://generativelanguage.googleapis.com/v1/models",
11
+ discord: "https://discord.com/api/v10/users/@me",
12
+ elevenlabs: "https://api.elevenlabs.io/v1/user",
13
+ deepgram: "https://api.deepgram.com/v1/projects",
14
+ };
@@ -0,0 +1,292 @@
1
+ import crypto from "node:crypto";
2
+ import type { PGlite } from "@electric-sql/pglite";
3
+ import Stripe from "stripe";
4
+ import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
5
+ import type { PlatformDb } from "../db/index.js";
6
+ import { creditAutoTopup } from "../db/schema/credit-auto-topup.js";
7
+ import { createTestDb, truncateAllTables } from "../test/db.js";
8
+ import { Credit } from "./credit.js";
9
+ import type { ITenantCustomerRepository } from "./tenant-customer-repository.js";
10
+ import { type AutoTopupChargeDeps, chargeAutoTopup, MAX_CONSECUTIVE_FAILURES } from "./auto-topup-charge.js";
11
+ import { DrizzleAutoTopupEventLogRepository } from "./auto-topup-event-log-repository.js";
12
+ import { CreditLedger } from "./credit-ledger.js";
13
+
14
+ function mockStripe(overrides?: {
15
+ paymentIntentId?: string;
16
+ shouldFail?: boolean;
17
+ failMessage?: string;
18
+ failWithStripeError?: boolean;
19
+ }) {
20
+ const piId = overrides?.paymentIntentId ?? `pi_${crypto.randomUUID()}`;
21
+ return {
22
+ paymentIntents: {
23
+ create: vi.fn().mockImplementation(async () => {
24
+ if (overrides?.shouldFail) {
25
+ if (overrides.failWithStripeError !== false) {
26
+ throw new Stripe.errors.StripeCardError({
27
+ message: overrides.failMessage ?? "card_declined",
28
+ type: "card_error",
29
+ code: overrides.failMessage ?? "card_declined",
30
+ });
31
+ }
32
+ throw new Error(overrides.failMessage ?? "card_declined");
33
+ }
34
+ return { id: piId, status: "succeeded" };
35
+ }),
36
+ },
37
+ customers: {
38
+ listPaymentMethods: vi.fn().mockResolvedValue({ data: [{ id: "pm_123" }] }),
39
+ },
40
+ };
41
+ }
42
+
43
+ function mockTenantStore(stripeCustomerId = "cus_123") {
44
+ return {
45
+ getByTenant: vi.fn().mockResolvedValue({ tenant: "t1", processor_customer_id: stripeCustomerId }),
46
+ };
47
+ }
48
+
49
+ describe("chargeAutoTopup", () => {
50
+ let pool: PGlite;
51
+ let db: PlatformDb;
52
+ let ledger: CreditLedger;
53
+
54
+ beforeAll(async () => {
55
+ ({ db, pool } = await createTestDb());
56
+ });
57
+
58
+ afterAll(async () => {
59
+ await pool.close();
60
+ });
61
+
62
+ beforeEach(async () => {
63
+ await truncateAllTables(pool);
64
+ ledger = new CreditLedger(db);
65
+ });
66
+
67
+ it("charges Stripe and credits ledger on success", async () => {
68
+ const stripe = mockStripe();
69
+ const tenantRepo = mockTenantStore();
70
+ const deps: AutoTopupChargeDeps = {
71
+ stripe: stripe as unknown as Stripe,
72
+ tenantRepo: tenantRepo as unknown as ITenantCustomerRepository,
73
+ creditLedger: ledger,
74
+ eventLogRepo: new DrizzleAutoTopupEventLogRepository(db),
75
+ };
76
+
77
+ const result = await chargeAutoTopup(deps, "t1", Credit.fromCents(500), "auto_topup_usage");
78
+
79
+ expect(result.success).toBe(true);
80
+ expect(result.paymentReference).toEqual(expect.any(String));
81
+ expect((await ledger.balance("t1")).toCents()).toBe(500);
82
+ const history = await ledger.history("t1");
83
+ expect(history[0].type).toBe("purchase");
84
+ expect(history[0].fundingSource).toBe("stripe");
85
+ });
86
+
87
+ it("writes success event to credit_auto_topup log", async () => {
88
+ const stripe = mockStripe();
89
+ const tenantRepo = mockTenantStore();
90
+ const deps: AutoTopupChargeDeps = {
91
+ stripe: stripe as unknown as Stripe,
92
+ tenantRepo: tenantRepo as unknown as ITenantCustomerRepository,
93
+ creditLedger: ledger,
94
+ eventLogRepo: new DrizzleAutoTopupEventLogRepository(db),
95
+ };
96
+
97
+ await chargeAutoTopup(deps, "t1", Credit.fromCents(500), "auto_topup_usage");
98
+
99
+ const events = await db
100
+ .select()
101
+ .from(creditAutoTopup)
102
+ .where((await import("drizzle-orm")).eq(creditAutoTopup.tenantId, "t1"));
103
+ expect(events).toHaveLength(1);
104
+ expect(events[0].status).toBe("success");
105
+ expect(events[0].amountCents).toBe(500);
106
+ });
107
+
108
+ it("returns failure result and writes failure event on Stripe error", async () => {
109
+ const stripe = mockStripe({ shouldFail: true, failMessage: "card_declined" });
110
+ const tenantRepo = mockTenantStore();
111
+ const deps: AutoTopupChargeDeps = {
112
+ stripe: stripe as unknown as Stripe,
113
+ tenantRepo: tenantRepo as unknown as ITenantCustomerRepository,
114
+ creditLedger: ledger,
115
+ eventLogRepo: new DrizzleAutoTopupEventLogRepository(db),
116
+ };
117
+
118
+ const result = await chargeAutoTopup(deps, "t1", Credit.fromCents(500), "auto_topup_usage");
119
+
120
+ expect(result.success).toBe(false);
121
+ expect(result.error).toContain("Card declined");
122
+ expect(result.error).toContain("card_declined");
123
+ expect((await ledger.balance("t1")).toCents()).toBe(0);
124
+ const events = await db
125
+ .select()
126
+ .from(creditAutoTopup)
127
+ .where((await import("drizzle-orm")).eq(creditAutoTopup.tenantId, "t1"));
128
+ expect(events).toHaveLength(1);
129
+ expect(events[0].status).toBe("failed");
130
+ });
131
+
132
+ it("returns failure when tenant has no Stripe customer", async () => {
133
+ const stripe = mockStripe();
134
+ const tenantRepo = { getByTenant: vi.fn().mockResolvedValue(null) };
135
+ const deps: AutoTopupChargeDeps = {
136
+ stripe: stripe as unknown as Stripe,
137
+ tenantRepo: tenantRepo as unknown as ITenantCustomerRepository,
138
+ creditLedger: ledger,
139
+ eventLogRepo: new DrizzleAutoTopupEventLogRepository(db),
140
+ };
141
+
142
+ const result = await chargeAutoTopup(deps, "t1", Credit.fromCents(500), "auto_topup_usage");
143
+
144
+ expect(result.success).toBe(false);
145
+ expect(result.error).toContain("No Stripe customer");
146
+ });
147
+
148
+ it("returns failure when tenant has no payment methods", async () => {
149
+ const stripe = mockStripe();
150
+ stripe.customers.listPaymentMethods = vi.fn().mockResolvedValue({ data: [] });
151
+ const tenantRepo = mockTenantStore();
152
+ const deps: AutoTopupChargeDeps = {
153
+ stripe: stripe as unknown as Stripe,
154
+ tenantRepo: tenantRepo as unknown as ITenantCustomerRepository,
155
+ creditLedger: ledger,
156
+ eventLogRepo: new DrizzleAutoTopupEventLogRepository(db),
157
+ };
158
+
159
+ const result = await chargeAutoTopup(deps, "t1", Credit.fromCents(500), "auto_topup_usage");
160
+
161
+ expect(result.success).toBe(false);
162
+ expect(result.error).toContain("No payment method");
163
+ });
164
+
165
+ it("is idempotent -- referenceId already credited means hasReferenceId returns true", async () => {
166
+ const piId = `pi_${crypto.randomUUID()}`;
167
+ const stripe = mockStripe({ paymentIntentId: piId });
168
+ const tenantRepo = mockTenantStore();
169
+ const deps: AutoTopupChargeDeps = {
170
+ stripe: stripe as unknown as Stripe,
171
+ tenantRepo: tenantRepo as unknown as ITenantCustomerRepository,
172
+ creditLedger: ledger,
173
+ eventLogRepo: new DrizzleAutoTopupEventLogRepository(db),
174
+ };
175
+
176
+ await chargeAutoTopup(deps, "t1", Credit.fromCents(500), "auto_topup_usage");
177
+ expect((await ledger.balance("t1")).toCents()).toBe(500);
178
+ expect(await ledger.hasReferenceId(piId)).toBe(true);
179
+ });
180
+
181
+ it("exports MAX_CONSECUTIVE_FAILURES as 3", () => {
182
+ expect(MAX_CONSECUTIVE_FAILURES).toBe(3);
183
+ });
184
+
185
+ it("logs structured card-decline info when Stripe throws StripeCardError on payment", async () => {
186
+ const cardError = new Stripe.errors.StripeCardError({
187
+ message: "Your card was declined.",
188
+ type: "card_error",
189
+ code: "card_declined",
190
+ decline_code: "insufficient_funds",
191
+ });
192
+ const stripe = mockStripe();
193
+ stripe.paymentIntents.create = vi.fn().mockRejectedValue(cardError);
194
+ const tenantRepo = mockTenantStore();
195
+ const deps: AutoTopupChargeDeps = {
196
+ stripe: stripe as unknown as Stripe,
197
+ tenantRepo: tenantRepo as unknown as ITenantCustomerRepository,
198
+ creditLedger: ledger,
199
+ eventLogRepo: new DrizzleAutoTopupEventLogRepository(db),
200
+ };
201
+
202
+ const result = await chargeAutoTopup(deps, "t1", Credit.fromCents(500), "auto_topup_usage");
203
+
204
+ expect(result.success).toBe(false);
205
+ expect(result.error).toContain("Card declined");
206
+ expect(result.error).toContain("card_declined");
207
+ });
208
+
209
+ it("logs structured Stripe error info when Stripe throws StripeAPIError on payment", async () => {
210
+ const apiError = new Stripe.errors.StripeAPIError({
211
+ message: "Internal Stripe error",
212
+ type: "api_error",
213
+ });
214
+ const stripe = mockStripe();
215
+ stripe.paymentIntents.create = vi.fn().mockRejectedValue(apiError);
216
+ const tenantRepo = mockTenantStore();
217
+ const deps: AutoTopupChargeDeps = {
218
+ stripe: stripe as unknown as Stripe,
219
+ tenantRepo: tenantRepo as unknown as ITenantCustomerRepository,
220
+ creditLedger: ledger,
221
+ eventLogRepo: new DrizzleAutoTopupEventLogRepository(db),
222
+ };
223
+
224
+ const result = await chargeAutoTopup(deps, "t1", Credit.fromCents(500), "auto_topup_usage");
225
+
226
+ expect(result.success).toBe(false);
227
+ expect(result.error).toContain("Stripe error");
228
+ });
229
+
230
+ it("re-throws non-Stripe errors from paymentIntents.create", async () => {
231
+ const stripe = mockStripe();
232
+ stripe.paymentIntents.create = vi.fn().mockRejectedValue(new TypeError("Cannot read properties of undefined"));
233
+ const tenantRepo = mockTenantStore();
234
+ const deps: AutoTopupChargeDeps = {
235
+ stripe: stripe as unknown as Stripe,
236
+ tenantRepo: tenantRepo as unknown as ITenantCustomerRepository,
237
+ creditLedger: ledger,
238
+ eventLogRepo: new DrizzleAutoTopupEventLogRepository(db),
239
+ };
240
+
241
+ await expect(chargeAutoTopup(deps, "t1", Credit.fromCents(500), "auto_topup_usage")).rejects.toThrow(TypeError);
242
+ });
243
+
244
+ it("returns structured error when StripeCardError thrown by listPaymentMethods", async () => {
245
+ const cardError = new Stripe.errors.StripeCardError({
246
+ message: "Card error on list",
247
+ type: "card_error",
248
+ code: "card_declined",
249
+ });
250
+ const stripe = mockStripe();
251
+ stripe.customers.listPaymentMethods = vi.fn().mockRejectedValue(cardError);
252
+ const tenantRepo = mockTenantStore();
253
+ const deps: AutoTopupChargeDeps = {
254
+ stripe: stripe as unknown as Stripe,
255
+ tenantRepo: tenantRepo as unknown as ITenantCustomerRepository,
256
+ creditLedger: ledger,
257
+ eventLogRepo: new DrizzleAutoTopupEventLogRepository(db),
258
+ };
259
+
260
+ const result = await chargeAutoTopup(deps, "t1", Credit.fromCents(500), "auto_topup_usage");
261
+
262
+ expect(result.success).toBe(false);
263
+ expect(result.error).toContain("Card declined");
264
+ });
265
+
266
+ it("propagates error with PaymentIntent ID when credit grant fails after Stripe charge", async () => {
267
+ const piId = "pi_ledger_fail_test";
268
+ const stripe = mockStripe({ paymentIntentId: piId });
269
+ const tenantRepo = mockTenantStore();
270
+ const deps: AutoTopupChargeDeps = {
271
+ stripe: stripe as unknown as Stripe,
272
+ tenantRepo: tenantRepo as unknown as ITenantCustomerRepository,
273
+ creditLedger: ledger,
274
+ eventLogRepo: new DrizzleAutoTopupEventLogRepository(db),
275
+ };
276
+
277
+ // Make credit() fail after Stripe succeeds
278
+ vi.spyOn(ledger, "credit").mockRejectedValueOnce(new Error("DB connection lost"));
279
+
280
+ // Error propagates and contains the PI ID for manual reconciliation
281
+ await expect(chargeAutoTopup(deps, "t1", Credit.fromCents(500), "auto_topup_usage")).rejects.toThrow(piId);
282
+
283
+ // Also verify the original error message is preserved
284
+ vi.spyOn(ledger, "credit").mockRejectedValueOnce(new Error("DB connection lost"));
285
+ await expect(chargeAutoTopup(deps, "t1", Credit.fromCents(500), "auto_topup_usage")).rejects.toThrow(
286
+ "DB connection lost",
287
+ );
288
+
289
+ // No credits were silently granted
290
+ expect((await ledger.balance("t1")).toCents()).toBe(0);
291
+ });
292
+ });
@@ -0,0 +1,171 @@
1
+ import Stripe from "stripe";
2
+ import { logger } from "../config/logger.js";
3
+ import type { Credit } from "./credit.js";
4
+ import type { ITenantCustomerRepository } from "./tenant-customer-repository.js";
5
+ import type { IAutoTopupEventLogRepository } from "./auto-topup-event-log-repository.js";
6
+ import type { ICreditLedger } from "./credit-ledger.js";
7
+
8
+ /** After this many consecutive Stripe failures, the auto-topup mode is disabled. */
9
+ export const MAX_CONSECUTIVE_FAILURES = 3;
10
+
11
+ export interface AutoTopupChargeDeps {
12
+ stripe: Stripe;
13
+ tenantRepo: ITenantCustomerRepository;
14
+ creditLedger: ICreditLedger;
15
+ eventLogRepo: IAutoTopupEventLogRepository;
16
+ }
17
+
18
+ export interface AutoTopupChargeResult {
19
+ success: boolean;
20
+ paymentReference?: string;
21
+ error?: string;
22
+ }
23
+
24
+ /**
25
+ * Charge a tenant's default Stripe payment method off-session and credit their ledger.
26
+ *
27
+ * Writes to both `credit_transactions` (type=purchase) and `credit_auto_topup` event log.
28
+ *
29
+ * @param source - Descriptive source tag for the credit_transactions description
30
+ * (e.g., "auto_topup_usage" or "auto_topup_schedule")
31
+ */
32
+ export async function chargeAutoTopup(
33
+ deps: AutoTopupChargeDeps,
34
+ tenantId: string,
35
+ amount: Credit,
36
+ source: string,
37
+ ): Promise<AutoTopupChargeResult> {
38
+ const amountCents = amount.toCentsFloor();
39
+
40
+ // 1. Look up Stripe customer
41
+ const mapping = await deps.tenantRepo.getByTenant(tenantId);
42
+ if (!mapping) {
43
+ const error = `No Stripe customer for tenant ${tenantId}`;
44
+ await deps.eventLogRepo.writeEvent({ tenantId, amountCents, status: "failed", failureReason: error });
45
+ return { success: false, error };
46
+ }
47
+
48
+ const customerId = mapping.processor_customer_id;
49
+
50
+ // 2. Get default payment method
51
+ let paymentMethodId: string;
52
+ try {
53
+ const methods = await deps.stripe.customers.listPaymentMethods(customerId, { limit: 1 });
54
+ if (!methods.data.length) {
55
+ const error = `No payment method on file for tenant ${tenantId}`;
56
+ await deps.eventLogRepo.writeEvent({ tenantId, amountCents, status: "failed", failureReason: error });
57
+ return { success: false, error };
58
+ }
59
+ paymentMethodId = methods.data[0].id;
60
+ } catch (err) {
61
+ let error: string;
62
+ if (err instanceof Stripe.errors.StripeCardError) {
63
+ error = `Card declined listing payment methods: ${err.code ?? "unknown"} (decline_code=${err.decline_code ?? "none"})`;
64
+ logger.warn("Card error listing payment methods", { tenantId, code: err.code, declineCode: err.decline_code });
65
+ } else if (err instanceof Stripe.errors.StripeError) {
66
+ error = `Stripe error listing payment methods: ${err.type} (code=${err.code ?? "none"}, status=${err.statusCode})`;
67
+ logger.error("Stripe error listing payment methods", {
68
+ tenantId,
69
+ type: err.type,
70
+ code: err.code,
71
+ statusCode: err.statusCode,
72
+ });
73
+ } else {
74
+ error = `Failed to list payment methods: ${err instanceof Error ? err.message : String(err)}`;
75
+ logger.error("Unexpected error listing payment methods", { tenantId, error });
76
+ }
77
+ await deps.eventLogRepo.writeEvent({ tenantId, amountCents, status: "failed", failureReason: error });
78
+ return { success: false, error };
79
+ }
80
+
81
+ // 3. Create off-session PaymentIntent (Stripe expects integer cents)
82
+ let paymentIntent: Stripe.PaymentIntent;
83
+ try {
84
+ paymentIntent = await deps.stripe.paymentIntents.create({
85
+ amount: amountCents,
86
+ currency: "usd",
87
+ customer: customerId,
88
+ payment_method: paymentMethodId,
89
+ off_session: true,
90
+ confirm: true,
91
+ metadata: {
92
+ wopr_tenant: tenantId,
93
+ wopr_source: source,
94
+ },
95
+ });
96
+ } catch (err) {
97
+ if (err instanceof Stripe.errors.StripeCardError) {
98
+ const error = `Card declined during auto top-up: ${err.code ?? "unknown"} (decline_code=${err.decline_code ?? "none"})`;
99
+ await deps.eventLogRepo.writeEvent({ tenantId, amountCents, status: "failed", failureReason: error });
100
+ logger.warn("Card declined during auto top-up", {
101
+ tenantId,
102
+ code: err.code,
103
+ declineCode: err.decline_code,
104
+ source,
105
+ });
106
+ return { success: false, error };
107
+ } else if (err instanceof Stripe.errors.StripeError) {
108
+ const error = `Stripe error during auto top-up: ${err.type} (code=${err.code ?? "none"}, status=${err.statusCode})`;
109
+ await deps.eventLogRepo.writeEvent({ tenantId, amountCents, status: "failed", failureReason: error });
110
+ logger.error("Stripe error during auto top-up", {
111
+ tenantId,
112
+ type: err.type,
113
+ code: err.code,
114
+ statusCode: err.statusCode,
115
+ source,
116
+ });
117
+ return { success: false, error };
118
+ } else {
119
+ throw err;
120
+ }
121
+ }
122
+
123
+ // 4. Verify payment succeeded (could be requires_action for 3DS)
124
+ if (paymentIntent.status !== "succeeded") {
125
+ const error = `PaymentIntent status: ${paymentIntent.status}`;
126
+ await deps.eventLogRepo.writeEvent({
127
+ tenantId,
128
+ amountCents,
129
+ status: "failed",
130
+ failureReason: error,
131
+ paymentReference: paymentIntent.id,
132
+ });
133
+ logger.warn("Auto-topup PaymentIntent not succeeded", { tenantId, status: paymentIntent.status });
134
+ return { success: false, error, paymentReference: paymentIntent.id };
135
+ }
136
+
137
+ // 5. Credit the ledger (idempotent via referenceId = PI ID)
138
+ try {
139
+ if (!(await deps.creditLedger.hasReferenceId(paymentIntent.id))) {
140
+ await deps.creditLedger.credit(
141
+ tenantId,
142
+ amount,
143
+ "purchase",
144
+ `Auto-topup (${source})`,
145
+ paymentIntent.id,
146
+ "stripe",
147
+ );
148
+ }
149
+ } catch (err) {
150
+ const message = `Stripe charge ${paymentIntent.id} succeeded but credit grant failed: ${err instanceof Error ? err.message : String(err)}`;
151
+ await deps.eventLogRepo
152
+ .writeEvent({
153
+ tenantId,
154
+ amountCents,
155
+ status: "failed",
156
+ failureReason: message,
157
+ paymentReference: paymentIntent.id,
158
+ })
159
+ .catch((logErr) => {
160
+ logger.error("Failed to write failure event after ledger error", { tenantId, piId: paymentIntent.id, logErr });
161
+ });
162
+ logger.error(message, { tenantId, piId: paymentIntent.id, source });
163
+ throw new Error(message);
164
+ }
165
+
166
+ // 6. Write success event
167
+ await deps.eventLogRepo.writeEvent({ tenantId, amountCents, status: "success", paymentReference: paymentIntent.id });
168
+ logger.info("Auto-topup charge succeeded", { tenantId, amount: amount.toString(), source, piId: paymentIntent.id });
169
+
170
+ return { success: true, paymentReference: paymentIntent.id };
171
+ }
@@ -0,0 +1,99 @@
1
+ import type { PGlite } from "@electric-sql/pglite";
2
+ import { eq } from "drizzle-orm";
3
+ import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
4
+ import type { PlatformDb } from "../db/index.js";
5
+ import { creditAutoTopup } from "../db/schema/credit-auto-topup.js";
6
+ import { beginTestTransaction, createTestDb, endTestTransaction, rollbackTestTransaction } from "../test/db.js";
7
+ import { DrizzleAutoTopupEventLogRepository } from "./auto-topup-event-log-repository.js";
8
+
9
+ describe("DrizzleAutoTopupEventLogRepository", () => {
10
+ let pool: PGlite;
11
+ let db: PlatformDb;
12
+ let repo: DrizzleAutoTopupEventLogRepository;
13
+
14
+ beforeAll(async () => {
15
+ ({ db, pool } = await createTestDb());
16
+ await beginTestTransaction(pool);
17
+ });
18
+
19
+ afterAll(async () => {
20
+ await endTestTransaction(pool);
21
+ await pool.close();
22
+ });
23
+
24
+ beforeEach(async () => {
25
+ await rollbackTestTransaction(pool);
26
+ repo = new DrizzleAutoTopupEventLogRepository(db);
27
+ });
28
+
29
+ it("writes a success event with all fields", async () => {
30
+ await repo.writeEvent({
31
+ tenantId: "t1",
32
+ amountCents: 500,
33
+ status: "success",
34
+ failureReason: null,
35
+ paymentReference: "pi_abc123",
36
+ });
37
+
38
+ const rows = await db.select().from(creditAutoTopup).where(eq(creditAutoTopup.tenantId, "t1"));
39
+ expect(rows).toHaveLength(1);
40
+ expect(rows[0].tenantId).toBe("t1");
41
+ expect(rows[0].amountCents).toBe(500);
42
+ expect(rows[0].status).toBe("success");
43
+ expect(rows[0].failureReason).toBeNull();
44
+ expect(rows[0].paymentReference).toBe("pi_abc123");
45
+ expect(rows[0].id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
46
+ expect(rows[0].createdAt).toMatch(/^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}/);
47
+ });
48
+
49
+ it("writes a failed event with failure reason", async () => {
50
+ await repo.writeEvent({
51
+ tenantId: "t1",
52
+ amountCents: 1000,
53
+ status: "failed",
54
+ failureReason: "card_declined",
55
+ paymentReference: null,
56
+ });
57
+
58
+ const rows = await db.select().from(creditAutoTopup).where(eq(creditAutoTopup.tenantId, "t1"));
59
+ expect(rows).toHaveLength(1);
60
+ expect(rows[0].status).toBe("failed");
61
+ expect(rows[0].failureReason).toBe("card_declined");
62
+ expect(rows[0].paymentReference).toBeNull();
63
+ });
64
+
65
+ it("defaults optional fields to null when omitted", async () => {
66
+ await repo.writeEvent({
67
+ tenantId: "t1",
68
+ amountCents: 200,
69
+ status: "success",
70
+ });
71
+
72
+ const rows = await db.select().from(creditAutoTopup).where(eq(creditAutoTopup.tenantId, "t1"));
73
+ expect(rows).toHaveLength(1);
74
+ expect(rows[0].failureReason).toBeNull();
75
+ expect(rows[0].paymentReference).toBeNull();
76
+ });
77
+
78
+ it("generates unique ids for each event", async () => {
79
+ await repo.writeEvent({ tenantId: "t1", amountCents: 100, status: "success" });
80
+ await repo.writeEvent({ tenantId: "t1", amountCents: 200, status: "success" });
81
+
82
+ const rows = await db.select().from(creditAutoTopup).where(eq(creditAutoTopup.tenantId, "t1"));
83
+ expect(rows).toHaveLength(2);
84
+ expect(rows[0].id).not.toBe(rows[1].id);
85
+ });
86
+
87
+ it("isolates events by tenant", async () => {
88
+ await repo.writeEvent({ tenantId: "t1", amountCents: 100, status: "success" });
89
+ await repo.writeEvent({ tenantId: "t2", amountCents: 200, status: "failed", failureReason: "insufficient_funds" });
90
+
91
+ const t1Rows = await db.select().from(creditAutoTopup).where(eq(creditAutoTopup.tenantId, "t1"));
92
+ const t2Rows = await db.select().from(creditAutoTopup).where(eq(creditAutoTopup.tenantId, "t2"));
93
+ expect(t1Rows).toHaveLength(1);
94
+ expect(t1Rows[0].amountCents).toBe(100);
95
+ expect(t2Rows).toHaveLength(1);
96
+ expect(t2Rows[0].amountCents).toBe(200);
97
+ expect(t2Rows[0].failureReason).toBe("insufficient_funds");
98
+ });
99
+ });
@@ -0,0 +1,30 @@
1
+ import crypto from "node:crypto";
2
+ import type { PlatformDb } from "../db/index.js";
3
+ import { creditAutoTopup } from "../db/schema/credit-auto-topup.js";
4
+
5
+ export interface AutoTopupEventLogEntry {
6
+ tenantId: string;
7
+ amountCents: number;
8
+ status: "success" | "failed";
9
+ failureReason?: string | null;
10
+ paymentReference?: string | null;
11
+ }
12
+
13
+ export interface IAutoTopupEventLogRepository {
14
+ writeEvent(entry: AutoTopupEventLogEntry): Promise<void>;
15
+ }
16
+
17
+ export class DrizzleAutoTopupEventLogRepository implements IAutoTopupEventLogRepository {
18
+ constructor(private readonly db: PlatformDb) {}
19
+
20
+ async writeEvent(entry: AutoTopupEventLogEntry): Promise<void> {
21
+ await this.db.insert(creditAutoTopup).values({
22
+ id: crypto.randomUUID(),
23
+ tenantId: entry.tenantId,
24
+ amountCents: entry.amountCents,
25
+ status: entry.status,
26
+ failureReason: entry.failureReason ?? null,
27
+ paymentReference: entry.paymentReference ?? null,
28
+ });
29
+ }
30
+ }