@adonis-agora/payments 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 (346) hide show
  1. package/CHANGELOG.md +2375 -0
  2. package/dist/assets/spa/assets/index-B7rU8eIL.css +2 -0
  3. package/dist/assets/spa/assets/index-BcM2VH5k.js +10 -0
  4. package/dist/assets/spa/assets/index-BcM2VH5k.js.map +1 -0
  5. package/dist/assets/spa/index.html +20 -0
  6. package/dist/commands/main.d.ts +4 -3
  7. package/dist/commands/main.d.ts.map +1 -1
  8. package/dist/commands/main.js +9 -2
  9. package/dist/commands/main.js.map +1 -1
  10. package/dist/commands/make_billable.d.ts.map +1 -1
  11. package/dist/commands/make_billable.js +1 -1
  12. package/dist/commands/make_billable.js.map +1 -1
  13. package/dist/commands/make_webhook_handler.d.ts.map +1 -1
  14. package/dist/commands/make_webhook_handler.js +1 -1
  15. package/dist/commands/make_webhook_handler.js.map +1 -1
  16. package/dist/commands/payments_health.d.ts +31 -0
  17. package/dist/commands/payments_health.d.ts.map +1 -0
  18. package/dist/commands/payments_health.js +113 -0
  19. package/dist/commands/payments_health.js.map +1 -0
  20. package/dist/commands/payments_sync.d.ts +18 -4
  21. package/dist/commands/payments_sync.d.ts.map +1 -1
  22. package/dist/commands/payments_sync.js +136 -40
  23. package/dist/commands/payments_sync.js.map +1 -1
  24. package/dist/commands/payments_webhook.d.ts.map +1 -1
  25. package/dist/commands/payments_webhook.js.map +1 -1
  26. package/dist/configure.d.ts +12 -5
  27. package/dist/configure.d.ts.map +1 -1
  28. package/dist/configure.js +18 -5
  29. package/dist/configure.js.map +1 -1
  30. package/dist/providers/dashboard_provider.d.ts +97 -0
  31. package/dist/providers/dashboard_provider.d.ts.map +1 -0
  32. package/dist/providers/dashboard_provider.js +430 -0
  33. package/dist/providers/dashboard_provider.js.map +1 -0
  34. package/dist/providers/payments_client_provider.d.ts +41 -0
  35. package/dist/providers/payments_client_provider.d.ts.map +1 -0
  36. package/dist/providers/payments_client_provider.js +95 -0
  37. package/dist/providers/payments_client_provider.js.map +1 -0
  38. package/dist/providers/payments_provider.d.ts +35 -0
  39. package/dist/providers/payments_provider.d.ts.map +1 -1
  40. package/dist/providers/payments_provider.js +250 -36
  41. package/dist/providers/payments_provider.js.map +1 -1
  42. package/dist/src/billing/billing_health.d.ts +85 -0
  43. package/dist/src/billing/billing_health.d.ts.map +1 -0
  44. package/dist/src/billing/billing_health.js +155 -0
  45. package/dist/src/billing/billing_health.js.map +1 -0
  46. package/dist/src/billing/billing_overview.d.ts +4 -3
  47. package/dist/src/billing/billing_overview.d.ts.map +1 -1
  48. package/dist/src/billing/billing_overview.js +14 -5
  49. package/dist/src/billing/billing_overview.js.map +1 -1
  50. package/dist/src/billing/billing_store.d.ts +634 -5
  51. package/dist/src/billing/billing_store.d.ts.map +1 -1
  52. package/dist/src/billing/billing_store.js +18 -1
  53. package/dist/src/billing/billing_store.js.map +1 -1
  54. package/dist/src/billing/index.d.ts +4 -3
  55. package/dist/src/billing/index.d.ts.map +1 -1
  56. package/dist/src/billing/index.js +2 -1
  57. package/dist/src/billing/index.js.map +1 -1
  58. package/dist/src/billing/list_query.d.ts +16 -0
  59. package/dist/src/billing/list_query.d.ts.map +1 -0
  60. package/dist/src/billing/list_query.js +28 -0
  61. package/dist/src/billing/list_query.js.map +1 -0
  62. package/dist/src/billing/lucid_billing_store.d.ts +122 -4
  63. package/dist/src/billing/lucid_billing_store.d.ts.map +1 -1
  64. package/dist/src/billing/lucid_billing_store.js +811 -13
  65. package/dist/src/billing/lucid_billing_store.js.map +1 -1
  66. package/dist/src/billing/mixins/index.d.ts +5 -2
  67. package/dist/src/billing/mixins/index.d.ts.map +1 -1
  68. package/dist/src/billing/mixins/index.js +5 -2
  69. package/dist/src/billing/mixins/index.js.map +1 -1
  70. package/dist/src/billing/mixins/with_audit_event.d.ts +48 -0
  71. package/dist/src/billing/mixins/with_audit_event.d.ts.map +1 -0
  72. package/dist/src/billing/mixins/with_audit_event.js +77 -0
  73. package/dist/src/billing/mixins/with_audit_event.js.map +1 -0
  74. package/dist/src/billing/mixins/with_billable.d.ts.map +1 -1
  75. package/dist/src/billing/mixins/with_customer.d.ts +39 -0
  76. package/dist/src/billing/mixins/with_customer.d.ts.map +1 -0
  77. package/dist/src/billing/mixins/with_customer.js +68 -0
  78. package/dist/src/billing/mixins/with_customer.js.map +1 -0
  79. package/dist/src/billing/mixins/with_dispute.d.ts +68 -0
  80. package/dist/src/billing/mixins/with_dispute.d.ts.map +1 -0
  81. package/dist/src/billing/mixins/with_dispute.js +83 -0
  82. package/dist/src/billing/mixins/with_dispute.js.map +1 -0
  83. package/dist/src/billing/mixins/with_payment.d.ts +42 -0
  84. package/dist/src/billing/mixins/with_payment.d.ts.map +1 -1
  85. package/dist/src/billing/mixins/with_payment.js +29 -3
  86. package/dist/src/billing/mixins/with_payment.js.map +1 -1
  87. package/dist/src/billing/mixins/with_subscription.d.ts +7 -0
  88. package/dist/src/billing/mixins/with_subscription.d.ts.map +1 -1
  89. package/dist/src/billing/mixins/with_subscription.js +15 -1
  90. package/dist/src/billing/mixins/with_subscription.js.map +1 -1
  91. package/dist/src/billing/mixins/with_usage_event.d.ts +7 -0
  92. package/dist/src/billing/mixins/with_usage_event.d.ts.map +1 -1
  93. package/dist/src/billing/mixins/with_usage_event.js +15 -1
  94. package/dist/src/billing/mixins/with_usage_event.js.map +1 -1
  95. package/dist/src/billing/mixins/with_webhook_event.d.ts +18 -0
  96. package/dist/src/billing/mixins/with_webhook_event.d.ts.map +1 -1
  97. package/dist/src/billing/mixins/with_webhook_event.js +18 -1
  98. package/dist/src/billing/mixins/with_webhook_event.js.map +1 -1
  99. package/dist/src/billing/resolve_dispatch.d.ts +22 -0
  100. package/dist/src/billing/resolve_dispatch.d.ts.map +1 -0
  101. package/dist/src/billing/resolve_dispatch.js +44 -0
  102. package/dist/src/billing/resolve_dispatch.js.map +1 -0
  103. package/dist/src/billing/resolve_store.d.ts +18 -0
  104. package/dist/src/billing/resolve_store.d.ts.map +1 -0
  105. package/dist/src/billing/resolve_store.js +35 -0
  106. package/dist/src/billing/resolve_store.js.map +1 -0
  107. package/dist/src/billing/schema.d.ts +108 -0
  108. package/dist/src/billing/schema.d.ts.map +1 -0
  109. package/dist/src/billing/schema.js +350 -0
  110. package/dist/src/billing/schema.js.map +1 -0
  111. package/dist/src/billing/webhook_dispatcher.d.ts +95 -16
  112. package/dist/src/billing/webhook_dispatcher.d.ts.map +1 -1
  113. package/dist/src/billing/webhook_dispatcher.js +186 -33
  114. package/dist/src/billing/webhook_dispatcher.js.map +1 -1
  115. package/dist/src/billing/webhook_events.d.ts +67 -1
  116. package/dist/src/billing/webhook_events.d.ts.map +1 -1
  117. package/dist/src/billing/webhook_events.js +15 -0
  118. package/dist/src/billing/webhook_events.js.map +1 -1
  119. package/dist/src/billing/webhook_processor.d.ts.map +1 -1
  120. package/dist/src/billing/webhook_processor.js +325 -5
  121. package/dist/src/billing/webhook_processor.js.map +1 -1
  122. package/dist/src/client/define_config.d.ts +150 -0
  123. package/dist/src/client/define_config.d.ts.map +1 -0
  124. package/dist/src/client/define_config.js +98 -0
  125. package/dist/src/client/define_config.js.map +1 -0
  126. package/dist/src/client/handlers.d.ts +71 -0
  127. package/dist/src/client/handlers.d.ts.map +1 -0
  128. package/dist/src/client/handlers.js +119 -0
  129. package/dist/src/client/handlers.js.map +1 -0
  130. package/dist/src/client/index.d.ts +15 -0
  131. package/dist/src/client/index.d.ts.map +1 -0
  132. package/dist/src/client/index.js +12 -0
  133. package/dist/src/client/index.js.map +1 -0
  134. package/dist/src/client/statuses.d.ts +38 -0
  135. package/dist/src/client/statuses.d.ts.map +1 -0
  136. package/dist/src/client/statuses.js +42 -0
  137. package/dist/src/client/statuses.js.map +1 -0
  138. package/dist/src/dashboard/actions.d.ts +143 -0
  139. package/dist/src/dashboard/actions.d.ts.map +1 -0
  140. package/dist/src/dashboard/actions.js +112 -0
  141. package/dist/src/dashboard/actions.js.map +1 -0
  142. package/dist/src/dashboard/auth.d.ts +136 -0
  143. package/dist/src/dashboard/auth.d.ts.map +1 -0
  144. package/dist/src/dashboard/auth.js +152 -0
  145. package/dist/src/dashboard/auth.js.map +1 -0
  146. package/dist/src/dashboard/define_config.d.ts +80 -0
  147. package/dist/src/dashboard/define_config.d.ts.map +1 -0
  148. package/dist/src/dashboard/define_config.js +83 -0
  149. package/dist/src/dashboard/define_config.js.map +1 -0
  150. package/dist/src/dashboard/handlers.d.ts +281 -0
  151. package/dist/src/dashboard/handlers.d.ts.map +1 -0
  152. package/dist/src/dashboard/handlers.js +960 -0
  153. package/dist/src/dashboard/handlers.js.map +1 -0
  154. package/dist/src/dashboard/index.d.ts +17 -0
  155. package/dist/src/dashboard/index.d.ts.map +1 -0
  156. package/dist/src/dashboard/index.js +13 -0
  157. package/dist/src/dashboard/index.js.map +1 -0
  158. package/dist/src/dashboard/login_page.d.ts +23 -0
  159. package/dist/src/dashboard/login_page.d.ts.map +1 -0
  160. package/dist/src/dashboard/login_page.js +170 -0
  161. package/dist/src/dashboard/login_page.js.map +1 -0
  162. package/dist/src/dashboard/period.d.ts +31 -0
  163. package/dist/src/dashboard/period.d.ts.map +1 -0
  164. package/dist/src/dashboard/period.js +44 -0
  165. package/dist/src/dashboard/period.js.map +1 -0
  166. package/dist/src/dashboard/session_cookie.d.ts +42 -0
  167. package/dist/src/dashboard/session_cookie.d.ts.map +1 -0
  168. package/dist/src/dashboard/session_cookie.js +97 -0
  169. package/dist/src/dashboard/session_cookie.js.map +1 -0
  170. package/dist/src/dashboard/spa.d.ts +24 -0
  171. package/dist/src/dashboard/spa.d.ts.map +1 -0
  172. package/dist/src/dashboard/spa.js +52 -0
  173. package/dist/src/dashboard/spa.js.map +1 -0
  174. package/dist/src/define_config.d.ts +245 -10
  175. package/dist/src/define_config.d.ts.map +1 -1
  176. package/dist/src/define_config.js +97 -1
  177. package/dist/src/define_config.js.map +1 -1
  178. package/dist/src/diagnostics.d.ts +287 -7
  179. package/dist/src/diagnostics.d.ts.map +1 -1
  180. package/dist/src/diagnostics.js +317 -1
  181. package/dist/src/diagnostics.js.map +1 -1
  182. package/dist/src/driver.d.ts +139 -5
  183. package/dist/src/driver.d.ts.map +1 -1
  184. package/dist/src/drivers/abacate.d.ts +13 -3
  185. package/dist/src/drivers/abacate.d.ts.map +1 -1
  186. package/dist/src/drivers/abacate.js +112 -19
  187. package/dist/src/drivers/abacate.js.map +1 -1
  188. package/dist/src/drivers/adyen.d.ts +164 -0
  189. package/dist/src/drivers/adyen.d.ts.map +1 -0
  190. package/dist/src/drivers/adyen.js +678 -0
  191. package/dist/src/drivers/adyen.js.map +1 -0
  192. package/dist/src/drivers/asaas.d.ts +53 -3
  193. package/dist/src/drivers/asaas.d.ts.map +1 -1
  194. package/dist/src/drivers/asaas.js +364 -29
  195. package/dist/src/drivers/asaas.js.map +1 -1
  196. package/dist/src/drivers/dodo.d.ts +135 -0
  197. package/dist/src/drivers/dodo.d.ts.map +1 -0
  198. package/dist/src/drivers/dodo.js +864 -0
  199. package/dist/src/drivers/dodo.js.map +1 -0
  200. package/dist/src/drivers/efi.d.ts +98 -0
  201. package/dist/src/drivers/efi.d.ts.map +1 -0
  202. package/dist/src/drivers/efi.js +628 -0
  203. package/dist/src/drivers/efi.js.map +1 -0
  204. package/dist/src/drivers/infinitepay.d.ts +156 -0
  205. package/dist/src/drivers/infinitepay.d.ts.map +1 -0
  206. package/dist/src/drivers/infinitepay.js +321 -0
  207. package/dist/src/drivers/infinitepay.js.map +1 -0
  208. package/dist/src/drivers/lemonsqueezy.d.ts +125 -0
  209. package/dist/src/drivers/lemonsqueezy.d.ts.map +1 -0
  210. package/dist/src/drivers/lemonsqueezy.js +632 -0
  211. package/dist/src/drivers/lemonsqueezy.js.map +1 -0
  212. package/dist/src/drivers/mercadopago.d.ts +97 -0
  213. package/dist/src/drivers/mercadopago.d.ts.map +1 -0
  214. package/dist/src/drivers/mercadopago.js +844 -0
  215. package/dist/src/drivers/mercadopago.js.map +1 -0
  216. package/dist/src/drivers/mollie.d.ts +125 -0
  217. package/dist/src/drivers/mollie.d.ts.map +1 -0
  218. package/dist/src/drivers/mollie.js +812 -0
  219. package/dist/src/drivers/mollie.js.map +1 -0
  220. package/dist/src/drivers/paddle.d.ts +148 -0
  221. package/dist/src/drivers/paddle.d.ts.map +1 -0
  222. package/dist/src/drivers/paddle.js +738 -0
  223. package/dist/src/drivers/paddle.js.map +1 -0
  224. package/dist/src/drivers/pagarme.d.ts +97 -0
  225. package/dist/src/drivers/pagarme.d.ts.map +1 -0
  226. package/dist/src/drivers/pagarme.js +829 -0
  227. package/dist/src/drivers/pagarme.js.map +1 -0
  228. package/dist/src/drivers/pagbank.d.ts +57 -0
  229. package/dist/src/drivers/pagbank.d.ts.map +1 -0
  230. package/dist/src/drivers/pagbank.js +499 -0
  231. package/dist/src/drivers/pagbank.js.map +1 -0
  232. package/dist/src/drivers/paypal.d.ts +120 -0
  233. package/dist/src/drivers/paypal.d.ts.map +1 -0
  234. package/dist/src/drivers/paypal.js +891 -0
  235. package/dist/src/drivers/paypal.js.map +1 -0
  236. package/dist/src/drivers/polar.d.ts +118 -0
  237. package/dist/src/drivers/polar.d.ts.map +1 -0
  238. package/dist/src/drivers/polar.js +604 -0
  239. package/dist/src/drivers/polar.js.map +1 -0
  240. package/dist/src/drivers/razorpay.d.ts +103 -0
  241. package/dist/src/drivers/razorpay.d.ts.map +1 -0
  242. package/dist/src/drivers/razorpay.js +867 -0
  243. package/dist/src/drivers/razorpay.js.map +1 -0
  244. package/dist/src/drivers/shared.d.ts +33 -0
  245. package/dist/src/drivers/shared.d.ts.map +1 -0
  246. package/dist/src/drivers/shared.js +37 -0
  247. package/dist/src/drivers/shared.js.map +1 -0
  248. package/dist/src/drivers/square.d.ts +133 -0
  249. package/dist/src/drivers/square.d.ts.map +1 -0
  250. package/dist/src/drivers/square.js +905 -0
  251. package/dist/src/drivers/square.js.map +1 -0
  252. package/dist/src/drivers/stripe.d.ts +41 -4
  253. package/dist/src/drivers/stripe.d.ts.map +1 -1
  254. package/dist/src/drivers/stripe.js +823 -26
  255. package/dist/src/drivers/stripe.js.map +1 -1
  256. package/dist/src/drivers/woovi.d.ts +11 -4
  257. package/dist/src/drivers/woovi.d.ts.map +1 -1
  258. package/dist/src/drivers/woovi.js +162 -29
  259. package/dist/src/drivers/woovi.js.map +1 -1
  260. package/dist/src/ensure_customer.d.ts +34 -1
  261. package/dist/src/ensure_customer.d.ts.map +1 -1
  262. package/dist/src/ensure_customer.js +29 -4
  263. package/dist/src/ensure_customer.js.map +1 -1
  264. package/dist/src/hooks/webhook_handlers.d.ts.map +1 -1
  265. package/dist/src/hooks/webhook_handlers.js.map +1 -1
  266. package/dist/src/http.d.ts +37 -3
  267. package/dist/src/http.d.ts.map +1 -1
  268. package/dist/src/http.js +69 -5
  269. package/dist/src/http.js.map +1 -1
  270. package/dist/src/index.d.ts +54 -26
  271. package/dist/src/index.d.ts.map +1 -1
  272. package/dist/src/index.js +29 -12
  273. package/dist/src/index.js.map +1 -1
  274. package/dist/src/invoice/drivers/asaas.d.ts.map +1 -1
  275. package/dist/src/invoice/drivers/asaas.js.map +1 -1
  276. package/dist/src/invoice/drivers/enotas.d.ts.map +1 -1
  277. package/dist/src/invoice/drivers/enotas.js.map +1 -1
  278. package/dist/src/invoice/drivers/focus.d.ts.map +1 -1
  279. package/dist/src/invoice/drivers/focus.js.map +1 -1
  280. package/dist/src/invoice/drivers/plugnotas.d.ts.map +1 -1
  281. package/dist/src/invoice/drivers/plugnotas.js.map +1 -1
  282. package/dist/src/invoice/drivers/tecnospeed.d.ts.map +1 -1
  283. package/dist/src/invoice/drivers/tecnospeed.js.map +1 -1
  284. package/dist/src/invoice/emit_invoice.d.ts.map +1 -1
  285. package/dist/src/invoice/emit_invoice.js +31 -11
  286. package/dist/src/invoice/emit_invoice.js.map +1 -1
  287. package/dist/src/invoice/invoice_manager.d.ts.map +1 -1
  288. package/dist/src/money.d.ts +23 -5
  289. package/dist/src/money.d.ts.map +1 -1
  290. package/dist/src/money.js +72 -7
  291. package/dist/src/money.js.map +1 -1
  292. package/dist/src/payments_manager.d.ts +8 -1
  293. package/dist/src/payments_manager.d.ts.map +1 -1
  294. package/dist/src/payments_manager.js +168 -6
  295. package/dist/src/payments_manager.js.map +1 -1
  296. package/dist/src/services/main.d.ts +38 -0
  297. package/dist/src/services/main.d.ts.map +1 -1
  298. package/dist/src/services/main.js +130 -0
  299. package/dist/src/services/main.js.map +1 -1
  300. package/dist/src/telescope/index.d.ts +2 -0
  301. package/dist/src/telescope/index.d.ts.map +1 -1
  302. package/dist/src/telescope/index.js +1 -0
  303. package/dist/src/telescope/index.js.map +1 -1
  304. package/dist/src/telescope/payments_watcher.d.ts +10 -4
  305. package/dist/src/telescope/payments_watcher.d.ts.map +1 -1
  306. package/dist/src/telescope/payments_watcher.js +66 -14
  307. package/dist/src/telescope/payments_watcher.js.map +1 -1
  308. package/dist/src/telescope/register.d.ts +62 -0
  309. package/dist/src/telescope/register.d.ts.map +1 -0
  310. package/dist/src/telescope/register.js +48 -0
  311. package/dist/src/telescope/register.js.map +1 -0
  312. package/dist/src/testing/fake_payments.d.ts +62 -0
  313. package/dist/src/testing/fake_payments.d.ts.map +1 -0
  314. package/dist/src/testing/fake_payments.js +82 -0
  315. package/dist/src/testing/fake_payments.js.map +1 -0
  316. package/dist/src/testing/fake_payments_driver.d.ts +1 -1
  317. package/dist/src/testing/fake_payments_driver.d.ts.map +1 -1
  318. package/dist/src/testing/fake_payments_driver.js.map +1 -1
  319. package/dist/src/testing/in_memory_billing_store.d.ts +151 -2
  320. package/dist/src/testing/in_memory_billing_store.d.ts.map +1 -1
  321. package/dist/src/testing/in_memory_billing_store.js +517 -4
  322. package/dist/src/testing/in_memory_billing_store.js.map +1 -1
  323. package/dist/src/testing/index.d.ts +3 -2
  324. package/dist/src/testing/index.d.ts.map +1 -1
  325. package/dist/src/testing/index.js +1 -0
  326. package/dist/src/testing/index.js.map +1 -1
  327. package/dist/src/testing/mutable_clock.d.ts.map +1 -1
  328. package/dist/src/types.d.ts +161 -5
  329. package/dist/src/types.d.ts.map +1 -1
  330. package/dist/src/types.js +30 -1
  331. package/dist/src/types.js.map +1 -1
  332. package/dist/src/webhook_handlers.d.ts +96 -2
  333. package/dist/src/webhook_handlers.d.ts.map +1 -1
  334. package/dist/src/webhook_handlers.js +91 -10
  335. package/dist/src/webhook_handlers.js.map +1 -1
  336. package/dist/src/webhook_security.d.ts +94 -3
  337. package/dist/src/webhook_security.d.ts.map +1 -1
  338. package/dist/src/webhook_security.js +173 -9
  339. package/dist/src/webhook_security.js.map +1 -1
  340. package/dist/stubs/config/payments.stub +13 -0
  341. package/dist/stubs/config/payments_client.stub +42 -0
  342. package/dist/stubs/config/payments_dashboard.stub +38 -0
  343. package/dist/stubs/database/migrations/create_billing_tables.stub +35 -77
  344. package/dist/stubs/main.js.map +1 -1
  345. package/dist/stubs/make/webhook_handler/main.stub +29 -11
  346. package/package.json +67 -32
