@adonis-agora/payments 0.2.0 → 0.3.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 (319) hide show
  1. package/CHANGELOG.md +2347 -0
  2. package/dist/assets/spa/assets/index-CT5Zon6P.css +1 -0
  3. package/dist/assets/spa/assets/index-CvEbJEnq.js +41 -0
  4. package/dist/assets/spa/assets/index-CvEbJEnq.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/payments_health.d.ts +31 -0
  11. package/dist/commands/payments_health.d.ts.map +1 -0
  12. package/dist/commands/payments_health.js +113 -0
  13. package/dist/commands/payments_health.js.map +1 -0
  14. package/dist/commands/payments_sync.d.ts +18 -4
  15. package/dist/commands/payments_sync.d.ts.map +1 -1
  16. package/dist/commands/payments_sync.js +136 -40
  17. package/dist/commands/payments_sync.js.map +1 -1
  18. package/dist/configure.d.ts +12 -5
  19. package/dist/configure.d.ts.map +1 -1
  20. package/dist/configure.js +18 -5
  21. package/dist/configure.js.map +1 -1
  22. package/dist/providers/dashboard_provider.d.ts +97 -0
  23. package/dist/providers/dashboard_provider.d.ts.map +1 -0
  24. package/dist/providers/dashboard_provider.js +430 -0
  25. package/dist/providers/dashboard_provider.js.map +1 -0
  26. package/dist/providers/payments_client_provider.d.ts +41 -0
  27. package/dist/providers/payments_client_provider.d.ts.map +1 -0
  28. package/dist/providers/payments_client_provider.js +95 -0
  29. package/dist/providers/payments_client_provider.js.map +1 -0
  30. package/dist/providers/payments_provider.d.ts +35 -0
  31. package/dist/providers/payments_provider.d.ts.map +1 -1
  32. package/dist/providers/payments_provider.js +250 -36
  33. package/dist/providers/payments_provider.js.map +1 -1
  34. package/dist/src/billing/billing_health.d.ts +85 -0
  35. package/dist/src/billing/billing_health.d.ts.map +1 -0
  36. package/dist/src/billing/billing_health.js +155 -0
  37. package/dist/src/billing/billing_health.js.map +1 -0
  38. package/dist/src/billing/billing_overview.d.ts +4 -3
  39. package/dist/src/billing/billing_overview.d.ts.map +1 -1
  40. package/dist/src/billing/billing_overview.js +14 -5
  41. package/dist/src/billing/billing_overview.js.map +1 -1
  42. package/dist/src/billing/billing_store.d.ts +634 -5
  43. package/dist/src/billing/billing_store.d.ts.map +1 -1
  44. package/dist/src/billing/billing_store.js +18 -1
  45. package/dist/src/billing/billing_store.js.map +1 -1
  46. package/dist/src/billing/index.d.ts +2 -1
  47. package/dist/src/billing/index.d.ts.map +1 -1
  48. package/dist/src/billing/index.js +1 -0
  49. package/dist/src/billing/index.js.map +1 -1
  50. package/dist/src/billing/list_query.d.ts +16 -0
  51. package/dist/src/billing/list_query.d.ts.map +1 -0
  52. package/dist/src/billing/list_query.js +28 -0
  53. package/dist/src/billing/list_query.js.map +1 -0
  54. package/dist/src/billing/lucid_billing_store.d.ts +122 -4
  55. package/dist/src/billing/lucid_billing_store.d.ts.map +1 -1
  56. package/dist/src/billing/lucid_billing_store.js +811 -13
  57. package/dist/src/billing/lucid_billing_store.js.map +1 -1
  58. package/dist/src/billing/mixins/index.d.ts +3 -0
  59. package/dist/src/billing/mixins/index.d.ts.map +1 -1
  60. package/dist/src/billing/mixins/index.js +3 -0
  61. package/dist/src/billing/mixins/index.js.map +1 -1
  62. package/dist/src/billing/mixins/with_audit_event.d.ts +48 -0
  63. package/dist/src/billing/mixins/with_audit_event.d.ts.map +1 -0
  64. package/dist/src/billing/mixins/with_audit_event.js +77 -0
  65. package/dist/src/billing/mixins/with_audit_event.js.map +1 -0
  66. package/dist/src/billing/mixins/with_customer.d.ts +39 -0
  67. package/dist/src/billing/mixins/with_customer.d.ts.map +1 -0
  68. package/dist/src/billing/mixins/with_customer.js +68 -0
  69. package/dist/src/billing/mixins/with_customer.js.map +1 -0
  70. package/dist/src/billing/mixins/with_dispute.d.ts +68 -0
  71. package/dist/src/billing/mixins/with_dispute.d.ts.map +1 -0
  72. package/dist/src/billing/mixins/with_dispute.js +83 -0
  73. package/dist/src/billing/mixins/with_dispute.js.map +1 -0
  74. package/dist/src/billing/mixins/with_payment.d.ts +42 -0
  75. package/dist/src/billing/mixins/with_payment.d.ts.map +1 -1
  76. package/dist/src/billing/mixins/with_payment.js +29 -3
  77. package/dist/src/billing/mixins/with_payment.js.map +1 -1
  78. package/dist/src/billing/mixins/with_subscription.d.ts +7 -0
  79. package/dist/src/billing/mixins/with_subscription.d.ts.map +1 -1
  80. package/dist/src/billing/mixins/with_subscription.js +15 -1
  81. package/dist/src/billing/mixins/with_subscription.js.map +1 -1
  82. package/dist/src/billing/mixins/with_usage_event.d.ts +7 -0
  83. package/dist/src/billing/mixins/with_usage_event.d.ts.map +1 -1
  84. package/dist/src/billing/mixins/with_usage_event.js +15 -1
  85. package/dist/src/billing/mixins/with_usage_event.js.map +1 -1
  86. package/dist/src/billing/mixins/with_webhook_event.d.ts +18 -0
  87. package/dist/src/billing/mixins/with_webhook_event.d.ts.map +1 -1
  88. package/dist/src/billing/mixins/with_webhook_event.js +18 -1
  89. package/dist/src/billing/mixins/with_webhook_event.js.map +1 -1
  90. package/dist/src/billing/resolve_dispatch.d.ts +22 -0
  91. package/dist/src/billing/resolve_dispatch.d.ts.map +1 -0
  92. package/dist/src/billing/resolve_dispatch.js +44 -0
  93. package/dist/src/billing/resolve_dispatch.js.map +1 -0
  94. package/dist/src/billing/resolve_store.d.ts +18 -0
  95. package/dist/src/billing/resolve_store.d.ts.map +1 -0
  96. package/dist/src/billing/resolve_store.js +35 -0
  97. package/dist/src/billing/resolve_store.js.map +1 -0
  98. package/dist/src/billing/schema.d.ts +108 -0
  99. package/dist/src/billing/schema.d.ts.map +1 -0
  100. package/dist/src/billing/schema.js +350 -0
  101. package/dist/src/billing/schema.js.map +1 -0
  102. package/dist/src/billing/webhook_dispatcher.d.ts +95 -16
  103. package/dist/src/billing/webhook_dispatcher.d.ts.map +1 -1
  104. package/dist/src/billing/webhook_dispatcher.js +186 -33
  105. package/dist/src/billing/webhook_dispatcher.js.map +1 -1
  106. package/dist/src/billing/webhook_events.d.ts +67 -1
  107. package/dist/src/billing/webhook_events.d.ts.map +1 -1
  108. package/dist/src/billing/webhook_events.js +15 -0
  109. package/dist/src/billing/webhook_events.js.map +1 -1
  110. package/dist/src/billing/webhook_processor.d.ts.map +1 -1
  111. package/dist/src/billing/webhook_processor.js +325 -5
  112. package/dist/src/billing/webhook_processor.js.map +1 -1
  113. package/dist/src/client/define_config.d.ts +150 -0
  114. package/dist/src/client/define_config.d.ts.map +1 -0
  115. package/dist/src/client/define_config.js +98 -0
  116. package/dist/src/client/define_config.js.map +1 -0
  117. package/dist/src/client/handlers.d.ts +71 -0
  118. package/dist/src/client/handlers.d.ts.map +1 -0
  119. package/dist/src/client/handlers.js +119 -0
  120. package/dist/src/client/handlers.js.map +1 -0
  121. package/dist/src/client/index.d.ts +16 -0
  122. package/dist/src/client/index.d.ts.map +1 -0
  123. package/dist/src/client/index.js +13 -0
  124. package/dist/src/client/index.js.map +1 -0
  125. package/dist/src/client/statuses.d.ts +38 -0
  126. package/dist/src/client/statuses.d.ts.map +1 -0
  127. package/dist/src/client/statuses.js +42 -0
  128. package/dist/src/client/statuses.js.map +1 -0
  129. package/dist/src/dashboard/actions.d.ts +143 -0
  130. package/dist/src/dashboard/actions.d.ts.map +1 -0
  131. package/dist/src/dashboard/actions.js +112 -0
  132. package/dist/src/dashboard/actions.js.map +1 -0
  133. package/dist/src/dashboard/auth.d.ts +136 -0
  134. package/dist/src/dashboard/auth.d.ts.map +1 -0
  135. package/dist/src/dashboard/auth.js +152 -0
  136. package/dist/src/dashboard/auth.js.map +1 -0
  137. package/dist/src/dashboard/define_config.d.ts +80 -0
  138. package/dist/src/dashboard/define_config.d.ts.map +1 -0
  139. package/dist/src/dashboard/define_config.js +83 -0
  140. package/dist/src/dashboard/define_config.js.map +1 -0
  141. package/dist/src/dashboard/handlers.d.ts +281 -0
  142. package/dist/src/dashboard/handlers.d.ts.map +1 -0
  143. package/dist/src/dashboard/handlers.js +960 -0
  144. package/dist/src/dashboard/handlers.js.map +1 -0
  145. package/dist/src/dashboard/index.d.ts +18 -0
  146. package/dist/src/dashboard/index.d.ts.map +1 -0
  147. package/dist/src/dashboard/index.js +14 -0
  148. package/dist/src/dashboard/index.js.map +1 -0
  149. package/dist/src/dashboard/login_page.d.ts +23 -0
  150. package/dist/src/dashboard/login_page.d.ts.map +1 -0
  151. package/dist/src/dashboard/login_page.js +170 -0
  152. package/dist/src/dashboard/login_page.js.map +1 -0
  153. package/dist/src/dashboard/period.d.ts +31 -0
  154. package/dist/src/dashboard/period.d.ts.map +1 -0
  155. package/dist/src/dashboard/period.js +44 -0
  156. package/dist/src/dashboard/period.js.map +1 -0
  157. package/dist/src/dashboard/session_cookie.d.ts +42 -0
  158. package/dist/src/dashboard/session_cookie.d.ts.map +1 -0
  159. package/dist/src/dashboard/session_cookie.js +97 -0
  160. package/dist/src/dashboard/session_cookie.js.map +1 -0
  161. package/dist/src/dashboard/spa.d.ts +24 -0
  162. package/dist/src/dashboard/spa.d.ts.map +1 -0
  163. package/dist/src/dashboard/spa.js +52 -0
  164. package/dist/src/dashboard/spa.js.map +1 -0
  165. package/dist/src/define_config.d.ts +244 -9
  166. package/dist/src/define_config.d.ts.map +1 -1
  167. package/dist/src/define_config.js +97 -1
  168. package/dist/src/define_config.js.map +1 -1
  169. package/dist/src/diagnostics.d.ts +287 -7
  170. package/dist/src/diagnostics.d.ts.map +1 -1
  171. package/dist/src/diagnostics.js +317 -1
  172. package/dist/src/diagnostics.js.map +1 -1
  173. package/dist/src/driver.d.ts +139 -5
  174. package/dist/src/driver.d.ts.map +1 -1
  175. package/dist/src/drivers/abacate.d.ts +12 -2
  176. package/dist/src/drivers/abacate.d.ts.map +1 -1
  177. package/dist/src/drivers/abacate.js +112 -19
  178. package/dist/src/drivers/abacate.js.map +1 -1
  179. package/dist/src/drivers/adyen.d.ts +164 -0
  180. package/dist/src/drivers/adyen.d.ts.map +1 -0
  181. package/dist/src/drivers/adyen.js +678 -0
  182. package/dist/src/drivers/adyen.js.map +1 -0
  183. package/dist/src/drivers/asaas.d.ts +52 -2
  184. package/dist/src/drivers/asaas.d.ts.map +1 -1
  185. package/dist/src/drivers/asaas.js +364 -29
  186. package/dist/src/drivers/asaas.js.map +1 -1
  187. package/dist/src/drivers/dodo.d.ts +135 -0
  188. package/dist/src/drivers/dodo.d.ts.map +1 -0
  189. package/dist/src/drivers/dodo.js +864 -0
  190. package/dist/src/drivers/dodo.js.map +1 -0
  191. package/dist/src/drivers/efi.d.ts +98 -0
  192. package/dist/src/drivers/efi.d.ts.map +1 -0
  193. package/dist/src/drivers/efi.js +628 -0
  194. package/dist/src/drivers/efi.js.map +1 -0
  195. package/dist/src/drivers/infinitepay.d.ts +156 -0
  196. package/dist/src/drivers/infinitepay.d.ts.map +1 -0
  197. package/dist/src/drivers/infinitepay.js +321 -0
  198. package/dist/src/drivers/infinitepay.js.map +1 -0
  199. package/dist/src/drivers/lemonsqueezy.d.ts +125 -0
  200. package/dist/src/drivers/lemonsqueezy.d.ts.map +1 -0
  201. package/dist/src/drivers/lemonsqueezy.js +632 -0
  202. package/dist/src/drivers/lemonsqueezy.js.map +1 -0
  203. package/dist/src/drivers/mercadopago.d.ts +97 -0
  204. package/dist/src/drivers/mercadopago.d.ts.map +1 -0
  205. package/dist/src/drivers/mercadopago.js +844 -0
  206. package/dist/src/drivers/mercadopago.js.map +1 -0
  207. package/dist/src/drivers/mollie.d.ts +125 -0
  208. package/dist/src/drivers/mollie.d.ts.map +1 -0
  209. package/dist/src/drivers/mollie.js +812 -0
  210. package/dist/src/drivers/mollie.js.map +1 -0
  211. package/dist/src/drivers/paddle.d.ts +148 -0
  212. package/dist/src/drivers/paddle.d.ts.map +1 -0
  213. package/dist/src/drivers/paddle.js +738 -0
  214. package/dist/src/drivers/paddle.js.map +1 -0
  215. package/dist/src/drivers/pagarme.d.ts +97 -0
  216. package/dist/src/drivers/pagarme.d.ts.map +1 -0
  217. package/dist/src/drivers/pagarme.js +829 -0
  218. package/dist/src/drivers/pagarme.js.map +1 -0
  219. package/dist/src/drivers/pagbank.d.ts +57 -0
  220. package/dist/src/drivers/pagbank.d.ts.map +1 -0
  221. package/dist/src/drivers/pagbank.js +499 -0
  222. package/dist/src/drivers/pagbank.js.map +1 -0
  223. package/dist/src/drivers/paypal.d.ts +120 -0
  224. package/dist/src/drivers/paypal.d.ts.map +1 -0
  225. package/dist/src/drivers/paypal.js +891 -0
  226. package/dist/src/drivers/paypal.js.map +1 -0
  227. package/dist/src/drivers/polar.d.ts +118 -0
  228. package/dist/src/drivers/polar.d.ts.map +1 -0
  229. package/dist/src/drivers/polar.js +604 -0
  230. package/dist/src/drivers/polar.js.map +1 -0
  231. package/dist/src/drivers/razorpay.d.ts +103 -0
  232. package/dist/src/drivers/razorpay.d.ts.map +1 -0
  233. package/dist/src/drivers/razorpay.js +867 -0
  234. package/dist/src/drivers/razorpay.js.map +1 -0
  235. package/dist/src/drivers/shared.d.ts +33 -0
  236. package/dist/src/drivers/shared.d.ts.map +1 -0
  237. package/dist/src/drivers/shared.js +37 -0
  238. package/dist/src/drivers/shared.js.map +1 -0
  239. package/dist/src/drivers/square.d.ts +133 -0
  240. package/dist/src/drivers/square.d.ts.map +1 -0
  241. package/dist/src/drivers/square.js +905 -0
  242. package/dist/src/drivers/square.js.map +1 -0
  243. package/dist/src/drivers/stripe.d.ts +40 -3
  244. package/dist/src/drivers/stripe.d.ts.map +1 -1
  245. package/dist/src/drivers/stripe.js +806 -25
  246. package/dist/src/drivers/stripe.js.map +1 -1
  247. package/dist/src/drivers/woovi.d.ts +10 -3
  248. package/dist/src/drivers/woovi.d.ts.map +1 -1
  249. package/dist/src/drivers/woovi.js +162 -29
  250. package/dist/src/drivers/woovi.js.map +1 -1
  251. package/dist/src/ensure_customer.d.ts +34 -1
  252. package/dist/src/ensure_customer.d.ts.map +1 -1
  253. package/dist/src/ensure_customer.js +29 -4
  254. package/dist/src/ensure_customer.js.map +1 -1
  255. package/dist/src/http.d.ts +37 -3
  256. package/dist/src/http.d.ts.map +1 -1
  257. package/dist/src/http.js +69 -5
  258. package/dist/src/http.js.map +1 -1
  259. package/dist/src/index.d.ts +40 -10
  260. package/dist/src/index.d.ts.map +1 -1
  261. package/dist/src/index.js +22 -4
  262. package/dist/src/index.js.map +1 -1
  263. package/dist/src/invoice/emit_invoice.d.ts.map +1 -1
  264. package/dist/src/invoice/emit_invoice.js +31 -11
  265. package/dist/src/invoice/emit_invoice.js.map +1 -1
  266. package/dist/src/money.d.ts +23 -5
  267. package/dist/src/money.d.ts.map +1 -1
  268. package/dist/src/money.js +72 -7
  269. package/dist/src/money.js.map +1 -1
  270. package/dist/src/payments_manager.d.ts +8 -1
  271. package/dist/src/payments_manager.d.ts.map +1 -1
  272. package/dist/src/payments_manager.js +168 -6
  273. package/dist/src/payments_manager.js.map +1 -1
  274. package/dist/src/services/main.d.ts +38 -0
  275. package/dist/src/services/main.d.ts.map +1 -1
  276. package/dist/src/services/main.js +130 -0
  277. package/dist/src/services/main.js.map +1 -1
  278. package/dist/src/telescope/index.d.ts +2 -0
  279. package/dist/src/telescope/index.d.ts.map +1 -1
  280. package/dist/src/telescope/index.js +1 -0
  281. package/dist/src/telescope/index.js.map +1 -1
  282. package/dist/src/telescope/payments_watcher.d.ts +10 -4
  283. package/dist/src/telescope/payments_watcher.d.ts.map +1 -1
  284. package/dist/src/telescope/payments_watcher.js +65 -13
  285. package/dist/src/telescope/payments_watcher.js.map +1 -1
  286. package/dist/src/telescope/register.d.ts +62 -0
  287. package/dist/src/telescope/register.d.ts.map +1 -0
  288. package/dist/src/telescope/register.js +48 -0
  289. package/dist/src/telescope/register.js.map +1 -0
  290. package/dist/src/testing/fake_payments.d.ts +62 -0
  291. package/dist/src/testing/fake_payments.d.ts.map +1 -0
  292. package/dist/src/testing/fake_payments.js +82 -0
  293. package/dist/src/testing/fake_payments.js.map +1 -0
  294. package/dist/src/testing/in_memory_billing_store.d.ts +151 -2
  295. package/dist/src/testing/in_memory_billing_store.d.ts.map +1 -1
  296. package/dist/src/testing/in_memory_billing_store.js +517 -4
  297. package/dist/src/testing/in_memory_billing_store.js.map +1 -1
  298. package/dist/src/testing/index.d.ts +2 -1
  299. package/dist/src/testing/index.d.ts.map +1 -1
  300. package/dist/src/testing/index.js +1 -0
  301. package/dist/src/testing/index.js.map +1 -1
  302. package/dist/src/types.d.ts +161 -5
  303. package/dist/src/types.d.ts.map +1 -1
  304. package/dist/src/types.js +30 -1
  305. package/dist/src/types.js.map +1 -1
  306. package/dist/src/webhook_handlers.d.ts +96 -2
  307. package/dist/src/webhook_handlers.d.ts.map +1 -1
  308. package/dist/src/webhook_handlers.js +91 -10
  309. package/dist/src/webhook_handlers.js.map +1 -1
  310. package/dist/src/webhook_security.d.ts +94 -3
  311. package/dist/src/webhook_security.d.ts.map +1 -1
  312. package/dist/src/webhook_security.js +173 -9
  313. package/dist/src/webhook_security.js.map +1 -1
  314. package/dist/stubs/config/payments.stub +13 -0
  315. package/dist/stubs/config/payments_client.stub +42 -0
  316. package/dist/stubs/config/payments_dashboard.stub +38 -0
  317. package/dist/stubs/database/migrations/create_billing_tables.stub +35 -77
  318. package/dist/stubs/make/webhook_handler/main.stub +29 -11
  319. package/package.json +60 -25
