@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
@@ -0,0 +1,905 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { publishPaymentDiagnostics, publishRefundDiagnostics, publishSubscriptionDiagnostics, } from '../diagnostics.js';
3
+ import { headerValue, httpRequest, isNotFound } from '../http.js';
4
+ import { emitInvoiceIfRequested } from '../invoice/emit_invoice.js';
5
+ import { verifyHmacOverPayload } from '../webhook_security.js';
6
+ import { requireCredential, requireCurrency } from './shared.js';
7
+ /** The `Square-Version` these mappings were written against. */
8
+ const DEFAULT_API_VERSION = '2026-08-19';
9
+ /** `idempotency_key` on the Payments and Refunds APIs. Payment links allow 192. */
10
+ const IDEMPOTENCY_MAX_LENGTH = 45;
11
+ /** `reference_id` on a Payment and on an Order. */
12
+ const REFERENCE_MAX_LENGTH = 40;
13
+ /** `note` on a Payment. */
14
+ const NOTE_MAX_LENGTH = 500;
15
+ /**
16
+ * Square driver — the US-first seller platform (`connect.squareup.com/v2`), Bearer token,
17
+ * multi-currency, everything scoped to one seller location.
18
+ *
19
+ * Three shapes of this API drive the mappings below:
20
+ *
21
+ * **A charge needs a token you cannot mint server-side.** `POST /v2/payments` takes a
22
+ * `source_id`: a single-use card nonce from the Web Payments SDK, or a `ccof:`/card-on-file
23
+ * id. There is no "charge this customer for this amount" call, so `charge()` requires
24
+ * `paymentMethodId` (or `card.token`) and refuses without one rather than inventing a flow.
25
+ *
26
+ * **`idempotency_key` is a body field, not a header** — on payments, refunds, subscriptions
27
+ * and payment links alike. Square also *requires* it on payments and refunds, so a call
28
+ * that arrives without one gets a generated UUID; that protects the retry inside a single
29
+ * call, not a retry from your own job queue, which is what `idempotencyKey` is for.
30
+ *
31
+ * **Webhook signatures cover `notificationUrl + body`.** The URL is part of the signed
32
+ * material, which means the driver has to be told its own public URL. Configure
33
+ * `webhookSignatureKey` without `notificationUrl` and the constructor throws: the
34
+ * alternative is a driver that rejects every genuine webhook, or worse, one that decides
35
+ * to skip verification because it lacks a piece of the input.
36
+ *
37
+ * Money is an integer in the currency's minor unit on both sides (`{ amount: 1990,
38
+ * currency: 'USD' }`), so this driver converts nothing — see {@link SquareDriver.charge}.
39
+ */
40
+ export class SquareDriver {
41
+ provider = 'square';
42
+ /**
43
+ * `source_id` decides the instrument, and it is minted in the browser — the driver hands
44
+ * Square an opaque token and learns what it was only from `source_type` on the way back.
45
+ * The Web Payments SDK mints more than card tokens, though: Cash App Pay and the device
46
+ * wallets (`WALLET`), ACH bank tokens (`BANK_ACCOUNT`) and Afterpay/Clearpay
47
+ * (`BUY_NOW_PAY_LATER`) all arrive at `POST /v2/payments` through the same `source_id`,
48
+ * so `charge()` genuinely produces those categories. `undefined` covers the hosted
49
+ * payment link, where the payer picks. Pix and boleto are absent because Square does not
50
+ * sell in Brazil.
51
+ */
52
+ supportedMethods = [
53
+ 'credit_card',
54
+ 'debit_card',
55
+ 'wallet',
56
+ 'bank_debit',
57
+ 'bnpl',
58
+ 'undefined',
59
+ ];
60
+ capabilities = { refunds: true, invoices: true, subscriptions: true };
61
+ #baseUrl;
62
+ #accessToken;
63
+ #locationId;
64
+ #currency;
65
+ #apiVersion;
66
+ #webhookSignatureKey;
67
+ #notificationUrl;
68
+ #invoiceCtx;
69
+ constructor(ctx, config) {
70
+ this.#invoiceCtx = ctx;
71
+ this.#accessToken = requireCredential({
72
+ driver: 'square',
73
+ option: 'accessToken',
74
+ env: 'SQUARE_ACCESS_TOKEN',
75
+ value: config.accessToken,
76
+ });
77
+ this.#locationId = requireCredential({
78
+ driver: 'square',
79
+ option: 'locationId',
80
+ env: 'SQUARE_LOCATION_ID',
81
+ value: config.locationId,
82
+ });
83
+ this.#currency = requireCurrency('square', config.currency);
84
+ this.#apiVersion = config.apiVersion ?? DEFAULT_API_VERSION;
85
+ this.#webhookSignatureKey =
86
+ config.webhookSignatureKey ?? process.env.SQUARE_WEBHOOK_SIGNATURE_KEY;
87
+ this.#notificationUrl = config.notificationUrl ?? process.env.SQUARE_WEBHOOK_NOTIFICATION_URL;
88
+ if (this.#webhookSignatureKey !== undefined && this.#notificationUrl === undefined) {
89
+ throw new Error('[payments] Driver "square" has a webhookSignatureKey but no notificationUrl. Square signs `notificationUrl + body`, so the URL is part of the secret: set `SQUARE_WEBHOOK_NOTIFICATION_URL` (or pass `notificationUrl`) to the exact URL registered on the webhook subscription.');
90
+ }
91
+ this.#baseUrl =
92
+ config.sandbox === true
93
+ ? 'https://connect.squareupsandbox.com/v2'
94
+ : 'https://connect.squareup.com/v2';
95
+ }
96
+ // ── Customers ────────────────────────────────────────────────────────────────────────
97
+ async createCustomer(input) {
98
+ const body = {
99
+ idempotency_key: this.#idempotencyKey(input.idempotencyKey),
100
+ // Square splits a person into `given_name`/`family_name` and has no single "name"
101
+ // field. Splitting on whitespace guesses which token is the surname, which is wrong
102
+ // for most of the world's names, so the whole string goes in `given_name` and the
103
+ // caller can pass `metadata.familyName` when it genuinely knows the split.
104
+ ...(input.name !== undefined ? { given_name: input.name } : {}),
105
+ ...(typeof input.metadata?.familyName === 'string'
106
+ ? { family_name: input.metadata.familyName }
107
+ : {}),
108
+ ...(typeof input.metadata?.companyName === 'string'
109
+ ? { company_name: input.metadata.companyName }
110
+ : {}),
111
+ ...(input.email !== undefined ? { email_address: input.email } : {}),
112
+ ...(typeof input.metadata?.phone === 'string' ? { phone_number: input.metadata.phone } : {}),
113
+ ...(typeof input.metadata?.referenceId === 'string'
114
+ ? { reference_id: input.metadata.referenceId }
115
+ : {}),
116
+ ...this.#taxIds(input.taxId, input.metadata),
117
+ };
118
+ const data = await this.#request('/customers', {
119
+ method: 'POST',
120
+ body,
121
+ });
122
+ return this.#mapCustomer(data.customer);
123
+ }
124
+ async findCustomer(customerId) {
125
+ try {
126
+ const data = await this.#request(`/customers/${customerId}`);
127
+ return this.#mapCustomer(data.customer);
128
+ }
129
+ catch (error) {
130
+ if (isNotFound(error))
131
+ return null;
132
+ throw error;
133
+ }
134
+ }
135
+ async updateCustomer(customerId, input) {
136
+ const body = {
137
+ ...(input.name !== undefined ? { given_name: input.name } : {}),
138
+ ...(typeof input.metadata?.familyName === 'string'
139
+ ? { family_name: input.metadata.familyName }
140
+ : {}),
141
+ ...(input.email !== undefined ? { email_address: input.email } : {}),
142
+ ...(typeof input.metadata?.phone === 'string' ? { phone_number: input.metadata.phone } : {}),
143
+ ...this.#taxIds(input.taxId, input.metadata),
144
+ };
145
+ const data = await this.#request(`/customers/${customerId}`, { method: 'PUT', body });
146
+ return this.#mapCustomer(data.customer);
147
+ }
148
+ // ── Payments ─────────────────────────────────────────────────────────────────────────
149
+ async charge(input) {
150
+ const sourceId = input.paymentMethodId ?? input.card?.token;
151
+ if (sourceId === undefined) {
152
+ throw new Error('[payments] Square needs a `source_id` to charge: a single-use card token from the Web Payments SDK, or a saved card id (`ccof:…`) together with `customerId`. Pass it as `paymentMethodId` (or `card.token`) — there is no server-side "charge this customer" call.');
153
+ }
154
+ if (input.method !== undefined &&
155
+ !['credit_card', 'debit_card', 'wallet', 'bank_debit', 'bnpl', 'undefined'].includes(input.method)) {
156
+ throw new Error(`[payments] Square has no "${input.method}" payment method. The instrument is fixed by the token in \`paymentMethodId\`, so \`method\` is a declaration, not an instruction: it may name a card (\`credit_card\`/\`debit_card\`), a wallet, \`bank_debit\` (ACH) or \`bnpl\` (Afterpay), and Square — not this call — decides which the token turns out to be.`);
157
+ }
158
+ if (input.split !== undefined && input.split.length > 0) {
159
+ throw new Error('[payments] Square does not split a payment across recipients. `app_fee_money` takes a single application fee for an OAuth-connected seller, which is not the same thing; pass it as `metadata.appFeeAmount` if that is what you want.');
160
+ }
161
+ const currency = (input.currency ?? this.#currency).toUpperCase();
162
+ const referenceId = this.#reference(input.externalReference, 'charge');
163
+ const body = {
164
+ source_id: sourceId,
165
+ // Square requires an idempotency key and takes it in the BODY, not a header. A
166
+ // generated UUID only makes Square's own retry safe; a caller that wants a job retry
167
+ // to be safe has to pass its own `idempotencyKey`.
168
+ idempotency_key: this.#idempotencyKey(input.idempotencyKey),
169
+ // No conversion anywhere in this driver: Square's `amount_money.amount` is already the
170
+ // integer minor unit, the same unit as this package's `Money`. The Brazilian drivers
171
+ // next door divide by 100 because their gateways want decimal reais — doing that here
172
+ // would bill a hundredth of the charge, and Square would accept it.
173
+ amount_money: { amount: input.amount, currency },
174
+ location_id: this.#locationId,
175
+ ...(input.customerId !== undefined ? { customer_id: input.customerId } : {}),
176
+ ...(referenceId !== undefined ? { reference_id: referenceId } : {}),
177
+ ...(input.description !== undefined
178
+ ? { note: input.description.slice(0, NOTE_MAX_LENGTH) }
179
+ : {}),
180
+ ...(input.card?.holder?.email !== undefined
181
+ ? { buyer_email_address: input.card.holder.email }
182
+ : {}),
183
+ // Delayed capture: `autocomplete: false` leaves the payment APPROVED (money held, not
184
+ // moved) until `completePayment` runs. See that method for why it is not on the
185
+ // driver contract.
186
+ ...(input.metadata?.autocomplete === false ? { autocomplete: false } : {}),
187
+ ...(typeof input.metadata?.appFeeAmount === 'number'
188
+ ? { app_fee_money: { amount: input.metadata.appFeeAmount, currency } }
189
+ : {}),
190
+ };
191
+ const data = await this.#request('/payments', {
192
+ method: 'POST',
193
+ body,
194
+ });
195
+ const payment = this.#mapPayment(data.payment);
196
+ await emitInvoiceIfRequested(this.#invoiceCtx, input, payment, this);
197
+ publishPaymentDiagnostics(payment);
198
+ return payment;
199
+ }
200
+ async findPayment(gatewayId) {
201
+ try {
202
+ const data = await this.#request(`/payments/${gatewayId}`);
203
+ return this.#mapPayment(data.payment);
204
+ }
205
+ catch (error) {
206
+ if (isNotFound(error))
207
+ return null;
208
+ throw error;
209
+ }
210
+ }
211
+ async refund(paymentGatewayId, amount, options) {
212
+ // `RefundPayment` has no "refund everything" mode — `amount_money` is mandatory. So a
213
+ // full refund is a read followed by a write, and the amount refunded is the amount
214
+ // Square reports for the payment, never a number this driver computed.
215
+ const target = amount !== undefined
216
+ ? { amount, currency: this.#currency.toUpperCase() }
217
+ : await this.#paymentAmount(paymentGatewayId);
218
+ const data = await this.#request('/refunds', {
219
+ method: 'POST',
220
+ body: {
221
+ // Square requires the key and takes it in the BODY. Generating one made Square's
222
+ // own retry safe and a retried job double-refund; the caller's key is what makes
223
+ // the second call return the first refund instead of issuing another.
224
+ idempotency_key: this.#idempotencyKey(options?.idempotencyKey),
225
+ payment_id: paymentGatewayId,
226
+ amount_money: target,
227
+ },
228
+ });
229
+ const refund = {
230
+ id: data.refund.id,
231
+ gatewayId: data.refund.id,
232
+ provider: this.provider,
233
+ amount: {
234
+ amount: data.refund.amount_money?.amount ?? target.amount,
235
+ currency: (data.refund.amount_money?.currency ?? target.currency).toLowerCase(),
236
+ },
237
+ // `PENDING` is the usual first answer for a card refund; `refund.updated` carrying
238
+ // `COMPLETED` is the only thing that means the money left.
239
+ status: data.refund.status === 'COMPLETED'
240
+ ? 'succeeded'
241
+ : data.refund.status === 'REJECTED' || data.refund.status === 'FAILED'
242
+ ? 'failed'
243
+ : 'pending',
244
+ createdAt: data.refund.created_at ?? new Date().toISOString(),
245
+ };
246
+ publishRefundDiagnostics(refund);
247
+ return refund;
248
+ }
249
+ /**
250
+ * Capture a payment created with `metadata: { autocomplete: false }`.
251
+ *
252
+ * Not part of {@link PaymentsDriver}: the contract has one verb for taking money, and a
253
+ * driver-specific second half does not fit it. It is public because the alternative is
254
+ * worse — an `APPROVED` payment now reads as `authorized`, and Square voids the
255
+ * authorization on its own when `delay_duration` runs out, so something has to be able
256
+ * to finish the job.
257
+ */
258
+ async completePayment(paymentGatewayId) {
259
+ const data = await this.#request(`/payments/${paymentGatewayId}/complete`, { method: 'POST', body: {} });
260
+ const payment = this.#mapPayment(data.payment);
261
+ publishPaymentDiagnostics(payment);
262
+ return payment;
263
+ }
264
+ // ── Checkout ─────────────────────────────────────────────────────────────────────────
265
+ async createCheckout(input) {
266
+ if (input.cancelUrl !== undefined) {
267
+ throw new Error('[payments] A Square payment link has one redirect (`checkout_options.redirect_url`) and no cancel URL — a buyer who abandons it stays on the link. Drop `cancelUrl` rather than letting it look configured.');
268
+ }
269
+ if (input.trialDays !== undefined) {
270
+ throw new Error('[payments] Square puts a free trial in the subscription plan variation as a free phase, not on the checkout. Remove `trialDays` and point `planId` at a variation whose first phase is free.');
271
+ }
272
+ const currency = (input.currency ?? this.#currency).toUpperCase();
273
+ const referenceId = this.#reference(input.externalReference, 'checkout');
274
+ const body = {
275
+ idempotency_key: this.#idempotencyKey(input.idempotencyKey),
276
+ // The `order` form rather than `quick_pay`, for one reason: a PaymentLink has no
277
+ // reference field of its own, and an Order has `reference_id` plus `metadata`. With
278
+ // `quick_pay` there is nowhere to put `externalReference` at all.
279
+ order: {
280
+ location_id: this.#locationId,
281
+ line_items: [
282
+ {
283
+ name: input.description ?? 'Payment',
284
+ // Square wants the quantity as a string.
285
+ quantity: '1',
286
+ base_price_money: { amount: input.amount, currency },
287
+ },
288
+ ],
289
+ ...(input.customerId !== undefined ? { customer_id: input.customerId } : {}),
290
+ ...(referenceId !== undefined ? { reference_id: referenceId } : {}),
291
+ ...(referenceId !== undefined || input.metadata !== undefined
292
+ ? { metadata: this.#orderMetadata(referenceId, input.metadata) }
293
+ : {}),
294
+ },
295
+ checkout_options: {
296
+ redirect_url: input.successUrl,
297
+ // A plan VARIATION id, not a plan id: Square subscribes buyers to variations.
298
+ ...(input.planId !== undefined ? { subscription_plan_id: input.planId } : {}),
299
+ },
300
+ // Also carried onto the resulting Payment's `note`, which is the one place a
301
+ // reference survives into `payment.created`. See the docs page for why both.
302
+ ...(referenceId !== undefined ? { payment_note: referenceId } : {}),
303
+ };
304
+ const data = await this.#request('/online-checkout/payment-links', { method: 'POST', body });
305
+ return {
306
+ id: data.payment_link.id,
307
+ gatewayId: data.payment_link.id,
308
+ provider: this.provider,
309
+ url: data.payment_link.url,
310
+ // Square has no status on a payment link — it either exists or it does not.
311
+ status: 'open',
312
+ amount: { amount: input.amount, currency: currency.toLowerCase() },
313
+ ...(input.customerId !== undefined ? { customerId: input.customerId } : {}),
314
+ };
315
+ }
316
+ // ── Subscriptions ────────────────────────────────────────────────────────────────────
317
+ async createSubscription(input) {
318
+ if (input.cycle !== undefined) {
319
+ throw new Error('[payments] Square takes the billing cadence from the subscription plan variation, not from the call. Remove `cycle` and point `planId` at a variation with that cadence.');
320
+ }
321
+ if (input.method !== undefined) {
322
+ throw new Error('[payments] Square bills a subscription from a card on file or by emailing an invoice; there is no method to name. Remove `method` and pass `metadata.cardId` for the card-on-file flow.');
323
+ }
324
+ if (input.trialDays !== undefined) {
325
+ throw new Error('[payments] Square expresses a trial as a free phase on the plan variation, not as a number of days on the subscription. Remove `trialDays` and use a variation whose first phase is free.');
326
+ }
327
+ if (input.card !== undefined) {
328
+ throw new Error('[payments] A Web Payments SDK card token is single-use and cannot become a subscription card. Save it first with `POST /v2/cards`, then pass the resulting `card_…` id as `metadata.cardId`.');
329
+ }
330
+ if (input.externalReference !== undefined) {
331
+ throw new Error('[payments] A Square subscription has no reference or metadata field, so `externalReference` would be silently dropped and could never come back on `subscription.updated`. Key your record on `customerId` plus the returned subscription id instead.');
332
+ }
333
+ const body = {
334
+ idempotency_key: this.#idempotencyKey(input.idempotencyKey),
335
+ location_id: this.#locationId,
336
+ plan_variation_id: input.planId,
337
+ customer_id: input.customerId,
338
+ ...(input.startDate !== undefined ? { start_date: this.#startDate(input.startDate) } : {}),
339
+ ...(typeof input.metadata?.cardId === 'string' ? { card_id: input.metadata.cardId } : {}),
340
+ ...(typeof input.metadata?.timezone === 'string'
341
+ ? { timezone: input.metadata.timezone }
342
+ : {}),
343
+ // A real Square field, and the one place the contract's `amount` fits: it overrides
344
+ // the variation's price for a statically-priced plan. Minor units, no conversion.
345
+ ...(input.amount !== undefined
346
+ ? {
347
+ price_override_money: {
348
+ amount: input.amount,
349
+ currency: this.#currency.toUpperCase(),
350
+ },
351
+ }
352
+ : {}),
353
+ };
354
+ const data = await this.#request('/subscriptions', { method: 'POST', body });
355
+ const subscription = this.#mapSubscription(data.subscription, input.customerId);
356
+ publishSubscriptionDiagnostics(subscription, 'subscription.created');
357
+ return subscription;
358
+ }
359
+ async cancelSubscription(subscriptionGatewayId, options) {
360
+ if (options?.atPeriodEnd === false) {
361
+ throw new Error('[payments] Square cannot end a subscription mid-period: `POST /v2/subscriptions/{id}/cancel` sets `canceled_date` to the end of the current billing period and the status stays ACTIVE until then. Call it without `atPeriodEnd: false`, and refund the last invoice if the buyer should not have been billed for the period.');
362
+ }
363
+ const data = await this.#request(`/subscriptions/${subscriptionGatewayId}/cancel`, { method: 'POST', body: {} });
364
+ const subscription = this.#mapSubscription(data.subscription);
365
+ publishSubscriptionDiagnostics(subscription, 'subscription.canceled');
366
+ return subscription;
367
+ }
368
+ async updateSubscription(subscriptionGatewayId, input) {
369
+ if (input.idempotencyKey !== undefined) {
370
+ throw new Error('[payments] Square has no idempotency key on a subscription update: neither `PUT /v2/subscriptions/{id}` nor `POST /v2/subscriptions/{id}/swap-plan` takes an `idempotency_key`, unlike the create call. Accepting one here would turn your retry guarantee into a second plan swap, so it is refused rather than dropped.');
371
+ }
372
+ if (input.amount !== undefined) {
373
+ throw new Error('[payments] Square will not reprice a live subscription: `price_override_money` is set at creation and `UpdateSubscription` documents only `card_id` and `canceled_date` as changeable. Swap to a plan variation at the new price with `metadata: { planVariationId: "…" }`, which is a change Square actually applies.');
374
+ }
375
+ if (input.description !== undefined) {
376
+ throw new Error('[payments] A Square subscription has no description field. Keep it on your own record.');
377
+ }
378
+ const planVariationId = input.metadata?.planVariationId;
379
+ const cardId = input.metadata?.cardId;
380
+ if (typeof planVariationId !== 'string' && typeof cardId !== 'string') {
381
+ throw new Error('[payments] Nothing to update on a Square subscription. It accepts a plan swap (`metadata: { planVariationId }`) or a new card on file (`metadata: { cardId }`); anything else would be a change the gateway never sees.');
382
+ }
383
+ if (typeof planVariationId === 'string') {
384
+ const swapped = await this.#request(`/subscriptions/${subscriptionGatewayId}/swap-plan`, { method: 'POST', body: { new_plan_variation_id: planVariationId } });
385
+ return this.#mapSubscription(swapped.subscription);
386
+ }
387
+ const data = await this.#request(`/subscriptions/${subscriptionGatewayId}`, { method: 'PUT', body: { subscription: { card_id: cardId } } });
388
+ return this.#mapSubscription(data.subscription);
389
+ }
390
+ async findSubscription(gatewayId) {
391
+ try {
392
+ const data = await this.#request(`/subscriptions/${gatewayId}`);
393
+ return this.#mapSubscription(data.subscription);
394
+ }
395
+ catch (error) {
396
+ if (isNotFound(error))
397
+ return null;
398
+ throw error;
399
+ }
400
+ }
401
+ // ── Invoices ─────────────────────────────────────────────────────────────────────────
402
+ async listInvoices(customerId) {
403
+ // `GET /v2/invoices` filters by location only; the search endpoint is the one that
404
+ // takes a customer. Square documents the filter as accepting one location and one
405
+ // customer, which is exactly this call.
406
+ const data = await this.#request('/invoices/search', {
407
+ method: 'POST',
408
+ body: {
409
+ query: { filter: { location_ids: [this.#locationId], customer_ids: [customerId] } },
410
+ limit: 100,
411
+ },
412
+ });
413
+ return (data.invoices ?? []).map((invoice) => this.#mapInvoice(invoice));
414
+ }
415
+ // ── Webhooks ─────────────────────────────────────────────────────────────────────────
416
+ /**
417
+ * Whether a delivery to `POST /payments/webhook/:provider` can be authenticated.
418
+ *
419
+ * Square signs `notificationUrl + body`, so BOTH are needed — a signature key with no URL
420
+ * verifies nothing, which is why the check below tests for both.
421
+ */
422
+ get webhookVerification() {
423
+ return this.#webhookSignatureKey !== undefined && this.#notificationUrl !== undefined
424
+ ? 'configured'
425
+ : 'unconfigured';
426
+ }
427
+ parseWebhook(rawBody, headers) {
428
+ if (this.#webhookSignatureKey !== undefined && this.#notificationUrl !== undefined) {
429
+ const signature = headerValue(headers, 'x-square-hmacsha256-signature');
430
+ if (signature === undefined || signature === '') {
431
+ throw new Error('[payments] Missing x-square-hmacsha256-signature on Square webhook request.');
432
+ }
433
+ // The notification URL is prepended to the raw body BEFORE hashing — Square signs
434
+ // `url + body`, so a signature lifted from another endpoint of the same account does
435
+ // not verify here.
436
+ const signed = `${this.#notificationUrl}${rawBody}`;
437
+ if (!verifyHmacOverPayload(signed, signature, this.#webhookSignatureKey, 'sha256', 'base64')) {
438
+ throw new Error('[payments] Invalid Square webhook signature.');
439
+ }
440
+ }
441
+ const payload = JSON.parse(rawBody);
442
+ return {
443
+ id: payload.event_id ?? `${payload.type}-${payload.data?.id ?? ''}`,
444
+ provider: this.provider,
445
+ type: this.#mapWebhookType(payload),
446
+ createdAt: payload.created_at ?? new Date().toISOString(),
447
+ data: this.#mapWebhookData(payload),
448
+ raw: payload,
449
+ };
450
+ }
451
+ // ── Mappers ──────────────────────────────────────────────────────────────────────────
452
+ #mapCustomer(data) {
453
+ const name = [data.given_name, data.family_name].filter(Boolean).join(' ');
454
+ return {
455
+ id: data.id,
456
+ ...(name !== '' ? { name } : {}),
457
+ ...(data.email_address ? { email: data.email_address } : {}),
458
+ ...(data.tax_ids?.eu_vat ? { taxId: data.tax_ids.eu_vat } : {}),
459
+ };
460
+ }
461
+ #mapPayment(data) {
462
+ const result = {
463
+ id: data.id,
464
+ gatewayId: data.id,
465
+ provider: this.provider,
466
+ amount: {
467
+ amount: data.amount_money?.amount ?? 0,
468
+ currency: (data.amount_money?.currency ?? this.#currency).toLowerCase(),
469
+ },
470
+ status: this.#mapPaymentStatus(data),
471
+ payload: data,
472
+ createdAt: data.created_at ?? new Date().toISOString(),
473
+ };
474
+ const method = this.#mapMethodToType(data);
475
+ if (method !== undefined)
476
+ result.method = method;
477
+ if (data.customer_id)
478
+ result.customerId = data.customer_id;
479
+ const paidAt = data.updated_at ?? data.created_at;
480
+ if (data.status === 'COMPLETED' && paidAt !== undefined)
481
+ result.paidAt = paidAt;
482
+ if (data.receipt_url)
483
+ result.hostedUrl = data.receipt_url;
484
+ return result;
485
+ }
486
+ /**
487
+ * `APPROVED` means Square is holding the funds on the buyer's card and nothing has moved
488
+ * to the seller; the authorization expires on its own if `CompletePayment` never runs.
489
+ * That is exactly `authorized` — it used to collapse into `pending`, which reads like a
490
+ * payment nobody has attempted rather than one whose money is reserved and ticking.
491
+ * `PENDING` stays `pending`: Square has not approved anything yet.
492
+ *
493
+ * A refunded payment stays `COMPLETED` at Square with a non-zero `refunded_money`, so
494
+ * that is read separately — otherwise a full refund would keep reporting as `paid`.
495
+ */
496
+ #mapPaymentStatus(data) {
497
+ const refunded = data.refunded_money?.amount ?? 0;
498
+ const total = data.amount_money?.amount ?? 0;
499
+ if (refunded > 0 && refunded >= total)
500
+ return 'refunded';
501
+ switch (data.status) {
502
+ case 'COMPLETED':
503
+ return 'paid';
504
+ case 'APPROVED':
505
+ return 'authorized';
506
+ case 'FAILED':
507
+ return 'failed';
508
+ case 'CANCELED':
509
+ return 'canceled';
510
+ default:
511
+ return 'pending';
512
+ }
513
+ }
514
+ /**
515
+ * Square's `source_type` → the canonical {@link PaymentMethodType}.
516
+ *
517
+ * All of these now have a name: a Cash App payment is a `wallet`, an ACH one is a
518
+ * `bank_debit` (pulled from the buyer's account), Afterpay is `bnpl`. They used to come
519
+ * back unset, which is indistinguishable from "Square did not say" — and the Square
520
+ * balance (`SQUARE_ACCOUNT`) is a stored balance, which is what `wallet` means.
521
+ *
522
+ * `CASH` and `EXTERNAL` stay unset on purpose: they are money recorded as taken outside
523
+ * Square altogether, and no member of the union describes that.
524
+ */
525
+ #mapMethodToType(data) {
526
+ switch (data.source_type) {
527
+ case 'CARD':
528
+ return data.card_details?.card?.card_type === 'DEBIT' ? 'debit_card' : 'card';
529
+ case 'WALLET':
530
+ case 'SQUARE_ACCOUNT':
531
+ return 'wallet';
532
+ case 'BANK_ACCOUNT':
533
+ return 'bank_debit';
534
+ case 'BUY_NOW_PAY_LATER':
535
+ return 'bnpl';
536
+ default:
537
+ return undefined;
538
+ }
539
+ }
540
+ #mapSubscription(data, customerId) {
541
+ const statusMap = {
542
+ PENDING: 'incomplete',
543
+ ACTIVE: 'active',
544
+ // A paused Square subscription bills nothing and will bill again when it resumes.
545
+ // `past_due` said the buyer owed money they did not; the subscriber is entitled to
546
+ // nothing either way, which is the part that must not change.
547
+ PAUSED: 'paused',
548
+ CANCELED: 'canceled',
549
+ DEACTIVATED: 'canceled',
550
+ COMPLETED: 'ended',
551
+ };
552
+ const result = {
553
+ id: data.id,
554
+ gatewayId: data.id,
555
+ provider: this.provider,
556
+ customerId: data.customer_id ?? customerId ?? '',
557
+ status: (data.status !== undefined ? statusMap[data.status] : undefined) ?? 'active',
558
+ planId: data.plan_variation_id ?? '',
559
+ payload: data,
560
+ createdAt: data.created_at ?? new Date().toISOString(),
561
+ };
562
+ if (data.price_override_money?.amount !== undefined) {
563
+ result.amount = {
564
+ amount: data.price_override_money.amount,
565
+ currency: (data.price_override_money.currency ?? this.#currency).toLowerCase(),
566
+ };
567
+ }
568
+ // Square's dates are `YYYY-MM-DD`, not timestamps; they are widened to UTC midnight so
569
+ // the whole package keeps holding ISO instants.
570
+ const startsAt = this.#toIso(data.start_date);
571
+ const endsAt = this.#toIso(data.canceled_date);
572
+ const chargedThrough = this.#toIso(data.charged_through_date);
573
+ if (startsAt !== undefined)
574
+ result.currentPeriodStart = startsAt;
575
+ if (chargedThrough !== undefined)
576
+ result.currentPeriodEnd = chargedThrough;
577
+ if (endsAt !== undefined)
578
+ result.endsAt = endsAt;
579
+ return result;
580
+ }
581
+ #mapInvoice(data) {
582
+ const statusMap = {
583
+ DRAFT: 'draft',
584
+ UNPAID: 'open',
585
+ SCHEDULED: 'open',
586
+ PARTIALLY_PAID: 'open',
587
+ PAID: 'paid',
588
+ PARTIALLY_REFUNDED: 'paid',
589
+ REFUNDED: 'paid',
590
+ CANCELED: 'canceled',
591
+ FAILED: 'failed',
592
+ PAYMENT_PENDING: 'pending',
593
+ };
594
+ const amount = data.payment_requests?.[0]?.computed_amount_money?.amount ??
595
+ data.next_payment_amount_money?.amount ??
596
+ 0;
597
+ const currency = data.payment_requests?.[0]?.computed_amount_money?.currency ??
598
+ data.next_payment_amount_money?.currency ??
599
+ this.#currency;
600
+ return {
601
+ id: data.id,
602
+ gatewayId: data.id,
603
+ provider: this.provider,
604
+ ...(data.primary_recipient?.customer_id
605
+ ? { customerId: data.primary_recipient.customer_id }
606
+ : {}),
607
+ ...(data.subscription_id ? { subscriptionId: data.subscription_id } : {}),
608
+ status: (data.status !== undefined ? statusMap[data.status] : undefined) ?? 'draft',
609
+ amount: { amount, currency: currency.toLowerCase() },
610
+ createdAt: data.created_at ?? new Date().toISOString(),
611
+ // `public_url` is Square's hosted invoice page, not a PDF; it is the closest thing
612
+ // the API offers and the field a reader will go looking in.
613
+ ...(data.public_url ? { hostedPdfUrl: data.public_url } : {}),
614
+ ...(data.invoice_number ? { number: data.invoice_number } : {}),
615
+ payload: data,
616
+ };
617
+ }
618
+ #mapWebhookType(payload) {
619
+ const object = payload.data?.object;
620
+ switch (payload.type) {
621
+ case 'payment.created':
622
+ case 'payment.updated': {
623
+ const payment = object?.payment;
624
+ if (payment === undefined)
625
+ return 'payment.updated';
626
+ const status = this.#mapPaymentStatus(payment);
627
+ if (status === 'paid')
628
+ return 'payment.succeeded';
629
+ if (status === 'refunded')
630
+ return 'payment.refunded';
631
+ if (status === 'failed' || status === 'canceled')
632
+ return 'payment.failed';
633
+ return 'payment.updated';
634
+ }
635
+ case 'refund.created':
636
+ case 'refund.updated':
637
+ // Only a COMPLETED refund means money left; `PENDING` and `REJECTED` are not a
638
+ // refund the ledger should record as one.
639
+ return object?.refund?.status === 'COMPLETED' ? 'payment.refunded' : 'payment.updated';
640
+ case 'invoice.payment_made':
641
+ return 'payment.succeeded';
642
+ // ── The dispute family ───────────────────────────────────────────────────────────
643
+ // Square answers the money question outright for a chargeback: when the bank notifies
644
+ // it of one, "Square withholds the disputed funds from the seller's Square account
645
+ // balance until the bank issues a final resolution on the case. If there are
646
+ // insufficient funds in the Square account balance, the funds are removed (debited)
647
+ // from the seller's most recently linked bank account." So `dispute.created` is
648
+ // `payment.disputed` — unless the state says this is an inquiry, see
649
+ // {@link SquareDriver.#disputeType}. `dispute.state.updated` carries the whole Dispute
650
+ // object, so it is read the same way and is where the resolution arrives.
651
+ case 'dispute.created':
652
+ case 'dispute.state.updated':
653
+ // The deprecated spelling of `dispute.state.updated`; an app still subscribed to it
654
+ // should not silently miss the resolution.
655
+ case 'dispute.state.changed':
656
+ return this.#disputeType(object?.dispute, payload.type);
657
+ // Evidence going in and out is paperwork inside an open dispute, not a resolution of
658
+ // it. `.created`/`.deleted` are the current names, `.added`/`.removed` the deprecated
659
+ // ones Square still publishes.
660
+ case 'dispute.evidence.added':
661
+ case 'dispute.evidence.created':
662
+ case 'dispute.evidence.deleted':
663
+ case 'dispute.evidence.removed':
664
+ return 'payment.updated';
665
+ case 'subscription.created':
666
+ return 'subscription.created';
667
+ case 'subscription.updated': {
668
+ const status = object?.subscription?.status;
669
+ return status === 'CANCELED' || status === 'DEACTIVATED'
670
+ ? 'subscription.canceled'
671
+ : 'subscription.updated';
672
+ }
673
+ default:
674
+ return payload.type;
675
+ }
676
+ }
677
+ /**
678
+ * A Dispute's `state` → the canonical event type.
679
+ *
680
+ * Square's `DisputeState` enum has eight values and splits cleanly in two. Four of them
681
+ * are a real chargeback — `EVIDENCE_REQUIRED` ("the initial state of a dispute with
682
+ * evidence required"), `PROCESSING` ("dispute evidence has been submitted and the bank is
683
+ * processing the dispute"), `WON` and `LOST` — and three more are prefixed `INQUIRY_`,
684
+ * which Square's own enum descriptions call "an inquiry" rather than a dispute.
685
+ *
686
+ * The inquiry states become `payment.dispute_warning`. **Square's reference does not say
687
+ * in words whether an inquiry withholds funds**: the "Square withholds the disputed funds"
688
+ * sentence is written about a cardholder requesting a charge reversal, and the support
689
+ * article on information requests says nothing about money at all. What the reference does
690
+ * do is name these states inquiries and keep them out of the dispute states, which is the
691
+ * same distinction Stripe draws with its `warning_*` statuses and the networks draw
692
+ * between a retrieval request and a chargeback. Reporting an inquiry as a warning writes
693
+ * nothing to the ledger; reporting it as `payment.disputed` moves a paid row.
694
+ *
695
+ * `INQUIRY_CLOSED` is "the inquiry is complete" and names no winner, so it stays a
696
+ * `payment.updated` — the processor throws on a close with no outcome precisely so a
697
+ * driver that cannot read one emits an update instead.
698
+ *
699
+ * `ACCEPTED` is a **loss**: `AcceptDispute` is documented as "Square returns the disputed
700
+ * amount to the cardholder and updates the dispute state to `ACCEPTED`. The dispute is now
701
+ * closed." The seller accepted liability and the money is gone, exactly as with Adyen's
702
+ * `Accepted` disputeStatus.
703
+ *
704
+ * With no state at all — Square marks the field nullable — `dispute.created` keeps the
705
+ * `payment.disputed` it has always had, and a state change with nothing to change stays an
706
+ * update.
707
+ */
708
+ #disputeType(dispute, eventType) {
709
+ switch (dispute?.state) {
710
+ case 'INQUIRY_EVIDENCE_REQUIRED':
711
+ case 'INQUIRY_PROCESSING':
712
+ return 'payment.dispute_warning';
713
+ case 'EVIDENCE_REQUIRED':
714
+ case 'PROCESSING':
715
+ return 'payment.disputed';
716
+ case 'WON':
717
+ case 'LOST':
718
+ case 'ACCEPTED':
719
+ return 'payment.dispute_closed';
720
+ default:
721
+ return eventType === 'dispute.created' ? 'payment.disputed' : 'payment.updated';
722
+ }
723
+ }
724
+ /** The three terminal states, and only those. `INQUIRY_CLOSED` names no winner. */
725
+ #disputeOutcome(state) {
726
+ if (state === 'WON')
727
+ return 'won';
728
+ if (state === 'LOST' || state === 'ACCEPTED')
729
+ return 'lost';
730
+ return undefined;
731
+ }
732
+ #mapWebhookData(payload) {
733
+ const object = payload.data?.object;
734
+ const subscription = object?.subscription;
735
+ if (subscription !== undefined) {
736
+ const mapped = this.#mapSubscription(subscription);
737
+ return {
738
+ gatewayId: mapped.gatewayId,
739
+ customerId: mapped.customerId,
740
+ status: mapped.status,
741
+ planId: mapped.planId,
742
+ ...(mapped.endsAt !== undefined ? { endsAt: mapped.endsAt } : {}),
743
+ };
744
+ }
745
+ const payment = object?.payment;
746
+ if (payment !== undefined) {
747
+ const mapped = this.#mapPayment(payment);
748
+ const externalReference = this.#externalReferenceOf(payment);
749
+ return {
750
+ gatewayId: mapped.gatewayId,
751
+ amount: mapped.amount.amount,
752
+ currency: mapped.amount.currency,
753
+ status: mapped.status,
754
+ ...(mapped.customerId !== undefined ? { customerId: mapped.customerId } : {}),
755
+ ...(payment.order_id ? { orderId: payment.order_id } : {}),
756
+ ...(externalReference !== undefined ? { externalReference } : {}),
757
+ };
758
+ }
759
+ const dispute = object?.dispute;
760
+ if (dispute !== undefined) {
761
+ // Keyed on the DISPUTED PAYMENT, not on the dispute: the ledger row that has to stop
762
+ // saying `paid` is the payment's. `amount_money` is the disputed amount, which for a
763
+ // partial dispute is less than the payment — `event.raw` carries the reason, the
764
+ // state and the evidence deadline.
765
+ const outcome = this.#disputeOutcome(dispute.state);
766
+ return {
767
+ gatewayId: dispute.disputed_payment?.payment_id ?? '',
768
+ amount: dispute.amount_money?.amount ?? 0,
769
+ currency: (dispute.amount_money?.currency ?? this.#currency).toLowerCase(),
770
+ ...(dispute.id !== undefined ? { disputeId: dispute.id } : {}),
771
+ ...(dispute.reason !== undefined ? { reason: dispute.reason } : {}),
772
+ // "The deadline by which the seller must respond to the dispute", already RFC 3339
773
+ // — and if it passes with no action Square automatically challenges on the seller's
774
+ // behalf, which is not the same as the seller having decided anything.
775
+ ...(dispute.due_at !== undefined ? { actionableUntil: dispute.due_at } : {}),
776
+ ...(dispute.state !== undefined ? { disputeState: dispute.state } : {}),
777
+ ...(outcome !== undefined ? { outcome } : {}),
778
+ };
779
+ }
780
+ const refund = object?.refund;
781
+ if (refund !== undefined) {
782
+ return {
783
+ // The ledger keys refunds on the PAYMENT, which is the row the amount came off.
784
+ gatewayId: refund.payment_id ?? refund.id,
785
+ amount: refund.amount_money?.amount ?? 0,
786
+ currency: (refund.amount_money?.currency ?? this.#currency).toLowerCase(),
787
+ refundId: refund.id,
788
+ };
789
+ }
790
+ const invoice = object?.invoice;
791
+ if (invoice !== undefined) {
792
+ const mapped = this.#mapInvoice(invoice);
793
+ return {
794
+ gatewayId: mapped.gatewayId,
795
+ amount: mapped.amount.amount,
796
+ currency: mapped.amount.currency,
797
+ ...(mapped.customerId !== undefined ? { customerId: mapped.customerId } : {}),
798
+ ...(mapped.subscriptionId !== undefined ? { subscriptionId: mapped.subscriptionId } : {}),
799
+ };
800
+ }
801
+ const order = object?.order;
802
+ if (order?.id !== undefined) {
803
+ return {
804
+ gatewayId: order.id,
805
+ amount: order.total_money?.amount ?? 0,
806
+ currency: (order.total_money?.currency ?? this.#currency).toLowerCase(),
807
+ ...(order.customer_id ? { customerId: order.customer_id } : {}),
808
+ ...(this.#orderReference(order) !== undefined
809
+ ? { externalReference: this.#orderReference(order) }
810
+ : {}),
811
+ };
812
+ }
813
+ return {};
814
+ }
815
+ // ── Helpers ──────────────────────────────────────────────────────────────────────────
816
+ /**
817
+ * Where a Square payment carries the app's own reference.
818
+ *
819
+ * `reference_id` is what {@link SquareDriver.charge} sets, and it comes back on the
820
+ * payment. A payment made through a hosted link is the awkward case: the reference lives
821
+ * on the ORDER, and Square's published `payment.created`/`payment.updated` examples do
822
+ * not show `reference_id` on the payment — which is why `createCheckout` also sends
823
+ * `payment_note`, read here as a fallback.
824
+ */
825
+ #externalReferenceOf(payment) {
826
+ return payment.reference_id ?? payment.note ?? undefined;
827
+ }
828
+ #orderReference(order) {
829
+ return order.reference_id ?? order.metadata?.external_reference ?? undefined;
830
+ }
831
+ /** Order metadata keys are `[a-zA-Z0-9_-]`, values 255 chars, ten entries at most. */
832
+ #orderMetadata(externalReference, extra) {
833
+ const metadata = {};
834
+ for (const [key, value] of Object.entries(extra ?? {})) {
835
+ if (value === undefined || value === null)
836
+ continue;
837
+ metadata[key.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 60)] = String(value).slice(0, 255);
838
+ }
839
+ if (externalReference !== undefined)
840
+ metadata.external_reference = externalReference;
841
+ return metadata;
842
+ }
843
+ #reference(externalReference, where) {
844
+ if (externalReference === undefined)
845
+ return undefined;
846
+ if (externalReference.length > REFERENCE_MAX_LENGTH) {
847
+ throw new Error(`[payments] Square carries \`externalReference\` as \`reference_id\`, capped at ${REFERENCE_MAX_LENGTH} characters; the ${where} got ${externalReference.length}.`);
848
+ }
849
+ return externalReference;
850
+ }
851
+ #idempotencyKey(key) {
852
+ if (key === undefined)
853
+ return randomUUID();
854
+ if (key.length > IDEMPOTENCY_MAX_LENGTH) {
855
+ throw new Error(`[payments] Square caps \`idempotency_key\` at ${IDEMPOTENCY_MAX_LENGTH} characters; got ${key.length}.`);
856
+ }
857
+ return key;
858
+ }
859
+ /** Square's tax id field is EU VAT and nothing else, so an arbitrary tax id is refused. */
860
+ #taxIds(taxId, metadata) {
861
+ if (taxId === undefined)
862
+ return {};
863
+ if (metadata?.taxIdType !== 'eu_vat') {
864
+ throw new Error('[payments] A Square customer has exactly one tax field, `tax_ids.eu_vat`, and no general-purpose one. Pass `metadata: { taxIdType: "eu_vat" }` if that is what `taxId` is; otherwise keep the tax id on your own record rather than having it silently dropped here.');
865
+ }
866
+ return { tax_ids: { eu_vat: taxId } };
867
+ }
868
+ /** Square subscription dates are `YYYY-MM-DD`; the contract wants an ISO instant. */
869
+ #startDate(value) {
870
+ const date = new Date(value);
871
+ if (Number.isNaN(date.getTime())) {
872
+ throw new Error(`[payments] Square: \`startDate\` "${value}" is not a date.`);
873
+ }
874
+ return date.toISOString().slice(0, 10);
875
+ }
876
+ #toIso(value) {
877
+ if (value === undefined || value === '')
878
+ return undefined;
879
+ const date = new Date(`${value}T00:00:00.000Z`);
880
+ if (Number.isNaN(date.getTime()))
881
+ return undefined;
882
+ return date.toISOString();
883
+ }
884
+ async #paymentAmount(paymentGatewayId) {
885
+ const data = await this.#request(`/payments/${paymentGatewayId}`);
886
+ const amount = data.payment.amount_money?.amount;
887
+ if (amount === undefined) {
888
+ throw new Error(`[payments] Square payment "${paymentGatewayId}" has no amount to refund. \`RefundPayment\` requires \`amount_money\`, so pass the amount explicitly.`);
889
+ }
890
+ return {
891
+ amount,
892
+ currency: (data.payment.amount_money?.currency ?? this.#currency).toUpperCase(),
893
+ };
894
+ }
895
+ async #request(path, options = {}) {
896
+ return httpRequest(path, {
897
+ baseUrl: this.#baseUrl,
898
+ ...(options.method !== undefined ? { method: options.method } : {}),
899
+ ...(options.body !== undefined ? { body: options.body } : {}),
900
+ bearerToken: this.#accessToken,
901
+ headers: { 'Square-Version': this.#apiVersion },
902
+ });
903
+ }
904
+ }
905
+ //# sourceMappingURL=square.js.map