@@ -1,4 +1,301 @@
1
- import type { BillingPayment, BillingSubscription, BillingUsageEvent, BillingWebhookEvent } from './mixins/index.js';
1
+ import type { BillingCustomer, BillingDispute, BillingPayment, BillingSubscription, BillingUsageEvent, BillingWebhookEvent } from './mixins/index.js';
2
+ /**
3
+ * One `billing_payments` row, normalized for reading.
4
+ *
5
+ * The write side of this SPI is generic over the row type (a Lucid model instance in one
6
+ * implementation, a plain object in the other). A reader — the dashboard, a report — cannot
7
+ * work against `PaymentRow` for that reason, so the LIST side returns this flat, plain
8
+ * shape instead: every implementation normalizes into it, and no caller ever has to know
9
+ * whether a Lucid model or a plain object produced it.
10
+ *
11
+ * `amount` is integer cents, like everywhere else in this package. Format at the edge.
12
+ */
13
+ export interface PaymentListItem {
14
+ id: string;
15
+ gatewayId: string;
16
+ provider: string;
17
+ status: string;
18
+ /** Integer cents — NEVER divide here. */
19
+ amount: number;
20
+ currency: string;
21
+ customerId: string | null;
22
+ subscriptionId: string | null;
23
+ /**
24
+ * The app's own id for this charge, as the gateway echoed it back.
25
+ *
26
+ * `null` on a payment whose gateway sent none, and on every row an install wrote before
27
+ * it ran an earlier schema.
28
+ */
29
+ externalReference: string | null;
30
+ /**
31
+ * How much of {@link PaymentListItem.amount} has come back, in the SAME integer minor
32
+ * units — `0` when nothing has, `null` on a row written before an earlier schema ran.
33
+ *
34
+ * Net revenue for a row is `amount - refundedAmount`. It is a separate figure precisely so
35
+ * a PARTIAL refund does not have to be spelled by mangling `amount` or `status`: a R$10
36
+ * refund on a R$100 charge leaves `amount: 10000, refundedAmount: 1000, status: 'paid'`.
37
+ * NEVER divide — format at the edge.
38
+ */
39
+ refundedAmount: number | null;
40
+ paidAt: Date | null;
41
+ createdAt: Date | null;
42
+ }
43
+ /** One `billing_customers` row, normalized for reading. See {@link PaymentListItem}. */
44
+ export interface CustomerListItem {
45
+ id: string;
46
+ gatewayId: string;
47
+ provider: string;
48
+ ownerType: string | null;
49
+ ownerId: string | null;
50
+ email: string | null;
51
+ name: string | null;
52
+ taxId: string | null;
53
+ createdAt: Date | null;
54
+ }
55
+ /** One `billing_subscriptions` row, normalized for reading. See {@link PaymentListItem}. */
56
+ export interface SubscriptionListItem {
57
+ id: string;
58
+ gatewayId: string;
59
+ provider: string;
60
+ status: string;
61
+ planId: string;
62
+ customerId: string | null;
63
+ trialEndsAt: Date | null;
64
+ endsAt: Date | null;
65
+ createdAt: Date | null;
66
+ }
67
+ /** One `billing_webhook_events` row, normalized for reading. See {@link PaymentListItem}. */
68
+ export interface WebhookEventListItem {
69
+ id: string;
70
+ gatewayEventId: string;
71
+ provider: string;
72
+ type: string;
73
+ status: string;
74
+ /** The handler's failure message when `status === 'failed'`; `null` otherwise. */
75
+ error: string | null;
76
+ createdAt: Date | null;
77
+ updatedAt: Date | null;
78
+ }
79
+ /** One `billing_disputes` row, normalized for reading. See {@link PaymentListItem}. */
80
+ export interface DisputeListItem {
81
+ id: string;
82
+ /** The DISPUTE's own gateway id — or the synthesized one, for a gateway that sends none. */
83
+ gatewayId: string;
84
+ /** The disputed payment's gateway id — the join back to `billing_payments`. */
85
+ paymentGatewayId: string;
86
+ provider: string;
87
+ /** A {@link import('../types.js').DisputeStatus} value. */
88
+ status: string;
89
+ reason: string | null;
90
+ /** Integer minor units, or `null` — an early fraud warning names no money. NEVER divide here. */
91
+ amount: number | null;
92
+ currency: string | null;
93
+ /**
94
+ * The deadline to respond, or `null` when the gateway sent none.
95
+ *
96
+ * `null` means "this gateway told us nothing", never "no hurry": a reader must not treat
97
+ * it as a distant deadline, and {@link BillingStore.listDisputesDueWithin} does not
98
+ * return rows that carry one.
99
+ */
100
+ evidenceDueBy: Date | null;
101
+ outcome: string | null;
102
+ openedAt: Date | null;
103
+ closedAt: Date | null;
104
+ createdAt: Date | null;
105
+ }
106
+ /**
107
+ * The dispute statuses that still need an answer — everything that is not a resolution.
108
+ *
109
+ * Shared, and exported, because the deadline read is only meaningful against the same set
110
+ * in both store implementations: one of them counting `expired` rows as open would make an
111
+ * alert that never goes quiet, and one of them omitting `warning` would drop the alert that
112
+ * arrives while a refund still prevents the chargeback.
113
+ */
114
+ export declare const OPEN_DISPUTE_STATUSES: readonly ['warning', 'open', 'under_review'];
115
+ /**
116
+ * "Which windows close soon?" — the one question this table was added to answer.
117
+ *
118
+ * Deliberately not expressible through {@link BillingListQuery}: it filters on a deadline,
119
+ * not on a creation time, and it is the read an alert is built on, so a store that quietly
120
+ * ignored the window would report a healthy zero.
121
+ */
122
+ export interface DisputeDeadlineQuery {
123
+ /** How far ahead to look, in HOURS. `72` = "everything due in the next three days". */
124
+ withinHours: number;
125
+ /** Overridable clock — the tests pass a fixed instant. Defaults to now. */
126
+ now?: Date;
127
+ /** Exact provider match. Omit for every provider. */
128
+ provider?: string;
129
+ limit?: number;
130
+ offset?: number;
131
+ }
132
+ /**
133
+ * One line of {@link BillingStore.webhookEventBreakdown} — how many ledger rows matched,
134
+ * grouped by the two fields that identify WHAT is failing: which gateway, and which event.
135
+ */
136
+ export interface WebhookEventBreakdownLine {
137
+ provider: string;
138
+ type: string;
139
+ count: number;
140
+ }
141
+ /**
142
+ * Filter for the count/breakdown reads — the operational questions ("how many events are
143
+ * stuck?", "what failed in the last day?") that a page of rows cannot answer.
144
+ *
145
+ * Kept separate from {@link BillingListQuery} on purpose: a store that silently ignored a
146
+ * window it did not implement would report a healthy zero, which is the one wrong answer an
147
+ * alert must never get.
148
+ */
149
+ export interface BillingCountQuery {
150
+ /** Exact status match. Omit for every status. */
151
+ status?: string;
152
+ /** Only rows created strictly BEFORE this instant — "older than", for staleness checks. */
153
+ createdBefore?: Date;
154
+ /** Only rows created at or AFTER this instant — "within the last N", for recency checks. */
155
+ createdAfter?: Date;
156
+ }
157
+ /**
158
+ * Filter + page for {@link BillingStore.listPayments}.
159
+ *
160
+ * The three fields beyond `status`/`provider` all answer the SAME question, which the console
161
+ * could not ask before: "did THIS charge land?" An operator holds one of three ids — the app's
162
+ * own reference for the order, the gateway's id for the payment, or the gateway's id for the
163
+ * customer — and a list that filters only on status makes them page a growing table looking for
164
+ * a string.
165
+ *
166
+ * All three are EXACT matches, not prefixes or substrings. They are join keys, not search terms:
167
+ * a substring match on `externalReference` would let `order-4` return `order-42`, and the whole
168
+ * point of the lookup is that the row it returns is the row that was asked for.
169
+ */
170
+ export interface PaymentListQuery extends BillingListQuery {
171
+ /**
172
+ * The app's own id for the charge (`ChargeInput.externalReference`), exactly as
173
+ * {@link BillingStore.findPaymentByExternalReference} matches it.
174
+ *
175
+ * Unlike that method this returns every row carrying the reference rather than the newest —
176
+ * an app may reuse a reference across retries, and when it did, "which one landed?" is
177
+ * precisely the question being asked.
178
+ */
179
+ externalReference?: string;
180
+ /** The gateway's own payment id (`pi_...`, `pay_...`) — what the gateway's dashboard shows. */
181
+ gatewayId?: string;
182
+ /** The gateway's customer id — every payment recorded for one customer. */
183
+ customerId?: string;
184
+ }
185
+ /** Filter + page for {@link BillingStore.listWebhookEvents}. */
186
+ export interface WebhookEventListQuery extends BillingListQuery {
187
+ /**
188
+ * Exact event-type match (`payment.succeeded`, `payment.refunded`, ...). Omit for every type.
189
+ *
190
+ * `status` + `provider` answers "what is failing on Asaas"; without this there is no way to
191
+ * ask "did any refund event arrive at all", which is the question a ledger is read for once
192
+ * a specific charge is in doubt. The type vocabulary is discoverable —
193
+ * {@link BillingStore.webhookEventBreakdown} with no filter enumerates exactly the types this
194
+ * install has actually received.
195
+ */
196
+ type?: string;
197
+ }
198
+ /** Filter + page for {@link BillingStore.listCustomers}. */
199
+ export interface CustomerListQuery extends BillingListQuery {
200
+ /** The app-side owner type (`'users'`) written by `ensureCustomer`. Exact match. */
201
+ ownerType?: string;
202
+ /** The app-side owner id. Exact match; pair it with `ownerType`. */
203
+ ownerId?: string;
204
+ /** The gateway's customer id — the join key a payment row carries. Exact match. */
205
+ gatewayId?: string;
206
+ }
207
+ /**
208
+ * "Which disputes are still unanswered?" — regardless of whether anyone told us a deadline.
209
+ *
210
+ * The deliberately deadline-FREE counterpart to {@link DisputeDeadlineQuery}, and it exists
211
+ * because the deadline one is structurally blind on most installs: `evidence_due_by` can only be
212
+ * filled by a gateway that publishes the field, and several of the eighteen drivers here never
213
+ * receive one. On those, `countDisputesDueWithin` returns zero while a chargeback sits open with
214
+ * the money already pulled — a healthy report about an install that is losing money by default.
215
+ */
216
+ export interface OpenDisputeQuery {
217
+ /** Exact provider match. Omit for every provider. */
218
+ provider?: string;
219
+ limit?: number;
220
+ offset?: number;
221
+ }
222
+ /**
223
+ * One `billing_audit_events` row, normalized for reading. See {@link PaymentListItem}.
224
+ *
225
+ * The table records the things that HAPPENED to this install that no other table keeps: a human
226
+ * refunding from the console, a human resolving a dispute the gateway will never close, and a
227
+ * delivery the endpoint REJECTED before it ever became a ledger row. Every one of those was
228
+ * previously a diagnostic and nothing else — i.e. a line in a log that has usually rotated away
229
+ * by the time somebody asks.
230
+ */
231
+ export interface AuditEventListItem {
232
+ id: string;
233
+ /** One of {@link AUDIT_ACTIONS}' values. A free string in the column: an app may add its own. */
234
+ action: string;
235
+ /**
236
+ * WHO did it — the dashboard session's user, as `enforce()` knew them.
237
+ *
238
+ * `null` when nothing authorised it in a human sense: a rejected delivery has no actor, and a
239
+ * console with no `dashboardAuth` configured cannot name one. `null` therefore means
240
+ * "unattributed", never "the system".
241
+ */
242
+ actor: string | null;
243
+ provider: string | null;
244
+ /** `'payment'` | `'dispute'` | `'webhook'`, or whatever an app records. */
245
+ subjectType: string | null;
246
+ /** The subject's gateway id — the join back to the table it names. */
247
+ subjectId: string | null;
248
+ /** Integer minor units, or `null`. NEVER divide here. */
249
+ amount: number | null;
250
+ currency: string | null;
251
+ /** A human sentence: the refusal reason, the operator's note. */
252
+ message: string | null;
253
+ metadata: Record<string, unknown> | null;
254
+ createdAt: Date | null;
255
+ }
256
+ /** The actions this package records itself. A free string in the column; these are the built-ins. */
257
+ export declare const AUDIT_ACTIONS: {
258
+ /** A refund issued FROM THE CONSOLE. The gateway's own webhook still moves the payment row. */
259
+ readonly refund: 'payment.refunded';
260
+ /** A dispute an operator closed by hand, because the gateway publishes no lost-dispute event. */
261
+ readonly disputeResolved: 'dispute.resolved';
262
+ /** A delivery the webhook endpoint refused: bad signature, unparsable body, unknown provider. */
263
+ readonly webhookRejected: 'webhook.rejected';
264
+ };
265
+ /** Filter + page for {@link BillingStore.listAuditEvents}. */
266
+ export interface AuditEventQuery {
267
+ /** Exact action match. */
268
+ action?: string;
269
+ /** Any of these actions — the timeline reads several at once. Composed with `action` as AND. */
270
+ actions?: readonly string[];
271
+ actor?: string;
272
+ provider?: string;
273
+ subjectType?: string;
274
+ subjectId?: string;
275
+ /** Only rows created at or AFTER this instant — the window a health check counts over. */
276
+ createdAfter?: Date;
277
+ /** Only rows created strictly BEFORE this instant. */
278
+ createdBefore?: Date;
279
+ limit?: number;
280
+ offset?: number;
281
+ }
282
+ /** The same filter without a page — the count a health check alerts on. */
283
+ export type AuditEventCountQuery = Omit<AuditEventQuery, 'limit' | 'offset'>;
284
+ /** Filter + page for the two list queries. `limit`/`offset` are applied after the filter. */
285
+ export interface BillingListQuery {
286
+ /** Exact status match. Omit for every status. */
287
+ status?: string;
288
+ /**
289
+ * Exact provider match. Omit for every provider.
290
+ *
291
+ * Not decorative: with eighteen gateways configurable at once, "what is failing on
292
+ * Asaas" is a different question from "what is failing", and without this the dashboard
293
+ * had to page the whole table and filter in memory.
294
+ */
295
+ provider?: string;
296
+ limit?: number;
297
+ offset?: number;
298
+ }
2
299
  /**
3
300
  * The persistence SPI for the billing layer. The Lucid implementation writes through the
4
301
  * configured models; an in-memory implementation exists in `src/testing` so the billing
@@ -7,7 +304,54 @@ import type { BillingPayment, BillingSubscription, BillingUsageEvent, BillingWeb
7
304
  * Generic over the row types so the in-memory store can use plain objects while the
8
305
  * Lucid store returns model instances.
9
306
  */