@@ -0,0 +1,829 @@
1
+ import { publishPaymentDiagnostics, publishRefundDiagnostics, publishSubscriptionDiagnostics, } from '../diagnostics.js';
2
+ import { headerValue, httpRequest, isNotFound } from '../http.js';
3
+ import { emitInvoiceIfRequested } from '../invoice/emit_invoice.js';
4
+ import { requireMatchingCredential } from '../webhook_security.js';
5
+ import { requireCredential } from './shared.js';
6
+ /**
7
+ * Pagar.me driver — the Stone group's Brazilian gateway (Core API v5).
8
+ *
9
+ * Speaks the REST API directly via `fetch` (no SDK dependency), authenticated with HTTP
10
+ * Basic: the secret key is the username and the password is empty.
11
+ *
12
+ * Two things separate it from the other Brazilian drivers:
13
+ *
14
+ * - **Money is already integer centavos.** Asaas, AbacatePay and Woovi take decimal reais,
15
+ * so their drivers convert; Pagar.me's `amount` fields are in the same unit as the
16
+ * library's {@link Money}, and this driver deliberately does no conversion at all.
17
+ * - **Charges hang off orders.** `POST /orders` creates an order carrying one `payments[]`
18
+ * entry, and the gateway answers with the order plus the `charges[]` it generated. The
19
+ * driver returns the first charge as the {@link Payment}, which is what `findPayment`
20
+ * (`GET /charges/{id}`) and the `charge.*` webhooks then talk about.
21
+ */
22
+ export class PagarmeDriver {
23
+ provider = 'pagarme';
24
+ /**
25
+ * No `'undefined'`: an order must name its `payment_method`, so "let the customer pick"
26
+ * exists only on a payment link — {@link PagarmeDriver.createCheckout} — never on a charge.
27
+ */
28
+ supportedMethods = ['pix', 'boleto', 'credit_card', 'debit_card'];
29
+ capabilities = { refunds: true, invoices: true, subscriptions: true };
30
+ #baseUrl;
31
+ #authHeader;
32
+ #invoiceCtx;
33
+ /** Optional HTTP Basic credentials configured on the webhook endpoint in the dashboard. */
34
+ #webhookUser;
35
+ #webhookPassword;
36
+ /** Default Pix expiry (seconds) when the charge doesn't name one. */
37
+ #pixExpiresIn;
38
+ constructor(ctx, config = {}) {
39
+ this.#invoiceCtx = ctx;
40
+ const secretKey = requireCredential({
41
+ driver: 'pagarme',
42
+ option: 'secretKey',
43
+ env: 'PAGARME_SECRET_KEY',
44
+ value: config.secretKey,
45
+ });
46
+ this.#baseUrl = config.baseUrl ?? 'https://api.pagar.me/core/v5';
47
+ // HTTP Basic with the secret key as the username and an empty password.
48
+ this.#authHeader = {
49
+ name: 'Authorization',
50
+ value: `Basic ${Buffer.from(`${secretKey}:`, 'utf8').toString('base64')}`,
51
+ };
52
+ this.#webhookUser = config.webhookUser ?? process.env.PAGARME_WEBHOOK_USER;
53
+ this.#webhookPassword = config.webhookPassword ?? process.env.PAGARME_WEBHOOK_PASSWORD;
54
+ this.#pixExpiresIn = config.pixExpiresIn ?? 86_400;
55
+ }
56
+ // ── Customers ────────────────────────────────────────────────────────────────────────
57
+ async createCustomer(input) {
58
+ this.#refuseIdempotencyKey(input.idempotencyKey, 'createCustomer', 'POST /customers');
59
+ const data = await this.#request('/customers', {
60
+ method: 'POST',
61
+ body: this.#customerBody(input),
62
+ });
63
+ return this.#mapCustomer(data);
64
+ }
65
+ async findCustomer(customerId) {
66
+ try {
67
+ const data = await this.#request(`/customers/${encodeURIComponent(customerId)}`);
68
+ return this.#mapCustomer(data);
69
+ }
70
+ catch (error) {
71
+ if (isNotFound(error))
72
+ return null;
73
+ throw error;
74
+ }
75
+ }
76
+ async updateCustomer(customerId, input) {
77
+ const data = await this.#request(`/customers/${encodeURIComponent(customerId)}`, { method: 'PUT', body: this.#customerBody(input) });
78
+ return this.#mapCustomer(data);
79
+ }
80
+ // ── Payments ─────────────────────────────────────────────────────────────────────────
81
+ async charge(input) {
82
+ const method = this.#resolveMethod(input);
83
+ const reference = input.externalReference ?? input.idempotencyKey;
84
+ const body = {
85
+ // One line per order: the contract charges an amount, not a cart.
86
+ items: [
87
+ {
88
+ // Pagar.me is integer centavos, the same unit as `Money` — unlike the
89
+ // decimal-reais Brazilian gateways, nothing here goes through `toDecimal`.
90
+ amount: input.amount,
91
+ quantity: 1,
92
+ description: input.description ?? 'Charge',
93
+ ...(reference !== undefined ? { code: reference } : {}),
94
+ },
95
+ ],
96
+ payments: [this.#paymentEntry(input, method)],
97
+ // Closed orders are billed as created; an open order waits for more charges.
98
+ closed: true,
99
+ ...(input.customerId !== undefined ? { customer_id: input.customerId } : {}),
100
+ ...(input.customerId === undefined ? { customer: this.#inlineCustomer(input) } : {}),
101
+ // `code` is the store's own identifier for the order, echoed on the order and on
102
+ // every charge it generates — plus a metadata copy, because order metadata is
103
+ // repeated on the charge object the webhooks deliver.
104
+ ...(reference !== undefined ? { code: reference } : {}),
105
+ metadata: {
106
+ ...input.metadata,
107
+ ...(reference !== undefined ? { external_reference: reference } : {}),
108
+ },
109
+ };
110
+ const order = await this.#request('/orders', {
111
+ method: 'POST',
112
+ body,
113
+ // The `Idempotency-key` header, which Pagar.me documents for order creation and
114
+ // nothing else. It is separate from the `code` above: `code` routes the webhook
115
+ // back to your record, this stops a retry becoming a second order. Two footguns
116
+ // worth knowing — Pagar.me does NOT compare the bodies, so the same key with a
117
+ // different payload still returns the first order; and the key lives 24h in
118
+ // production but only 5 minutes in sandbox.
119
+ ...(input.idempotencyKey !== undefined ? { idempotencyKey: input.idempotencyKey } : {}),
120
+ });
121
+ const charge = order.charges?.[0];
122
+ if (!charge) {
123
+ throw new Error(`[payments] Pagar.me created order "${order.id}" without a charge. Check the account is enabled for the requested payment method.`);
124
+ }
125
+ const payment = this.#mapCharge(charge, order);
126
+ await emitInvoiceIfRequested(this.#invoiceCtx, input, payment, this);
127
+ publishPaymentDiagnostics(payment);
128
+ return payment;
129
+ }
130
+ async findPayment(gatewayId) {
131
+ try {
132
+ const data = await this.#request(`/charges/${encodeURIComponent(gatewayId)}`);
133
+ return this.#mapCharge(data);
134
+ }
135
+ catch (error) {
136
+ if (isNotFound(error))
137
+ return null;
138
+ throw error;
139
+ }
140
+ }
141
+ async refund(paymentGatewayId, amount, options) {
142
+ this.#refuseIdempotencyKey(options?.idempotencyKey, 'refund', 'DELETE /charges/{id}');
143
+ // Pagar.me has one endpoint for both: cancelling an unsettled charge and refunding a
144
+ // paid one. Omitting `amount` cancels/refunds the full value.
145
+ const data = await this.#request(`/charges/${encodeURIComponent(paymentGatewayId)}`, { method: 'DELETE', ...(amount !== undefined ? { body: { amount } } : {}) });
146
+ const refunded = data.status === 'canceled' ||
147
+ data.status === 'refunded' ||
148
+ data.status === 'partial_canceled' ||
149
+ data.status === 'partial_refunded';
150
+ const refund = {
151
+ id: data.id,
152
+ gatewayId: data.id,
153
+ provider: this.provider,
154
+ amount: { amount: amount ?? data.canceled_amount ?? data.amount, currency: 'brl' },
155
+ status: refunded ? 'succeeded' : data.status === 'failed' ? 'failed' : 'pending',
156
+ createdAt: this.#toIso(data.canceled_at) ?? new Date().toISOString(),
157
+ };
158
+ publishRefundDiagnostics(refund);
159
+ return refund;
160
+ }
161
+ // ── Checkout ─────────────────────────────────────────────────────────────────────────
162
+ /**
163
+ * Creates a Pagar.me payment link (`POST /paymentlinks`) — the hosted checkout page.
164
+ *
165
+ * `planId` switches the link to a subscription link, which the API only accepts with
166
+ * `credit_card`. There is no cancel URL in the API: a link has `flow_settings.success_url`
167
+ * and nothing else, so {@link CheckoutInput.cancelUrl} is ignored rather than faked.
168
+ */
169
+ async createCheckout(input) {
170
+ const recurring = input.planId !== undefined;
171
+ const methods = recurring ? ['credit_card'] : this.#checkoutMethods(input);
172
+ const body = {
173
+ type: recurring ? 'subscription' : 'order',
174
+ name: (input.description ?? 'Checkout').slice(0, 64),
175
+ // `order_code` is the link's correlation id: the store's own identifier, echoed as
176
+ // the `code` of every order the link generates and read back out of the webhook.
177
+ ...((input.externalReference ?? input.idempotencyKey) !== undefined
178
+ ? { order_code: input.externalReference ?? input.idempotencyKey }
179
+ : {}),
180
+ payment_settings: {
181
+ accepted_payment_methods: methods,
182
+ ...(methods.includes('credit_card')
183
+ ? { credit_card_settings: { operation_type: 'auth_and_capture' } }
184
+ : {}),
185
+ },
186
+ cart_settings: {
187
+ items: [
188
+ {
189
+ name: (input.description ?? 'Checkout').slice(0, 64),
190
+ amount: input.amount,
191
+ default_quantity: 1,
192
+ },
193
+ ],
194
+ ...(recurring
195
+ ? {
196
+ recurrences: [
197
+ {
198
+ plan_id: input.planId,
199
+ ...(input.trialDays !== undefined ? { start_in: input.trialDays } : {}),
200
+ },
201
+ ],
202
+ }
203
+ : {}),
204
+ },
205
+ ...(input.customerId !== undefined
206
+ ? { customer_settings: { customer_id: input.customerId } }
207
+ : {}),
208
+ flow_settings: { success_url: input.successUrl },
209
+ };
210
+ const link = await this.#request('/paymentlinks', {
211
+ method: 'POST',
212
+ body,
213
+ });
214
+ return {
215
+ id: link.id,
216
+ gatewayId: link.id,
217
+ provider: this.provider,
218
+ url: link.url,
219
+ status: link.status === 'expired' || link.status === 'canceled' ? 'expired' : 'open',
220
+ amount: { amount: link.cart_settings?.total_cost ?? input.amount, currency: 'brl' },
221
+ ...(input.customerId !== undefined ? { customerId: input.customerId } : {}),
222
+ };
223
+ }
224
+ // ── Subscriptions ────────────────────────────────────────────────────────────────────
225
+ /**
226
+ * Creates a subscription (`POST /subscriptions`), in whichever of the API's two shapes
227
+ * the call describes:
228
+ *
229
+ * - `planId` naming a Pagar.me plan (`plan_…`) creates a subscription from that plan;
230
+ * the plan owns the interval and the price.
231
+ * - Any other `planId` creates a plan-less ("avulsa") subscription built from `amount`
232
+ * and `cycle`, with `planId` kept as the subscription's `code`. `amount` is required
233
+ * in that shape — there is no plan to read a price from.
234
+ */
235
+ async createSubscription(input) {
236
+ this.#refuseIdempotencyKey(input.idempotencyKey, 'createSubscription', 'POST /subscriptions');
237
+ const fromPlan = input.planId.startsWith('plan_');
238
+ const method = this.#mapMethodOut(input.method ?? 'credit_card');
239
+ const body = {
240
+ customer_id: input.customerId,
241
+ payment_method: method,
242
+ ...(input.startDate !== undefined ? { start_at: input.startDate } : {}),
243
+ ...(input.card !== undefined ? { card_token: input.card.token } : {}),
244
+ // Pagar.me's own identifier for the subscription in your system — the routing key,
245
+ // echoed on the subscription and on the invoices/charges it generates.
246
+ ...(input.externalReference !== undefined
247
+ ? { code: input.externalReference }
248
+ : fromPlan
249
+ ? {}
250
+ : { code: input.planId }),
251
+ metadata: {
252
+ ...input.metadata,
253
+ ...(input.externalReference !== undefined
254
+ ? { external_reference: input.externalReference }
255
+ : {}),
256
+ },
257
+ };
258
+ if (fromPlan) {
259
+ body.plan_id = input.planId;
260
+ }
261
+ else {
262
+ if (input.amount === undefined) {
263
+ throw new Error('[payments] Pagar.me needs an `amount` to create a subscription without a plan. ' +
264
+ `Pass \`amount\`, or set \`planId\` to a Pagar.me plan id ("plan_…").`);
265
+ }
266
+ const { interval, intervalCount } = this.#mapCycle(input.cycle);
267
+ body.interval = interval;
268
+ body.interval_count = intervalCount;
269
+ body.billing_type = 'prepaid';
270
+ body.items = [
271
+ {
272
+ description: input.description ?? input.planId,
273
+ quantity: 1,
274
+ pricing_scheme: { price: input.amount },
275
+ },
276
+ ];
277
+ }
278
+ const data = await this.#request('/subscriptions', {
279
+ method: 'POST',
280
+ body,
281
+ });
282
+ const subscription = this.#mapSubscription(data);
283
+ publishSubscriptionDiagnostics(subscription, 'subscription.created');
284
+ return subscription;
285
+ }
286
+ /**
287
+ * Cancels a subscription. Pagar.me cancels **immediately** — the API has no period-end
288
+ * flag — so `atPeriodEnd` cannot keep the subscription running. What it does control is
289
+ * the invoices already issued for the current cycle: `atPeriodEnd: true` sends
290
+ * `cancel_pending_invoices: false`, leaving them payable; the default cancels them too.
291
+ */
292
+ async cancelSubscription(subscriptionGatewayId, options) {
293
+ const data = await this.#request(`/subscriptions/${encodeURIComponent(subscriptionGatewayId)}`, { method: 'DELETE', body: { cancel_pending_invoices: options?.atPeriodEnd !== true } });
294
+ const subscription = this.#mapSubscription(data);
295
+ publishSubscriptionDiagnostics(subscription, 'subscription.canceled');
296
+ return subscription;
297
+ }
298
+ /**
299
+ * Not supported. A Pagar.me subscription has no amount or description of its own — the
300
+ * price lives on its items, each with its own `pricing_scheme`, and the API changes one
301
+ * through the subscription-item sub-resource rather than through the subscription. There
302
+ * is no request this method could make that means what the contract says it means, so it
303
+ * refuses instead of returning a subscription the gateway never changed.
304
+ */
305
+ async updateSubscription(subscriptionGatewayId, _input) {
306
+ throw new Error(`[payments] Pagar.me cannot update subscription "${subscriptionGatewayId}": its price lives on its items, not on the subscription. ` +
307
+ `Cancel "${subscriptionGatewayId}" and create a new subscription, or edit its item in the dashboard.`);
308
+ }
309
+ async findSubscription(gatewayId) {
310
+ try {
311
+ const data = await this.#request(`/subscriptions/${encodeURIComponent(gatewayId)}`);
312
+ return this.#mapSubscription(data);
313
+ }
314
+ catch (error) {
315
+ if (isNotFound(error))
316
+ return null;
317
+ throw error;
318
+ }
319
+ }
320
+ // ── Invoices ─────────────────────────────────────────────────────────────────────────
321
+ /** Lists the gateway's subscription invoices (`GET /invoices?customer_id=…`). */
322
+ async listInvoices(customerId) {
323
+ const data = await this.#request(`/invoices?customer_id=${encodeURIComponent(customerId)}`);
324
+ return (data.data ?? []).map((invoice) => {
325
+ const result = {
326
+ id: invoice.id,
327
+ gatewayId: invoice.id,
328
+ provider: this.provider,
329
+ status: this.#mapInvoiceStatus(invoice.status),
330
+ amount: { amount: invoice.amount, currency: 'brl' },
331
+ createdAt: this.#toIso(invoice.created_at) ?? new Date().toISOString(),
332
+ payload: invoice,
333
+ };
334
+ if (invoice.customer?.id !== undefined)
335
+ result.customerId = invoice.customer.id;
336
+ if (invoice.subscription?.id !== undefined)
337
+ result.subscriptionId = invoice.subscription.id;
338
+ if (invoice.code !== undefined)
339
+ result.number = invoice.code;
340
+ // `url` is a dashboard path (`/invoices/in_…`), not a PDF — only surface it when the
341
+ // gateway hands back something absolute.
342
+ if (invoice.url?.startsWith('http'))
343
+ result.hostedPdfUrl = invoice.url;
344
+ return result;
345
+ });
346
+ }
347
+ // ── Webhooks ─────────────────────────────────────────────────────────────────────────
348
+ /**
349
+ * Pagar.me signs nothing: the dashboard's webhook configuration offers optional HTTP
350
+ * Basic credentials and no HMAC. When `webhookUser`/`webhookPassword` are configured,
351
+ * every request must carry the matching `Authorization: Basic …`; when they are not,
352
+ * the payload is accepted as-is — so configure them in production.
353
+ */
354
+ /**
355
+ * Whether a delivery to `POST /payments/webhook/:provider` can be authenticated.
356
+ *
357
+ * Pagar.me authenticates with HTTP Basic on the callback; with neither half configured the
358
+ * credentials are not checked.
359
+ */
360
+ get webhookVerification() {
361
+ return this.#webhookUser !== undefined || this.#webhookPassword !== undefined
362
+ ? 'configured'
363
+ : 'unconfigured';
364
+ }
365
+ parseWebhook(rawBody, headers) {
366
+ if (this.#webhookUser !== undefined || this.#webhookPassword !== undefined) {
367
+ this.#requireBasicAuth(headers);
368
+ }
369
+ const payload = JSON.parse(rawBody);
370
+ const type = payload.type ?? 'unknown';
371
+ return {
372
+ id: payload.id ?? `${type}-${payload.data?.id ?? Math.random()}`,
373
+ provider: this.provider,
374
+ type: this.#mapWebhookType(type),
375
+ createdAt: this.#toIso(payload.created_at) ?? new Date().toISOString(),
376
+ data: this.#mapWebhookData(type, payload.data),
377
+ raw: payload,
378
+ };
379
+ }
380
+ // ── Mappers ──────────────────────────────────────────────────────────────────────────
381
+ #customerBody(input) {
382
+ const document = input.taxId?.replace(/\D/g, '');
383
+ return {
384
+ ...(input.name !== undefined ? { name: input.name } : {}),
385
+ ...(input.email !== undefined ? { email: input.email } : {}),
386
+ ...(document !== undefined
387
+ ? {
388
+ document,
389
+ document_type: document.length > 11 ? 'CNPJ' : 'CPF',
390
+ type: document.length > 11 ? 'company' : 'individual',
391
+ }
392
+ : {}),
393
+ ...(input.metadata !== undefined ? { metadata: input.metadata } : {}),
394
+ };
395
+ }
396
+ #mapCustomer(data) {
397
+ const customer = { id: data.id };
398
+ if (data.name !== undefined)
399
+ customer.name = data.name;
400
+ if (data.email !== undefined)
401
+ customer.email = data.email;
402
+ if (data.document !== undefined)
403
+ customer.taxId = data.document;
404
+ if (data.metadata !== undefined)
405
+ customer.metadata = data.metadata;
406
+ return customer;
407
+ }
408
+ /** The payer data an order carries when no `customer_id` was given. */
409
+ #inlineCustomer(input) {
410
+ const name = input.customer?.name ?? input.card?.holder?.name;
411
+ const email = input.customer?.email ?? input.card?.holder?.email;
412
+ const taxId = input.customer?.taxId ?? input.card?.holder?.cpfCnpj;
413
+ if (name === undefined) {
414
+ throw new Error('[payments] Pagar.me needs a payer on every order. Pass `customerId`, or `customer.name` on the charge.');
415
+ }
416
+ const document = taxId?.replace(/\D/g, '');
417
+ return {
418
+ name,
419
+ ...(email !== undefined ? { email } : {}),
420
+ ...(document !== undefined
421
+ ? {
422
+ document,
423
+ document_type: document.length > 11 ? 'CNPJ' : 'CPF',
424
+ type: document.length > 11 ? 'company' : 'individual',
425
+ }
426
+ : {}),
427
+ };
428
+ }
429
+ /** The single `payments[]` entry an order carries, built for the resolved method. */
430
+ #paymentEntry(input, method) {
431
+ const entry = { payment_method: method };
432
+ if (method === 'credit_card' || method === 'debit_card') {
433
+ const token = input.card?.token ?? undefined;
434
+ const cardId = input.paymentMethodId ?? undefined;
435
+ if (token === undefined && cardId === undefined) {
436
+ throw new Error(`[payments] Pagar.me needs a tokenized card for a ${method} charge. Pass \`card.token\` (checkout transparente) or \`paymentMethodId\` (a saved \`card_…\` id).`);
437
+ }
438
+ entry[method] = {
439
+ operation_type: 'auth_and_capture',
440
+ installments: Number(input.metadata?.installments ?? 1),
441
+ ...(input.metadata?.statementDescriptor !== undefined
442
+ ? { statement_descriptor: input.metadata.statementDescriptor }
443
+ : {}),
444
+ ...(token !== undefined ? { card_token: token } : {}),
445
+ ...(cardId !== undefined ? { card_id: cardId } : {}),
446
+ };
447
+ }
448
+ else if (method === 'pix') {
449
+ // `expires_in` is mandatory on a Pix payment — the driver always sends one.
450
+ entry.pix = { expires_in: Number(input.metadata?.expiresIn ?? this.#pixExpiresIn) };
451
+ }
452
+ else if (method === 'boleto') {
453
+ entry.boleto = {
454
+ ...(input.metadata?.dueDate !== undefined ? { due_at: input.metadata.dueDate } : {}),
455
+ ...(input.description !== undefined ? { instructions: input.description } : {}),
456
+ };
457
+ }
458
+ const split = this.#mapSplit(input);
459
+ if (split !== undefined)
460
+ entry.split = split;
461
+ return entry;
462
+ }
463
+ /**
464
+ * Marketplace split. A Pagar.me rule carries exactly one `type` — `percentage` or
465
+ * `flat` — so an entry that names both a percent and a fixed value has no honest
466
+ * mapping and is refused rather than silently halved.
467
+ */
468
+ #mapSplit(input) {
469
+ if (input.split === undefined || input.split.length === 0)
470
+ return undefined;
471
+ return input.split.map((entry) => {
472
+ if (entry.percentualValue !== undefined && entry.fixedValue !== undefined) {
473
+ throw new Error(`[payments] Pagar.me split rules are either \`percentage\` or \`flat\`, never both. Wallet "${entry.walletId}" set percentualValue and fixedValue — pick one.`);
474
+ }
475
+ if (entry.percentualValue === undefined && entry.fixedValue === undefined) {
476
+ throw new Error(`[payments] Pagar.me split rule for wallet "${entry.walletId}" has no value. Set \`percentualValue\` or \`fixedValue\`.`);
477
+ }
478
+ const percentage = entry.percentualValue !== undefined;
479
+ return {
480
+ recipient_id: entry.walletId,
481
+ type: percentage ? 'percentage' : 'flat',
482
+ // Pagar.me is centavos throughout, so a `flat` share needs no conversion.
483
+ amount: percentage ? entry.percentualValue : entry.fixedValue,
484
+ };
485
+ });
486
+ }
487
+ #mapCharge(data, order) {
488
+ const method = this.#mapMethodIn(data.payment_method);
489
+ const transaction = data.last_transaction;
490
+ const payment = {
491
+ id: data.id,
492
+ gatewayId: data.id,
493
+ provider: this.provider,
494
+ amount: { amount: data.amount, currency: 'brl' },
495
+ status: this.#chargeStatus(data, transaction),
496
+ payload: data,
497
+ createdAt: this.#toIso(data.created_at) ?? new Date().toISOString(),
498
+ };
499
+ const customerId = data.customer?.id ?? order?.customer?.id;
500
+ if (customerId !== undefined)
501
+ payment.customerId = customerId;
502
+ if (method !== 'unknown')
503
+ payment.method = method;
504
+ if (transaction?.qr_code !== undefined) {
505
+ payment.pixCode = transaction.qr_code;
506
+ payment.pixCopiaECola = transaction.qr_code;
507
+ }
508
+ if (transaction?.qr_code_url !== undefined) {
509
+ payment.pixQrCodeImage = transaction.qr_code_url;
510
+ payment.pixQrCode = transaction.qr_code_url;
511
+ }
512
+ // Boleto: `url` is the hosted slip, `pdf` the printable one.
513
+ const hosted = transaction?.url ?? transaction?.pdf;
514
+ if (hosted !== undefined)
515
+ payment.hostedUrl = hosted;
516
+ const paidAt = this.#toIso(data.paid_at);
517
+ if (paidAt !== null)
518
+ payment.paidAt = paidAt;
519
+ return payment;
520
+ }
521
+ /**
522
+ * A charge's canonical status — which Pagar.me's `charge.status` alone cannot give.
523
+ *
524
+ * Pagar.me has no `authorized` charge status: a card taken with `operation_type:
525
+ * "auth_only"` leaves the CHARGE at `pending`, exactly like a boleto nobody has paid,
526
+ * and only `last_transaction.status` says `authorized_pending_capture`. So `pending`
527
+ * here is overloaded — "waiting for the payer" and "money held on the card, waiting for
528
+ * you to capture it" are the same word — and reading only the charge collapses a hold
529
+ * the issuer has already granted into a charge that may never happen.
530
+ */
531
+ #chargeStatus(data, transaction) {
532
+ const statusMap = {
533
+ pending: 'pending',
534
+ processing: 'pending',
535
+ waiting_payment: 'pending',
536
+ paid: 'paid',
537
+ overpaid: 'paid',
538
+ underpaid: 'paid',
539
+ canceled: 'canceled',
540
+ partial_canceled: 'refunded',
541
+ refunded: 'refunded',
542
+ partial_refunded: 'refunded',
543
+ voided: 'canceled',
544
+ failed: 'failed',
545
+ payment_failed: 'failed',
546
+ not_authorized: 'failed',
547
+ with_error: 'failed',
548
+ chargedback: 'disputed',
549
+ };
550
+ const mapped = statusMap[data.status] ?? 'pending';
551
+ if (mapped !== 'pending')
552
+ return mapped;
553
+ // Both transaction statuses mean the same thing: authorized, nothing captured.
554
+ return transaction?.status === 'authorized_pending_capture' ||
555
+ transaction?.status === 'waiting_capture'
556
+ ? 'authorized'
557
+ : 'pending';
558
+ }
559
+ #mapSubscription(data) {
560
+ const statusMap = {
561
+ active: 'active',
562
+ future: 'incomplete',
563
+ canceled: 'canceled',
564
+ };
565
+ const price = data.items?.[0]?.pricing_scheme?.price;
566
+ const subscription = {
567
+ id: data.id,
568
+ gatewayId: data.id,
569
+ provider: this.provider,
570
+ customerId: data.customer?.id ?? '',
571
+ status: statusMap[data.status] ?? 'active',
572
+ planId: data.plan?.id ?? data.code ?? data.id,
573
+ payload: data,
574
+ createdAt: this.#toIso(data.created_at) ?? new Date().toISOString(),
575
+ };
576
+ if (price !== undefined)
577
+ subscription.amount = { amount: price, currency: 'brl' };
578
+ const endsAt = this.#toIso(data.canceled_at);
579
+ if (endsAt !== null)
580
+ subscription.endsAt = endsAt;
581
+ const periodStart = this.#toIso(data.current_cycle?.start_at);
582
+ if (periodStart !== null)
583
+ subscription.currentPeriodStart = periodStart;
584
+ const periodEnd = this.#toIso(data.current_cycle?.end_at);
585
+ if (periodEnd !== null)
586
+ subscription.currentPeriodEnd = periodEnd;
587
+ return subscription;
588
+ }
589
+ #mapInvoiceStatus(status) {
590
+ switch (status) {
591
+ case 'paid':
592
+ return 'paid';
593
+ case 'pending':
594
+ case 'scheduled':
595
+ case 'partial_paid':
596
+ return 'open';
597
+ case 'canceled':
598
+ return 'canceled';
599
+ case 'failed':
600
+ return 'failed';
601
+ default:
602
+ return 'draft';
603
+ }
604
+ }
605
+ #mapWebhookType(type) {
606
+ switch (type) {
607
+ case 'charge.paid':
608
+ case 'order.paid':
609
+ case 'invoice.paid':
610
+ return 'payment.succeeded';
611
+ case 'charge.payment_failed':
612
+ case 'order.payment_failed':
613
+ case 'invoice.payment_failed':
614
+ return 'payment.failed';
615
+ case 'charge.refunded':
616
+ case 'charge.partial_canceled':
617
+ return 'payment.refunded';
618
+ case 'charge.created':
619
+ case 'charge.pending':
620
+ case 'charge.processing':
621
+ case 'charge.updated':
622
+ case 'charge.underpaid':
623
+ case 'charge.overpaid':
624
+ case 'order.created':
625
+ case 'order.updated':
626
+ case 'order.closed':
627
+ return 'payment.updated';
628
+ // The chargeback: the issuer has pulled the money back. The charge's own status
629
+ // becomes `chargedback` too, and only the charge's — Pagar.me leaves the order's
630
+ // status alone. Note the spelling: `chargedback`, with the `d` in the middle, which
631
+ // Pagar.me's own docs call out because everyone gets it wrong.
632
+ //
633
+ // The charge payload carries no defense deadline. Pagar.me's response window lives
634
+ // only on the Disputes API (`responseDeadline` on `GET /v1/disputes/{id}`), which is
635
+ // not on the driver contract — so this event has no `actionableUntil` to carry, and
636
+ // inventing one would be worse than the operator going to look.
637
+ case 'charge.chargedback':
638
+ return 'payment.disputed';
639
+ // `chargeback.received` REPLACES `charge.chargedback` — Pagar.me's own event list
640
+ // marks the latter "será descontinuado", migration due **30/09/2026**. As of this
641
+ // reading (August 2026) the event exists in that list with a one-line description
642
+ // and nothing else: **no payload is documented anywhere**, and no example is
643
+ // published. The neighbouring Disputes API documents a dispute as
644
+ // `{ disputeId, chargeId, responseDeadline, chargebackAmount, status, … }`, which
645
+ // would map cleanly if that is what arrives — but "would map cleanly if" is not a
646
+ // reference. Filing `payment.disputed` against an id that turns out to be the
647
+ // dispute's rather than the charge's writes a `disputed` row nothing reconciles,
648
+ // so this keeps its own name until one real payload is seen.
649
+ case 'chargeback.received':
650
+ return type;
651
+ // A cancel is not a dispute. The contract has no cancel event either, and inventing
652
+ // one would route straight to the processor's no-op branch under a name nothing
653
+ // reads — these land as `payment.updated` with `event.raw.type` intact.
654
+ case 'order.canceled':
655
+ case 'invoice.canceled':
656
+ return 'payment.updated';
657
+ case 'subscription.created':
658
+ return 'subscription.created';
659
+ case 'subscription.updated':
660
+ return 'subscription.updated';
661
+ case 'subscription.canceled':
662
+ return 'subscription.canceled';
663
+ default:
664
+ return type;
665
+ }
666
+ }
667
+ #mapWebhookData(type, data) {
668
+ if (data === undefined)
669
+ return {};
670
+ // An undocumented payload passes through UNTOUCHED. Running `chargeback.received`
671
+ // through the charge mapper below fabricated a `{ gatewayId, amount, currency }` out of
672
+ // fields a dispute object does not have — an empty id and a zero — and handed it to any
673
+ // app handler registered for the event. What Pagar.me sent is the only honest answer
674
+ // until its shape is documented, and it is also what you need to report the shape.
675
+ if (type === 'chargeback.received')
676
+ return data;
677
+ if (type.startsWith('subscription.')) {
678
+ const subscription = this.#mapSubscription(data);
679
+ return {
680
+ gatewayId: subscription.gatewayId,
681
+ customerId: subscription.customerId,
682
+ status: subscription.status,
683
+ planId: subscription.planId,
684
+ ...(subscription.endsAt !== undefined ? { endsAt: subscription.endsAt } : {}),
685
+ ...(this.#reference(data) !== undefined
686
+ ? { externalReference: this.#reference(data) }
687
+ : {}),
688
+ };
689
+ }
690
+ // `order.*` delivers the order with its charges; `charge.*` delivers the charge
691
+ // itself. Both normalize onto the charge, which is what a Payment row tracks.
692
+ const charge = type.startsWith('order.')
693
+ ? (data.charges?.[0] ?? undefined)
694
+ : data;
695
+ if (charge === undefined)
696
+ return {};
697
+ const payment = this.#mapCharge(charge, type.startsWith('order.') ? data : undefined);
698
+ const reference = this.#reference(charge) ?? this.#reference(data);
699
+ return {
700
+ gatewayId: payment.gatewayId,
701
+ amount: payment.amount.amount,
702
+ currency: payment.amount.currency,
703
+ ...(payment.customerId !== undefined ? { customerId: payment.customerId } : {}),
704
+ ...(reference !== undefined ? { externalReference: reference } : {}),
705
+ };
706
+ }
707
+ /**
708
+ * The app's own id echoed back — `metadata.external_reference` first (order metadata is
709
+ * repeated on every charge), then the `code` the order/subscription was created with.
710
+ */
711
+ #reference(data) {
712
+ const fromMetadata = data.metadata?.external_reference;
713
+ if (typeof fromMetadata === 'string' && fromMetadata !== '')
714
+ return fromMetadata;
715
+ return data.code;
716
+ }
717
+ /** Resolve the order's `payment_method` from the charge input, refusing what an order can't express. */
718
+ #resolveMethod(input) {
719
+ if (input.method === undefined) {
720
+ // A card was handed over without naming a method — that is unambiguous.
721
+ if (input.card !== undefined || input.paymentMethodId !== undefined)
722
+ return 'credit_card';
723
+ throw new Error('[payments] Pagar.me needs an explicit `method` on every charge — an order names its ' +
724
+ "`payment_method`. Pass 'pix', 'boleto', 'credit_card' or 'debit_card'.");
725
+ }
726
+ return this.#mapMethodOut(input.method);
727
+ }
728
+ #mapMethodOut(method) {
729
+ switch (method) {
730
+ case 'pix':
731
+ return 'pix';
732
+ case 'boleto':
733
+ return 'boleto';
734
+ case 'credit_card':
735
+ return 'credit_card';
736
+ case 'debit_card':
737
+ return 'debit_card';
738
+ default:
739
+ throw new Error(`[payments] Pagar.me does not support payment method "${method}". Supported: pix, boleto, credit_card, debit_card.`);
740
+ }
741
+ }
742
+ #mapMethodIn(paymentMethod) {
743
+ switch (paymentMethod?.toLowerCase()) {
744
+ case 'pix':
745
+ return 'pix';
746
+ case 'boleto':
747
+ return 'boleto';
748
+ case 'credit_card':
749
+ return 'card';
750
+ case 'debit_card':
751
+ return 'debit_card';
752
+ default:
753
+ return 'unknown';
754
+ }
755
+ }
756
+ /** Map the contract's cycle onto Pagar.me's `interval` + `interval_count` pair. */
757
+ #mapCycle(cycle) {
758
+ switch (cycle) {
759
+ case 'WEEKLY':
760
+ return { interval: 'week', intervalCount: 1 };
761
+ case 'BIWEEKLY':
762
+ return { interval: 'week', intervalCount: 2 };
763
+ case 'QUARTERLY':
764
+ return { interval: 'month', intervalCount: 3 };
765
+ case 'SEMIANNUALLY':
766
+ return { interval: 'month', intervalCount: 6 };
767
+ case 'YEARLY':
768
+ return { interval: 'year', intervalCount: 1 };
769
+ default:
770
+ return { interval: 'month', intervalCount: 1 };
771
+ }
772
+ }
773
+ /** Which methods a payment link accepts, from the charge's routing hint. */
774
+ #checkoutMethods(input) {
775
+ const named = (input.metadata?.methods ?? undefined);
776
+ if (Array.isArray(named) && named.length > 0) {
777
+ return named.map((method) => this.#mapMethodOut(method));
778
+ }
779
+ return ['credit_card', 'pix', 'boleto'];
780
+ }
781
+ /**
782
+ * Pagar.me documents `Idempotency-key` for **order creation and nothing else** — the
783
+ * reference for `DELETE /charges/{id}`, `POST /customers` and `POST /subscriptions`
784
+ * lists no header but `Authorization`. The header might well be honoured there too, on
785
+ * the same gateway; "might" is not a retry guarantee, and quietly forwarding a key the
786
+ * reference does not promise anything about turns a caller's retry into a second
787
+ * refund. So those operations refuse it and say where it does work.
788
+ */
789
+ #refuseIdempotencyKey(key, operation, endpoint) {
790
+ if (key === undefined)
791
+ return;
792
+ throw new Error(`[payments] Pagar.me documents idempotency only for order creation, so \`${operation}\` cannot honour an idempotencyKey — its \`${endpoint}\` reference documents no such header. Only \`charge()\` (POST /orders) deduplicates; for the rest, deduplicate before you call.`);
793
+ }
794
+ /** Parse a gateway timestamp into an ISO string, or `null` when absent/invalid. */
795
+ #toIso(value) {
796
+ if (value === undefined || value === '')
797
+ return null;
798
+ const date = new Date(value);
799
+ if (Number.isNaN(date.getTime()))
800
+ return null;
801
+ return date.toISOString();
802
+ }
803
+ #requireBasicAuth(headers) {
804
+ const header = headerValue(headers, 'authorization');
805
+ if (header === undefined || !header.toLowerCase().startsWith('basic ')) {
806
+ throw new Error('[payments] Missing webhook Basic credentials on Pagar.me request.');
807
+ }
808
+ const decoded = Buffer.from(header.slice(6).trim(), 'base64').toString('utf8');
809
+ const separator = decoded.indexOf(':');
810
+ const user = separator === -1 ? decoded : decoded.slice(0, separator);
811
+ const password = separator === -1 ? '' : decoded.slice(separator + 1);
812
+ requireMatchingCredential(user, this.#webhookUser ?? '', 'Pagar.me', 'user');
813
+ requireMatchingCredential(password, this.#webhookPassword ?? '', 'Pagar.me', 'password');
814
+ }
815
+ async #request(path, options = {}) {
816
+ return httpRequest(path, {
817
+ baseUrl: this.#baseUrl,
818
+ ...(options.method !== undefined ? { method: options.method } : {}),
819
+ ...(options.body !== undefined ? { body: options.body } : {}),
820
+ authHeader: this.#authHeader,
821
+ // Pagar.me spells it `Idempotency-key`; the value is case-sensitive, and two
822
+ // concurrent requests carrying the same one get a 409 rather than two orders.
823
+ ...(options.idempotencyKey !== undefined
824
+ ? { headers: { 'Idempotency-key': options.idempotencyKey } }
825
+ : {}),
826
+ });
827
+ }
828
+ }
829
+ //# sourceMappingURL=pagarme.js.map