10
- export interface BillingStore<SubscriptionRow = BillingSubscription, PaymentRow = BillingPayment, WebhookEventRow = BillingWebhookEvent, UsageEventRow = BillingUsageEvent> {
307
+ export interface BillingStore<SubscriptionRow = BillingSubscription, PaymentRow = BillingPayment, WebhookEventRow = BillingWebhookEvent, UsageEventRow = BillingUsageEvent, CustomerRow = BillingCustomer, DisputeRow = BillingDispute> {
308
+ /**
309
+ * Upsert the mapping between an app-side owner and its gateway customer, keyed by
310
+ * gateway id.
311
+ *
312
+ * Nothing records this for you — the library learns a gateway customer id inside
313
+ * `ensureCustomer`, which takes a store precisely so the mapping is written where it is
314
+ * created. Without it `billing_customers` stays empty and `payments:sync --all` has
315
+ * nothing to reconcile over.
316
+ */
317
+ saveCustomer(customer: {
318
+ gatewayId: string;
319
+ provider: string;
320
+ ownerType?: string | null;
321
+ ownerId?: string | null;
322
+ email?: string | null;
323
+ name?: string | null;
324
+ taxId?: string | null;
325
+ metadata?: Record<string, unknown>;
326
+ }): Promise<CustomerRow>;
327
+ findCustomerByGatewayId(gatewayId: string): Promise<CustomerRow | null>;
328
+ /**
329
+ * The customer an app-side row holds at one gateway.
330
+ *
331
+ * `provider` is part of the question, not an optional filter: one owner may legitimately
332
+ * have a customer at every configured gateway, and answering without it would return an
333
+ * arbitrary one of them.
334
+ */
335
+ findCustomerByOwner(ownerType: string, ownerId: string, provider: string): Promise<CustomerRow | null>;
336
+ /**
337
+ * Page through recorded customers, newest first, optionally narrowed to one provider.
338
+ *
339
+ * This is what `payments:sync --all` iterates. Returns the normalized
340
+ * {@link CustomerListItem} so a reader never depends on Lucid.
341
+ */
342
+ listCustomers(query: CustomerListQuery): Promise<CustomerListItem[]>;
343
+ /**
344
+ * The customer mappings behind a set of gateway customer ids, in one read.
345
+ *
346
+ * The join the payments screen needs and could not do: a payment row carries the GATEWAY's
347
+ * customer id (`cus_…`), and the only thing that ties it back to an app user is
348
+ * `billing_customers.owner_type`/`owner_id`, written by `ensureCustomer`. Resolving that one
349
+ * row at a time would be a query per row of every page.
350
+ *
351
+ * Ids not present simply do not appear — a short result is the answer, not an error. An empty
352
+ * input returns an empty array without touching the database.
353
+ */
354
+ listCustomersByGatewayIds(gatewayIds: readonly string[]): Promise<CustomerListItem[]>;
11
355
  /** Upsert a subscription keyed by gateway id. Returns the stored row. */
12
356
  saveSubscription(sub: {
13
357
  gatewayId: string;
@@ -20,7 +364,32 @@ export interface BillingStore<SubscriptionRow = BillingSubscription, PaymentRow
20
364
  payload?: Record<string, unknown>;
21
365
  }): Promise<SubscriptionRow>;
22
366
  findSubscriptionByGatewayId(gatewayId: string): Promise<SubscriptionRow | null>;
23
- /** Upsert a payment keyed by gateway id. Returns the stored row. */
367
+ /**
368
+ * Page through subscriptions, newest first, optionally filtered by status.
369
+ *
370
+ * `countActiveSubscriptions()` answers "how many", which is the wrong question on any day
371
+ * something is wrong: the operational ones are WHICH subscriptions are `past_due`, and
372
+ * which are `paused` — a count cannot name a customer to email.
373
+ */
374
+ listSubscriptions(query: BillingListQuery): Promise<SubscriptionListItem[]>;
375
+ /** How many subscriptions match a status and/or a creation window. */
376
+ countSubscriptions(query: BillingCountQuery): Promise<number>;
377
+ /**
378
+ * Upsert a payment keyed by gateway id. Returns the stored row.
379
+ *
380
+ * `externalReference` left `undefined` does NOT erase a stored one: the first webhook of a
381
+ * charge usually carries the app's reference and a later one (a refund, a dispute) often
382
+ * does not, and blanking it there would throw away the only key
383
+ * {@link BillingStore.findPaymentByExternalReference} can route on. Pass `null` explicitly
384
+ * to clear it. Same rule, same reason, as `saveCustomer`'s owner mapping.
385
+ *
386
+ * **`paidAt` and `refundedAmount` follow exactly the same rule**, and the reason is money.
387
+ * A refund, a chargeback and a dispute close all write this row and none of them carries a
388
+ * settlement date; a store that wrote `undefined` through as `null` destroyed `paid_at` on
389
+ * every one of them, and {@link BillingStore.revenue} filters on `paid_at`. A dispute closed
390
+ * as WON therefore restored `status = 'paid'` with no date and the recovered money dropped
391
+ * out of every windowed revenue figure. Absent means "not stated"; `null` means "clear it".
392
+ */
24
393
  savePayment(payment: {
25
394
  gatewayId: string;
26
395
  provider: string;
@@ -29,19 +398,246 @@ export interface BillingStore<SubscriptionRow = BillingSubscription, PaymentRow
29
398
  currency: string;
30
399
  customerId?: string | null;
31
400
  subscriptionId?: string | null;
401
+ /** The app's own id for this charge (`ChargeInput.externalReference`), echoed by the gateway. */
402
+ externalReference?: string | null;
32
403
  paidAt?: Date | null;
404
+ /**
405
+ * Integer minor units refunded so far — the whole point of the field is that a PARTIAL
406
+ * refund can be recorded without lying about `amount` or `status`. Absent leaves the
407
+ * stored figure alone; `null` clears it.
408
+ */
409
+ refundedAmount?: number | null;
33
410
  payload?: Record<string, unknown>;
34
411
  }): Promise<PaymentRow>;
35
412
  findPaymentByGatewayId(gatewayId: string): Promise<PaymentRow | null>;
36
- /** Record a webhook event for idempotency. Returns null when the event was already recorded. */
413
+ /**
414
+ * The payment carrying one app-side reference — the id the APP knows the charge by.
415
+ *
416
+ * The other direction of {@link findPaymentByGatewayId}, and the one an app actually has
417
+ * at hand: a checkout page polls for "order-1042", not for `pi_3Qx...`. Backed by
418
+ * `billing_payments_external_reference_idx`.
419
+ *
420
+ * `null` when no row carries it — including on a payment written before the install ran
421
+ * an earlier schema, whose column is `null` for every row.
422
+ * Ties are broken newest-first: nothing stops an app reusing a reference across retries.
423
+ */
424
+ findPaymentByExternalReference(reference: string): Promise<PaymentRow | null>;
425
+ /**
426
+ * Page through recorded payments, newest first, optionally filtered by status.
427
+ *
428
+ * A narrow read the aggregates above cannot answer: `revenue()` sums, it does not say
429
+ * WHICH payment failed. Returns the normalized {@link PaymentListItem}, not the
430
+ * implementation's row type, so a reader never depends on Lucid.
431
+ */
432
+ listPayments(query: PaymentListQuery): Promise<PaymentListItem[]>;
433
+ /**
434
+ * How many payments match a status and/or a creation window.
435
+ *
436
+ * The check this exists for: charges that were created and never confirmed
437
+ * (`{ status: 'pending', createdBefore: twoHoursAgo }`). A webhook endpoint that stops
438
+ * being reachable produces exactly that and nothing else in the system errors.
439
+ */
440
+ countPayments(query: BillingCountQuery): Promise<number>;
441
+ /**
442
+ * Upsert a dispute keyed by `gatewayId` — the DISPUTE's own id, not the payment's.
443
+ *
444
+ * Absent fields do NOT erase what is already recorded, the same rule (and for a sharper
445
+ * reason) as `savePayment`'s `externalReference`: the event that opens a dispute carries
446
+ * the deadline and the reason, and the event that CLOSES it carries neither. Blanking
447
+ * them on the close would destroy the only record of the window that was answered — and
448
+ * the deadline is the entire reason this table exists. Pass `null` explicitly to clear.
449
+ *
450
+ * Returns `null` — and writes nothing — when the install has no `billing_disputes` table
451
+ * yet, i.e. it upgraded the package before running an earlier schema. The dispute row
452
+ * is ADDITIONAL (the payment row still moves, the diagnostics still publish), so the write
453
+ * is skipped rather than failing every gateway delivery until someone runs the migration.
454
+ */
455
+ saveDispute(dispute: {
456
+ /** The dispute's own gateway id. See {@link DisputeListItem.gatewayId}. */
457
+ gatewayId: string;
458
+ /** The disputed payment's gateway id. */
459
+ paymentGatewayId: string;
460
+ provider: string;
461
+ /** A {@link import('../types.js').DisputeStatus} value. */
462
+ status: string;
463
+ reason?: string | null;
464
+ /** Integer minor units. */
465
+ amount?: number | null;
466
+ currency?: string | null;
467
+ evidenceDueBy?: Date | null;
468
+ outcome?: string | null;
469
+ /** Defaults to the insert instant, and is never moved by a later update. */
470
+ openedAt?: Date | null;
471
+ closedAt?: Date | null;
472
+ payload?: Record<string, unknown>;
473
+ }): Promise<DisputeRow | null>;
474
+ /** The dispute with this gateway id, or `null`. The idempotency lookup behind the upsert. */
475
+ findDisputeByGatewayId(gatewayId: string): Promise<DisputeRow | null>;
476
+ /**
477
+ * The most recent UNRESOLVED dispute against one payment, or `null`.
478
+ *
479
+ * The processor's answer to an event that carries no dispute id: several gateways send
480
+ * the id when the dispute opens and omit it when it closes, and without this the close
481
+ * would open a second row instead of finishing the first. Resolved disputes are skipped
482
+ * so a fresh chargeback on a previously-lost payment starts its own row.
483
+ */
484
+ findOpenDisputeByPayment(paymentGatewayId: string): Promise<DisputeRow | null>;
485
+ /**
486
+ * Page through recorded disputes, newest first, `status`/`provider` filters — the same
487
+ * shape as {@link listPayments}, and normalized for the same reason.
488
+ */
489
+ listDisputes(query: BillingListQuery): Promise<DisputeListItem[]>;
490
+ /** How many disputes match a status and/or a creation window. */
491
+ countDisputes(query: BillingCountQuery): Promise<number>;
492
+ /**
493
+ * The open disputes whose response window closes within `withinHours` — ordered by the
494
+ * deadline, soonest FIRST.
495
+ *
496
+ * The one read that earns the table, and the only list here that is not newest-first: the
497
+ * ordering is the priority order, because a window that closes tomorrow outranks a dispute
498
+ * that arrived today.
499
+ *
500
+ * A deadline that is already PAST is included, not filtered out. It is still open, nothing
501
+ * has answered it, and dropping it the moment it expires would make the alert go quiet at
502
+ * exactly the moment it became true — the operator would read that as resolved.
503
+ *
504
+ * Rows with no `evidenceDueBy` are excluded: a gateway that sent no deadline gives nothing
505
+ * to be late for, and putting them in the list would bury the rows that do have one.
506
+ */
507
+ listDisputesDueWithin(query: DisputeDeadlineQuery): Promise<DisputeListItem[]>;
508
+ /**
509
+ * The count behind {@link listDisputesDueWithin}, unbounded by any page.
510
+ *
511
+ * Separate from the list on purpose: the health check alerts on this number, and a count
512
+ * derived from a capped page saturates at the cap — which reads as "200 disputes" forever
513
+ * and, worse, would report a healthy zero if the page limit were ever misapplied.
514
+ */
515
+ countDisputesDueWithin(query: Omit<DisputeDeadlineQuery, 'limit' | 'offset'>): Promise<number>;
516
+ /**
517
+ * Every dispute that still needs an answer, oldest FIRST — deadline or no deadline.
518
+ *
519
+ * The check {@link listDisputesDueWithin} cannot be. That one requires `evidence_due_by`, and
520
+ * the deadline only ever arrives from a gateway that publishes one; on Asaas it comes from
521
+ * `chargeback.deadlineToSendDisputeDocuments`, which no published webhook example even
522
+ * contains. On such an install every deadline read answers zero forever, so a chargeback can
523
+ * be open with the money already pulled back while the report says healthy.
524
+ *
525
+ * Oldest first because with no deadline to sort on, "how long has nobody answered this" is the
526
+ * only priority signal left.
527
+ */
528
+ listOpenDisputes(query: OpenDisputeQuery): Promise<DisputeListItem[]>;
529
+ /** The unbounded count behind {@link listOpenDisputes} — what a health check alerts on. */
530
+ countOpenDisputes(query: {
531
+ provider?: string;
532
+ }): Promise<number>;
533
+ /**
534
+ * Record a webhook event for idempotency.
535
+ *
536
+ * Returns `null` when this gateway event is already in flight or already
537
+ * processed — that is the guard that makes a gateway redelivery a no-op.
538
+ *
539
+ * An event whose previous attempt **failed** is claimed again and returned,
540
+ * so a retry actually re-runs it. Without that, the first failure would seal
541
+ * the ledger and every retry — in-process or durable — would short-circuit
542
+ * into doing nothing.
543
+ */
37
544
  recordWebhookEvent(event: {
38
545
  gatewayEventId: string;
39
546
  provider: string;
40
547
  type: string;
41
548
  payload: Record<string, unknown>;
549
+ /**
550
+ * The NORMALIZED event (`WebhookEvent.data`), stored alongside the raw payload.
551
+ *
552
+ * Without it the dashboard's retry had to rebuild the event by calling `parseWebhook`
553
+ * over the stored payload, which re-verifies a signature computed from headers the
554
+ * ledger never kept — so a retry on Stripe or Adyen answered `422` and only unsigned
555
+ * gateways could replay. Stored once, on the way in, where it is still in hand.
556
+ *
557
+ * Left `undefined` on the re-claim path (a retry), where the row keeps what it has.
558
+ */
559
+ normalized?: unknown;
42
560
  }): Promise<WebhookEventRow | null>;
43
561
  markWebhookProcessed(id: string): Promise<void>;
44
562
  markWebhookFailed(id: string, error: string): Promise<void>;
563
+ /**
564
+ * Page through the ledger, newest first, optionally filtered by status.
565
+ *
566
+ * `status: 'failed'` is the operationally load-bearing one: a `failed` row means a
567
+ * handler threw and the dispatcher gave up, so the event's effect never happened. It
568
+ * carries the handler's `error`, which is the only place that message survives.
569
+ */
570
+ listWebhookEvents(query: WebhookEventListQuery): Promise<WebhookEventListItem[]>;
571
+ /**
572
+ * The ledger rows whose stored delivery NAMES one payment — that payment's timeline.
573
+ *
574
+ * `billing_payments` is a single mutable row upserted in place: it holds the current state and
575
+ * no history at all, so "what happened to this charge, and when?" has no answer from the
576
+ * payments table. The ledger does have one — every delivery that moved the row is in it — but
577
+ * nothing links a ledger row to a payment, because the link lives inside the stored payload.
578
+ *
579
+ * So this is a SUBSTRING match over the payload, and both of that decision's costs are real:
580
+ * it is an unindexed scan (bounded by `limit`, newest first, which is why the bound is not
581
+ * optional in spirit even though it has a default), and a gateway id that happens to be a
582
+ * substring of some other identifier in another delivery will match. It is offered anyway
583
+ * because the alternative on the table today is nothing at all. The honest fix is a
584
+ * `payment_gateway_id` column the PROCESSOR fills on the way in; see the changeset.
585
+ *
586
+ * Never returns the payload itself — the same rule the rest of this console follows.
587
+ */
588
+ listWebhookEventsForPayment(paymentGatewayId: string, query?: {
589
+ limit?: number;
590
+ }): Promise<WebhookEventListItem[]>;
591
+ /**
592
+ * The ledger row for one **gateway** event id — the id the gateway's dashboard shows.
593
+ *
594
+ * This is the first question when a customer paid and nothing happened: did the event
595
+ * arrive at all? `null` means it never reached the processor; a row with status `failed`
596
+ * carries the handler's error.
597
+ */
598
+ findWebhookEventByGatewayEventId(gatewayEventId: string): Promise<WebhookEventListItem | null>;
599
+ /**
600
+ * How many ledger rows match a status and/or a creation window.
601
+ *
602
+ * `{ status: 'received', createdBefore: fifteenMinutesAgo }` is the one to alert on: an
603
+ * event was claimed and nothing ever finished it, which usually means the worker the
604
+ * dispatcher depends on is not running.
605
+ */
606
+ countWebhookEvents(query: BillingCountQuery): Promise<number>;
607
+ /**
608
+ * The same count, grouped by provider and event type — "what is failing", not just
609
+ * "how much". Ordered by count, descending.
610
+ */
611
+ webhookEventBreakdown(query: BillingCountQuery): Promise<WebhookEventBreakdownLine[]>;
612
+ /**
613
+ * Record something that happened which no other table keeps: a console refund, a dispute an
614
+ * operator resolved by hand, a delivery the endpoint rejected.
615
+ *
616
+ * Returns `null` — and writes nothing — when the install has no `billing_audit_events` table
617
+ * yet, i.e. it upgraded the package before running an earlier schema. Exactly the tolerance
618
+ * {@link BillingStore.saveDispute} has, and for the same reason: the audit row is ADDITIONAL,
619
+ * and failing a refund that the gateway already accepted because the note could not be filed
620
+ * would be the worse outcome by a distance.
621
+ */
622
+ recordAuditEvent(event: {
623
+ action: string;
624
+ /** WHO. `null`/absent means unattributed — see {@link AuditEventListItem.actor}. */
625
+ actor?: string | null;
626
+ provider?: string | null;
627
+ subjectType?: string | null;
628
+ subjectId?: string | null;
629
+ /** Integer minor units. */
630
+ amount?: number | null;
631
+ currency?: string | null;
632
+ message?: string | null;
633
+ metadata?: Record<string, unknown> | null;
634
+ /** Defaults to the insert instant. */
635
+ createdAt?: Date;
636
+ }): Promise<AuditEventListItem | null>;
637
+ /** Page through the audit trail, newest first. Empty on an install with no table yet. */
638
+ listAuditEvents(query: AuditEventQuery): Promise<AuditEventListItem[]>;
639
+ /** How many audit rows match — the count the rejected-delivery check alerts on. */
640
+ countAuditEvents(query: AuditEventCountQuery): Promise<number>;
45
641
  /** Record one metered-usage event (a metered subscription's consumption). */
46
642
  recordUsage(event: {
47
643
  subscriptionId?: string | null;
@@ -62,11 +658,44 @@ export interface BillingStore<SubscriptionRow = BillingSubscription, PaymentRow
62
658
  meter: string;
63
659
  quantity: number;
64
660
  }>>;
65
- /** Sum of paid payments within a window (revenue, in cents). */
661
+ /**
662
+ * Sum of paid payments within a window, **GROSS** — `amount` on `status = 'paid'` rows,
663
+ * windowed on `paid_at`, with nothing subtracted.
664
+ *
665
+ * A half-refunded charge counts at its FULL value here, because that is what gross means:
666
+ * it answers "how much did we charge and collect in this window". For "how much did we
667
+ * keep", see {@link BillingStore.netRevenue}. Both are legitimate figures and neither is
668
+ * the other's replacement — a screen that shows one without saying which is the screen that
669
+ * misleads. Integer minor units; NEVER divide.
670
+ */
66
671
  revenue(query: {
67
672
  from?: Date;
68
673
  to?: Date;
69
674
  }): Promise<number>;
675
+ /**
676
+ * The same rows and the same window as {@link BillingStore.revenue}, **NET of refunds**:
677
+ * `SUM(amount - COALESCE(refunded_amount, 0))`.
678
+ *
679
+ * The `refunded_amount` column exists precisely so a PARTIAL refund does not have to be
680
+ * spelled by mangling `amount` or `status` — a R$10 refund on a R$100 charge leaves the row
681
+ * `paid` at `amount: 10000, refundedAmount: 1000`. Gross sees R$100 of that row, net sees
682
+ * R$90, and until this existed only gross was reachable, so a partially refunded charge was
683
+ * invisible in every revenue figure the console printed.
684
+ *
685
+ * `COALESCE` is not decoration: `refunded_amount` is `NULL` on every row written before the
686
+ * column existed, and `10000 - NULL` is `NULL` in SQL, which would poison the whole SUM into
687
+ * `NULL` — one legacy row would report zero revenue for the entire window. `NULL` means
688
+ * "nothing came back", so it reads as zero.
689
+ *
690
+ * On an install whose table predates the column, this equals {@link BillingStore.revenue}:
691
+ * no refund was ever recorded, so there is nothing to subtract.
692
+ *
693
+ * Integer minor units, like everything else here. NEVER divide — format at the edge.
694
+ */
695
+ netRevenue(query: {
696
+ from?: Date;
697
+ to?: Date;
698
+ }): Promise<number>;
70
699
  /** Count of active subscriptions (status `'active'`/`'trialing'`). */
71
700
  countActiveSubscriptions(): Promise<number>;
72
701
  }