@easypayment/medusa-paypal 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (272) hide show
  1. package/.medusa/server/src/admin/index.js +2127 -0
  2. package/.medusa/server/src/admin/index.mjs +2128 -0
  3. package/.medusa/server/src/api/admin/payment-collections/[id]/payment-sessions/route.d.ts +3 -0
  4. package/.medusa/server/src/api/admin/payment-collections/[id]/payment-sessions/route.d.ts.map +1 -0
  5. package/.medusa/server/src/api/admin/payment-collections/[id]/payment-sessions/route.js +25 -0
  6. package/.medusa/server/src/api/admin/payment-collections/[id]/payment-sessions/route.js.map +1 -0
  7. package/.medusa/server/src/api/admin/paypal/audit-logs/route.d.ts +3 -0
  8. package/.medusa/server/src/api/admin/paypal/audit-logs/route.d.ts.map +1 -0
  9. package/.medusa/server/src/api/admin/paypal/audit-logs/route.js +12 -0
  10. package/.medusa/server/src/api/admin/paypal/audit-logs/route.js.map +1 -0
  11. package/.medusa/server/src/api/admin/paypal/disconnect/route.d.ts +3 -0
  12. package/.medusa/server/src/api/admin/paypal/disconnect/route.d.ts.map +1 -0
  13. package/.medusa/server/src/api/admin/paypal/disconnect/route.js +9 -0
  14. package/.medusa/server/src/api/admin/paypal/disconnect/route.js.map +1 -0
  15. package/.medusa/server/src/api/admin/paypal/disputes/[id]/route.d.ts +3 -0
  16. package/.medusa/server/src/api/admin/paypal/disputes/[id]/route.d.ts.map +1 -0
  17. package/.medusa/server/src/api/admin/paypal/disputes/[id]/route.js +17 -0
  18. package/.medusa/server/src/api/admin/paypal/disputes/[id]/route.js.map +1 -0
  19. package/.medusa/server/src/api/admin/paypal/disputes/route.d.ts +3 -0
  20. package/.medusa/server/src/api/admin/paypal/disputes/route.d.ts.map +1 -0
  21. package/.medusa/server/src/api/admin/paypal/disputes/route.js +27 -0
  22. package/.medusa/server/src/api/admin/paypal/disputes/route.js.map +1 -0
  23. package/.medusa/server/src/api/admin/paypal/disputes/summary/route.d.ts +3 -0
  24. package/.medusa/server/src/api/admin/paypal/disputes/summary/route.d.ts.map +1 -0
  25. package/.medusa/server/src/api/admin/paypal/disputes/summary/route.js +17 -0
  26. package/.medusa/server/src/api/admin/paypal/disputes/summary/route.js.map +1 -0
  27. package/.medusa/server/src/api/admin/paypal/environment/route.d.ts +4 -0
  28. package/.medusa/server/src/api/admin/paypal/environment/route.d.ts.map +1 -0
  29. package/.medusa/server/src/api/admin/paypal/environment/route.js +23 -0
  30. package/.medusa/server/src/api/admin/paypal/environment/route.js.map +1 -0
  31. package/.medusa/server/src/api/admin/paypal/onboard-complete/route.d.ts +8 -0
  32. package/.medusa/server/src/api/admin/paypal/onboard-complete/route.d.ts.map +1 -0
  33. package/.medusa/server/src/api/admin/paypal/onboard-complete/route.js +41 -0
  34. package/.medusa/server/src/api/admin/paypal/onboard-complete/route.js.map +1 -0
  35. package/.medusa/server/src/api/admin/paypal/onboarding-link/route.d.ts +4 -0
  36. package/.medusa/server/src/api/admin/paypal/onboarding-link/route.d.ts.map +1 -0
  37. package/.medusa/server/src/api/admin/paypal/onboarding-link/route.js +35 -0
  38. package/.medusa/server/src/api/admin/paypal/onboarding-link/route.js.map +1 -0
  39. package/.medusa/server/src/api/admin/paypal/onboarding-status/route.d.ts +3 -0
  40. package/.medusa/server/src/api/admin/paypal/onboarding-status/route.d.ts.map +1 -0
  41. package/.medusa/server/src/api/admin/paypal/onboarding-status/route.js +20 -0
  42. package/.medusa/server/src/api/admin/paypal/onboarding-status/route.js.map +1 -0
  43. package/.medusa/server/src/api/admin/paypal/reconciliation-status/route.d.ts +3 -0
  44. package/.medusa/server/src/api/admin/paypal/reconciliation-status/route.d.ts.map +1 -0
  45. package/.medusa/server/src/api/admin/paypal/reconciliation-status/route.js +8 -0
  46. package/.medusa/server/src/api/admin/paypal/reconciliation-status/route.js.map +1 -0
  47. package/.medusa/server/src/api/admin/paypal/rotate-credentials/route.d.ts +3 -0
  48. package/.medusa/server/src/api/admin/paypal/rotate-credentials/route.d.ts.map +1 -0
  49. package/.medusa/server/src/api/admin/paypal/rotate-credentials/route.js +9 -0
  50. package/.medusa/server/src/api/admin/paypal/rotate-credentials/route.js.map +1 -0
  51. package/.medusa/server/src/api/admin/paypal/save-credentials/route.d.ts +3 -0
  52. package/.medusa/server/src/api/admin/paypal/save-credentials/route.d.ts.map +1 -0
  53. package/.medusa/server/src/api/admin/paypal/save-credentials/route.js +13 -0
  54. package/.medusa/server/src/api/admin/paypal/save-credentials/route.js.map +1 -0
  55. package/.medusa/server/src/api/admin/paypal/settings/route.d.ts +4 -0
  56. package/.medusa/server/src/api/admin/paypal/settings/route.d.ts.map +1 -0
  57. package/.medusa/server/src/api/admin/paypal/settings/route.js +14 -0
  58. package/.medusa/server/src/api/admin/paypal/settings/route.js.map +1 -0
  59. package/.medusa/server/src/api/admin/paypal/status/route.d.ts +3 -0
  60. package/.medusa/server/src/api/admin/paypal/status/route.d.ts.map +1 -0
  61. package/.medusa/server/src/api/admin/paypal/status/route.js +11 -0
  62. package/.medusa/server/src/api/admin/paypal/status/route.js.map +1 -0
  63. package/.medusa/server/src/api/store/payment-collections/[id]/payment-sessions/route.d.ts +3 -0
  64. package/.medusa/server/src/api/store/payment-collections/[id]/payment-sessions/route.d.ts.map +1 -0
  65. package/.medusa/server/src/api/store/payment-collections/[id]/payment-sessions/route.js +43 -0
  66. package/.medusa/server/src/api/store/payment-collections/[id]/payment-sessions/route.js.map +1 -0
  67. package/.medusa/server/src/api/store/paypal/capture-order/route.d.ts +3 -0
  68. package/.medusa/server/src/api/store/paypal/capture-order/route.d.ts.map +1 -0
  69. package/.medusa/server/src/api/store/paypal/capture-order/route.js +215 -0
  70. package/.medusa/server/src/api/store/paypal/capture-order/route.js.map +1 -0
  71. package/.medusa/server/src/api/store/paypal/config/route.d.ts +3 -0
  72. package/.medusa/server/src/api/store/paypal/config/route.d.ts.map +1 -0
  73. package/.medusa/server/src/api/store/paypal/config/route.js +45 -0
  74. package/.medusa/server/src/api/store/paypal/config/route.js.map +1 -0
  75. package/.medusa/server/src/api/store/paypal/create-order/route.d.ts +3 -0
  76. package/.medusa/server/src/api/store/paypal/create-order/route.d.ts.map +1 -0
  77. package/.medusa/server/src/api/store/paypal/create-order/route.js +305 -0
  78. package/.medusa/server/src/api/store/paypal/create-order/route.js.map +1 -0
  79. package/.medusa/server/src/api/store/paypal/disputes/route.d.ts +3 -0
  80. package/.medusa/server/src/api/store/paypal/disputes/route.d.ts.map +1 -0
  81. package/.medusa/server/src/api/store/paypal/disputes/route.js +46 -0
  82. package/.medusa/server/src/api/store/paypal/disputes/route.js.map +1 -0
  83. package/.medusa/server/src/api/store/paypal/settings/route.d.ts +3 -0
  84. package/.medusa/server/src/api/store/paypal/settings/route.d.ts.map +1 -0
  85. package/.medusa/server/src/api/store/paypal/settings/route.js +14 -0
  86. package/.medusa/server/src/api/store/paypal/settings/route.js.map +1 -0
  87. package/.medusa/server/src/api/store/paypal/webhook/route.d.ts +3 -0
  88. package/.medusa/server/src/api/store/paypal/webhook/route.d.ts.map +1 -0
  89. package/.medusa/server/src/api/store/paypal/webhook/route.js +203 -0
  90. package/.medusa/server/src/api/store/paypal/webhook/route.js.map +1 -0
  91. package/.medusa/server/src/jobs/paypal-reconcile.d.ts +7 -0
  92. package/.medusa/server/src/jobs/paypal-reconcile.d.ts.map +1 -0
  93. package/.medusa/server/src/jobs/paypal-reconcile.js +131 -0
  94. package/.medusa/server/src/jobs/paypal-reconcile.js.map +1 -0
  95. package/.medusa/server/src/jobs/paypal-webhook-retry.d.ts +7 -0
  96. package/.medusa/server/src/jobs/paypal-webhook-retry.d.ts.map +1 -0
  97. package/.medusa/server/src/jobs/paypal-webhook-retry.js +78 -0
  98. package/.medusa/server/src/jobs/paypal-webhook-retry.js.map +1 -0
  99. package/.medusa/server/src/modules/paypal/clients/paypal-seller.client.d.ts +14 -0
  100. package/.medusa/server/src/modules/paypal/clients/paypal-seller.client.d.ts.map +1 -0
  101. package/.medusa/server/src/modules/paypal/clients/paypal-seller.client.js +65 -0
  102. package/.medusa/server/src/modules/paypal/clients/paypal-seller.client.js.map +1 -0
  103. package/.medusa/server/src/modules/paypal/index.d.ts +92 -0
  104. package/.medusa/server/src/modules/paypal/index.d.ts.map +1 -0
  105. package/.medusa/server/src/modules/paypal/index.js +13 -0
  106. package/.medusa/server/src/modules/paypal/index.js.map +1 -0
  107. package/.medusa/server/src/modules/paypal/migrations/20260115120000_create_paypal_connection.d.ts +6 -0
  108. package/.medusa/server/src/modules/paypal/migrations/20260115120000_create_paypal_connection.d.ts.map +1 -0
  109. package/.medusa/server/src/modules/paypal/migrations/20260115120000_create_paypal_connection.js +36 -0
  110. package/.medusa/server/src/modules/paypal/migrations/20260115120000_create_paypal_connection.js.map +1 -0
  111. package/.medusa/server/src/modules/paypal/migrations/20260123090000_create_paypal_settings.d.ts +6 -0
  112. package/.medusa/server/src/modules/paypal/migrations/20260123090000_create_paypal_settings.d.ts.map +1 -0
  113. package/.medusa/server/src/modules/paypal/migrations/20260123090000_create_paypal_settings.js +25 -0
  114. package/.medusa/server/src/modules/paypal/migrations/20260123090000_create_paypal_settings.js.map +1 -0
  115. package/.medusa/server/src/modules/paypal/migrations/20260201090000_create_paypal_webhook_event.d.ts +6 -0
  116. package/.medusa/server/src/modules/paypal/migrations/20260201090000_create_paypal_webhook_event.d.ts.map +1 -0
  117. package/.medusa/server/src/modules/paypal/migrations/20260201090000_create_paypal_webhook_event.js +32 -0
  118. package/.medusa/server/src/modules/paypal/migrations/20260201090000_create_paypal_webhook_event.js.map +1 -0
  119. package/.medusa/server/src/modules/paypal/migrations/20260301090000_create_paypal_audit_log.d.ts +6 -0
  120. package/.medusa/server/src/modules/paypal/migrations/20260301090000_create_paypal_audit_log.d.ts.map +1 -0
  121. package/.medusa/server/src/modules/paypal/migrations/20260301090000_create_paypal_audit_log.js +29 -0
  122. package/.medusa/server/src/modules/paypal/migrations/20260301090000_create_paypal_audit_log.js.map +1 -0
  123. package/.medusa/server/src/modules/paypal/migrations/20260401090000_create_paypal_metric.d.ts +6 -0
  124. package/.medusa/server/src/modules/paypal/migrations/20260401090000_create_paypal_metric.d.ts.map +1 -0
  125. package/.medusa/server/src/modules/paypal/migrations/20260401090000_create_paypal_metric.js +30 -0
  126. package/.medusa/server/src/modules/paypal/migrations/20260401090000_create_paypal_metric.js.map +1 -0
  127. package/.medusa/server/src/modules/paypal/migrations/20260501090000_create_paypal_dispute.d.ts +6 -0
  128. package/.medusa/server/src/modules/paypal/migrations/20260501090000_create_paypal_dispute.d.ts.map +1 -0
  129. package/.medusa/server/src/modules/paypal/migrations/20260501090000_create_paypal_dispute.js +43 -0
  130. package/.medusa/server/src/modules/paypal/migrations/20260501090000_create_paypal_dispute.js.map +1 -0
  131. package/.medusa/server/src/modules/paypal/migrations/20260701090000_add_paypal_webhook_event_processing.d.ts +6 -0
  132. package/.medusa/server/src/modules/paypal/migrations/20260701090000_add_paypal_webhook_event_processing.d.ts.map +1 -0
  133. package/.medusa/server/src/modules/paypal/migrations/20260701090000_add_paypal_webhook_event_processing.js +34 -0
  134. package/.medusa/server/src/modules/paypal/migrations/20260701090000_add_paypal_webhook_event_processing.js.map +1 -0
  135. package/.medusa/server/src/modules/paypal/models/paypal_audit_log.d.ts +7 -0
  136. package/.medusa/server/src/modules/paypal/models/paypal_audit_log.d.ts.map +1 -0
  137. package/.medusa/server/src/modules/paypal/models/paypal_audit_log.js +10 -0
  138. package/.medusa/server/src/modules/paypal/models/paypal_audit_log.js.map +1 -0
  139. package/.medusa/server/src/modules/paypal/models/paypal_connection.d.ts +14 -0
  140. package/.medusa/server/src/modules/paypal/models/paypal_connection.d.ts.map +1 -0
  141. package/.medusa/server/src/modules/paypal/models/paypal_connection.js +17 -0
  142. package/.medusa/server/src/modules/paypal/models/paypal_connection.js.map +1 -0
  143. package/.medusa/server/src/modules/paypal/models/paypal_dispute.d.ts +16 -0
  144. package/.medusa/server/src/modules/paypal/models/paypal_dispute.d.ts.map +1 -0
  145. package/.medusa/server/src/modules/paypal/models/paypal_dispute.js +19 -0
  146. package/.medusa/server/src/modules/paypal/models/paypal_dispute.js.map +1 -0
  147. package/.medusa/server/src/modules/paypal/models/paypal_metric.d.ts +7 -0
  148. package/.medusa/server/src/modules/paypal/models/paypal_metric.d.ts.map +1 -0
  149. package/.medusa/server/src/modules/paypal/models/paypal_metric.js +10 -0
  150. package/.medusa/server/src/modules/paypal/models/paypal_metric.js.map +1 -0
  151. package/.medusa/server/src/modules/paypal/models/paypal_settings.d.ts +6 -0
  152. package/.medusa/server/src/modules/paypal/models/paypal_settings.d.ts.map +1 -0
  153. package/.medusa/server/src/modules/paypal/models/paypal_settings.js +9 -0
  154. package/.medusa/server/src/modules/paypal/models/paypal_settings.js.map +1 -0
  155. package/.medusa/server/src/modules/paypal/models/paypal_webhook_event.d.ts +17 -0
  156. package/.medusa/server/src/modules/paypal/models/paypal_webhook_event.d.ts.map +1 -0
  157. package/.medusa/server/src/modules/paypal/models/paypal_webhook_event.js +20 -0
  158. package/.medusa/server/src/modules/paypal/models/paypal_webhook_event.js.map +1 -0
  159. package/.medusa/server/src/modules/paypal/payment-provider/card-service.d.ts +35 -0
  160. package/.medusa/server/src/modules/paypal/payment-provider/card-service.d.ts.map +1 -0
  161. package/.medusa/server/src/modules/paypal/payment-provider/card-service.js +569 -0
  162. package/.medusa/server/src/modules/paypal/payment-provider/card-service.js.map +1 -0
  163. package/.medusa/server/src/modules/paypal/payment-provider/index.d.ts +10 -0
  164. package/.medusa/server/src/modules/paypal/payment-provider/index.d.ts.map +1 -0
  165. package/.medusa/server/src/modules/paypal/payment-provider/index.js +22 -0
  166. package/.medusa/server/src/modules/paypal/payment-provider/index.js.map +1 -0
  167. package/.medusa/server/src/modules/paypal/payment-provider/service.d.ts +44 -0
  168. package/.medusa/server/src/modules/paypal/payment-provider/service.d.ts.map +1 -0
  169. package/.medusa/server/src/modules/paypal/payment-provider/service.js +825 -0
  170. package/.medusa/server/src/modules/paypal/payment-provider/service.js.map +1 -0
  171. package/.medusa/server/src/modules/paypal/payment-provider/webhook-utils.d.ts +3 -0
  172. package/.medusa/server/src/modules/paypal/payment-provider/webhook-utils.d.ts.map +1 -0
  173. package/.medusa/server/src/modules/paypal/payment-provider/webhook-utils.js +74 -0
  174. package/.medusa/server/src/modules/paypal/payment-provider/webhook-utils.js.map +1 -0
  175. package/.medusa/server/src/modules/paypal/service.d.ts +362 -0
  176. package/.medusa/server/src/modules/paypal/service.d.ts.map +1 -0
  177. package/.medusa/server/src/modules/paypal/service.js +1180 -0
  178. package/.medusa/server/src/modules/paypal/service.js.map +1 -0
  179. package/.medusa/server/src/modules/paypal/types/config.d.ts +14 -0
  180. package/.medusa/server/src/modules/paypal/types/config.d.ts.map +1 -0
  181. package/.medusa/server/src/modules/paypal/types/config.js +33 -0
  182. package/.medusa/server/src/modules/paypal/types/config.js.map +1 -0
  183. package/.medusa/server/src/modules/paypal/utils/amounts.d.ts +3 -0
  184. package/.medusa/server/src/modules/paypal/utils/amounts.d.ts.map +1 -0
  185. package/.medusa/server/src/modules/paypal/utils/amounts.js +40 -0
  186. package/.medusa/server/src/modules/paypal/utils/amounts.js.map +1 -0
  187. package/.medusa/server/src/modules/paypal/utils/crypto.d.ts +4 -0
  188. package/.medusa/server/src/modules/paypal/utils/crypto.d.ts.map +1 -0
  189. package/.medusa/server/src/modules/paypal/utils/crypto.js +47 -0
  190. package/.medusa/server/src/modules/paypal/utils/crypto.js.map +1 -0
  191. package/.medusa/server/src/modules/paypal/utils/currencies.d.ts +19 -0
  192. package/.medusa/server/src/modules/paypal/utils/currencies.d.ts.map +1 -0
  193. package/.medusa/server/src/modules/paypal/utils/currencies.js +69 -0
  194. package/.medusa/server/src/modules/paypal/utils/currencies.js.map +1 -0
  195. package/.medusa/server/src/modules/paypal/utils/provider-ids.d.ts +9 -0
  196. package/.medusa/server/src/modules/paypal/utils/provider-ids.d.ts.map +1 -0
  197. package/.medusa/server/src/modules/paypal/utils/provider-ids.js +50 -0
  198. package/.medusa/server/src/modules/paypal/utils/provider-ids.js.map +1 -0
  199. package/.medusa/server/src/modules/paypal/webhook-processor.d.ts +38 -0
  200. package/.medusa/server/src/modules/paypal/webhook-processor.d.ts.map +1 -0
  201. package/.medusa/server/src/modules/paypal/webhook-processor.js +265 -0
  202. package/.medusa/server/src/modules/paypal/webhook-processor.js.map +1 -0
  203. package/LICENSE +21 -0
  204. package/README.md +67 -0
  205. package/package.json +61 -0
  206. package/postcss.config.cjs +3 -0
  207. package/src/admin/index.ts +7 -0
  208. package/src/admin/routes/settings/paypal/_components/Tabs.tsx +55 -0
  209. package/src/admin/routes/settings/paypal/_components/Toast.tsx +51 -0
  210. package/src/admin/routes/settings/paypal/additional-settings/page.tsx +346 -0
  211. package/src/admin/routes/settings/paypal/advanced-card-payments/page.tsx +381 -0
  212. package/src/admin/routes/settings/paypal/apple-pay/page.tsx +5 -0
  213. package/src/admin/routes/settings/paypal/audit-logs/page.tsx +131 -0
  214. package/src/admin/routes/settings/paypal/connection/page.tsx +750 -0
  215. package/src/admin/routes/settings/paypal/disputes/page.tsx +259 -0
  216. package/src/admin/routes/settings/paypal/google-pay/page.tsx +5 -0
  217. package/src/admin/routes/settings/paypal/page.tsx +16 -0
  218. package/src/admin/routes/settings/paypal/pay-later-messaging/page.tsx +5 -0
  219. package/src/admin/routes/settings/paypal/paypal-settings/page.tsx +557 -0
  220. package/src/admin/routes/settings/paypal/reconciliation-status/page.tsx +165 -0
  221. package/src/api/admin/payment-collections/[id]/payment-sessions/route.ts +32 -0
  222. package/src/api/admin/paypal/audit-logs/route.ts +13 -0
  223. package/src/api/admin/paypal/disconnect/route.ts +8 -0
  224. package/src/api/admin/paypal/disputes/[id]/route.ts +19 -0
  225. package/src/api/admin/paypal/disputes/route.ts +30 -0
  226. package/src/api/admin/paypal/disputes/summary/route.ts +18 -0
  227. package/src/api/admin/paypal/environment/route.ts +25 -0
  228. package/src/api/admin/paypal/onboard-complete/route.ts +44 -0
  229. package/src/api/admin/paypal/onboarding-link/route.ts +45 -0
  230. package/src/api/admin/paypal/onboarding-status/route.ts +18 -0
  231. package/src/api/admin/paypal/reconciliation-status/route.ts +7 -0
  232. package/src/api/admin/paypal/rotate-credentials/route.ts +8 -0
  233. package/src/api/admin/paypal/save-credentials/route.ts +14 -0
  234. package/src/api/admin/paypal/settings/route.ts +14 -0
  235. package/src/api/admin/paypal/status/route.ts +12 -0
  236. package/src/api/store/payment-collections/[id]/payment-sessions/route.ts +51 -0
  237. package/src/api/store/paypal/capture-order/route.ts +270 -0
  238. package/src/api/store/paypal/config/route.ts +59 -0
  239. package/src/api/store/paypal/create-order/route.ts +374 -0
  240. package/src/api/store/paypal/disputes/route.ts +67 -0
  241. package/src/api/store/paypal/settings/route.ts +12 -0
  242. package/src/api/store/paypal/webhook/route.ts +247 -0
  243. package/src/jobs/paypal-reconcile.ts +135 -0
  244. package/src/jobs/paypal-webhook-retry.ts +86 -0
  245. package/src/modules/paypal/clients/paypal-seller.client.ts +59 -0
  246. package/src/modules/paypal/index.ts +8 -0
  247. package/src/modules/paypal/migrations/20260115120000_create_paypal_connection.ts +33 -0
  248. package/src/modules/paypal/migrations/20260123090000_create_paypal_settings.ts +22 -0
  249. package/src/modules/paypal/migrations/20260201090000_create_paypal_webhook_event.ts +29 -0
  250. package/src/modules/paypal/migrations/20260301090000_create_paypal_audit_log.ts +26 -0
  251. package/src/modules/paypal/migrations/20260401090000_create_paypal_metric.ts +27 -0
  252. package/src/modules/paypal/migrations/20260501090000_create_paypal_dispute.ts +40 -0
  253. package/src/modules/paypal/migrations/20260701090000_add_paypal_webhook_event_processing.ts +31 -0
  254. package/src/modules/paypal/models/paypal_audit_log.ts +9 -0
  255. package/src/modules/paypal/models/paypal_connection.ts +21 -0
  256. package/src/modules/paypal/models/paypal_dispute.ts +18 -0
  257. package/src/modules/paypal/models/paypal_metric.ts +9 -0
  258. package/src/modules/paypal/models/paypal_settings.ts +8 -0
  259. package/src/modules/paypal/models/paypal_webhook_event.ts +19 -0
  260. package/src/modules/paypal/payment-provider/README.md +22 -0
  261. package/src/modules/paypal/payment-provider/card-service.ts +710 -0
  262. package/src/modules/paypal/payment-provider/index.ts +19 -0
  263. package/src/modules/paypal/payment-provider/service.ts +1035 -0
  264. package/src/modules/paypal/payment-provider/webhook-utils.ts +88 -0
  265. package/src/modules/paypal/service.ts +1422 -0
  266. package/src/modules/paypal/types/config.ts +47 -0
  267. package/src/modules/paypal/utils/amounts.ts +41 -0
  268. package/src/modules/paypal/utils/crypto.ts +51 -0
  269. package/src/modules/paypal/utils/currencies.ts +84 -0
  270. package/src/modules/paypal/utils/provider-ids.ts +53 -0
  271. package/src/modules/paypal/webhook-processor.ts +313 -0
  272. package/tsconfig.json +31 -0
@@ -0,0 +1,1035 @@
1
+ import { AbstractPaymentProvider } from "@medusajs/framework/utils"
2
+ import { randomUUID } from "crypto"
3
+ import type {
4
+ AuthorizePaymentInput,
5
+ AuthorizePaymentOutput,
6
+ CapturePaymentInput,
7
+ CapturePaymentOutput,
8
+ CancelPaymentInput,
9
+ CancelPaymentOutput,
10
+ CreateAccountHolderInput,
11
+ CreateAccountHolderOutput,
12
+ DeletePaymentInput,
13
+ DeletePaymentOutput,
14
+ GetPaymentStatusInput,
15
+ GetPaymentStatusOutput,
16
+ InitiatePaymentInput,
17
+ InitiatePaymentOutput,
18
+ RefundPaymentInput,
19
+ RefundPaymentOutput,
20
+ RetrievePaymentInput,
21
+ RetrievePaymentOutput,
22
+ UpdatePaymentInput,
23
+ UpdatePaymentOutput,
24
+ ProviderWebhookPayload,
25
+ WebhookActionResult,
26
+ } from "@medusajs/framework/types"
27
+ import { formatAmountForPayPal } from "../utils/amounts"
28
+ import {
29
+ assertPayPalCurrencySupported,
30
+ normalizeCurrencyCode,
31
+ } from "../utils/currencies"
32
+ import { normalizePayPalProviderId } from "../utils/provider-ids"
33
+ import type PayPalModuleService from "../service"
34
+ import { getPayPalWebhookActionAndData } from "./webhook-utils"
35
+
36
+ type Options = {}
37
+
38
+ const LEGACY_PROVIDER_ID_MAP: Record<string, string> = {
39
+ pp_paypal_paypal: "paypal",
40
+ pp_paypal_card_paypal: "paypal_card",
41
+ pp: "paypal",
42
+ card: "paypal_card",
43
+ }
44
+
45
+ function normalizeProviderId(providerId?: string | null) {
46
+ if (!providerId) {
47
+ return providerId
48
+ }
49
+ return LEGACY_PROVIDER_ID_MAP[providerId] ?? providerId
50
+ }
51
+
52
+ function generateSessionId() {
53
+ try {
54
+ return randomUUID()
55
+ } catch {
56
+ // Fallback for environments where randomUUID isn't available
57
+ return `pp_${Date.now()}_${Math.random().toString(16).slice(2)}`
58
+ }
59
+ }
60
+
61
+ class PayPalPaymentProvider extends AbstractPaymentProvider<Options> {
62
+ static identifier = "paypal"
63
+
64
+ protected readonly options_: Options
65
+
66
+ constructor(cradle: Record<string, any>, options: Options) {
67
+ super(cradle, options)
68
+ this.options_ = options
69
+ }
70
+
71
+ private resolvePayPalService() {
72
+ const container = this.container as {
73
+ resolve<T>(key: string): T
74
+ }
75
+ return container.resolve<PayPalModuleService>("paypal_onboarding")
76
+ }
77
+
78
+ private async resolveSettings() {
79
+ const paypal = this.resolvePayPalService()
80
+ const settings = await paypal.getSettings().catch(() => ({}))
81
+ const data =
82
+ settings && typeof settings === "object" && "data" in settings
83
+ ? ((settings as { data?: Record<string, any> }).data ?? {})
84
+ : {}
85
+ return {
86
+ additionalSettings: (data.additional_settings || {}) as Record<string, any>,
87
+ apiDetails: (data.api_details || {}) as Record<string, any>,
88
+ }
89
+ }
90
+
91
+ private async resolveCurrencyOverride() {
92
+ const { apiDetails } = await this.resolveSettings()
93
+ if (typeof apiDetails.currency_code === "string" && apiDetails.currency_code.trim()) {
94
+ return normalizeCurrencyCode(apiDetails.currency_code)
95
+ }
96
+ return normalizeCurrencyCode(process.env.PAYPAL_CURRENCY || "USD")
97
+ }
98
+
99
+ private async getPayPalAccessToken() {
100
+ const paypal = this.resolvePayPalService()
101
+ const creds = await paypal.getActiveCredentials()
102
+ const base =
103
+ creds.environment === "live"
104
+ ? "https://api-m.paypal.com"
105
+ : "https://api-m.sandbox.paypal.com"
106
+ const auth = Buffer.from(`${creds.client_id}:${creds.client_secret}`).toString("base64")
107
+
108
+ const resp = await fetch(`${base}/v1/oauth2/token`, {
109
+ method: "POST",
110
+ headers: {
111
+ Authorization: `Basic ${auth}`,
112
+ "Content-Type": "application/x-www-form-urlencoded",
113
+ },
114
+ body: "grant_type=client_credentials",
115
+ })
116
+
117
+ const text = await resp.text()
118
+ if (!resp.ok) {
119
+ throw new Error(`PayPal token error (${resp.status}): ${text}`)
120
+ }
121
+
122
+ const json = JSON.parse(text)
123
+ return { accessToken: String(json.access_token), base }
124
+ }
125
+
126
+ private async getOrderDetails(orderId: string) {
127
+ const { accessToken, base } = await this.getPayPalAccessToken()
128
+ const resp = await fetch(`${base}/v2/checkout/orders/${orderId}`, {
129
+ method: "GET",
130
+ headers: {
131
+ Authorization: `Bearer ${accessToken}`,
132
+ "Content-Type": "application/json",
133
+ },
134
+ })
135
+
136
+ const text = await resp.text()
137
+ if (!resp.ok) {
138
+ throw new Error(`PayPal get order error (${resp.status}): ${text}`)
139
+ }
140
+
141
+ return JSON.parse(text)
142
+ }
143
+
144
+ private getIdempotencyKey(input: { context?: { idempotency_key?: string } }, suffix: string) {
145
+ const key = input?.context?.idempotency_key?.trim()
146
+ if (key) {
147
+ return `${key}-${suffix}`
148
+ }
149
+ return `pp-${suffix}-${generateSessionId()}`
150
+ }
151
+
152
+ private async normalizePaymentData(input: { data?: Record<string, unknown> }) {
153
+ const data = (input.data || {}) as Record<string, any>
154
+ const amount = Number(data.amount ?? 0)
155
+ const currencyOverride = await this.resolveCurrencyOverride()
156
+ const currencyCode = normalizeCurrencyCode(
157
+ data.currency_code || currencyOverride || "USD"
158
+ )
159
+ assertPayPalCurrencySupported({
160
+ currencyCode,
161
+ paypalCurrencyOverride: currencyOverride,
162
+ })
163
+ return { data, amount, currencyCode }
164
+ }
165
+
166
+ private mapCaptureStatus(status?: string) {
167
+ const normalized = String(status || "").toUpperCase()
168
+ if (!normalized) {
169
+ return null
170
+ }
171
+
172
+ if (normalized === "COMPLETED") {
173
+ return "captured"
174
+ }
175
+
176
+ if (normalized === "PENDING") {
177
+ return "pending"
178
+ }
179
+
180
+ if (["DENIED", "DECLINED", "FAILED"].includes(normalized)) {
181
+ return "error"
182
+ }
183
+
184
+ if (["REFUNDED", "PARTIALLY_REFUNDED", "REVERSED"].includes(normalized)) {
185
+ return "canceled"
186
+ }
187
+
188
+ return null
189
+ }
190
+
191
+ private mapAuthorizationStatus(status?: string) {
192
+ const normalized = String(status || "").toUpperCase()
193
+ if (!normalized) {
194
+ return null
195
+ }
196
+
197
+ if (["CREATED", "APPROVED", "PENDING"].includes(normalized)) {
198
+ return "authorized"
199
+ }
200
+
201
+ if (["VOIDED", "EXPIRED"].includes(normalized)) {
202
+ return "canceled"
203
+ }
204
+
205
+ if (["DENIED", "DECLINED", "FAILED"].includes(normalized)) {
206
+ return "error"
207
+ }
208
+
209
+ return null
210
+ }
211
+
212
+ private serializeError(error: unknown) {
213
+ if (error instanceof Error) {
214
+ const errorWithCause = error as Error & { cause?: unknown }
215
+ const cause = errorWithCause.cause
216
+ return {
217
+ name: error.name,
218
+ message: error.message,
219
+ stack: error.stack,
220
+ cause:
221
+ cause instanceof Error
222
+ ? {
223
+ name: cause.name,
224
+ message: cause.message,
225
+ stack: cause.stack,
226
+ }
227
+ : cause,
228
+ }
229
+ }
230
+
231
+ return {
232
+ message: String(error),
233
+ }
234
+ }
235
+
236
+ private mapOrderStatus(status?: string) {
237
+ const normalized = String(status || "").toUpperCase()
238
+ if (!normalized) {
239
+ return "pending"
240
+ }
241
+
242
+ if (normalized === "COMPLETED") {
243
+ return "captured"
244
+ }
245
+
246
+ if (normalized === "APPROVED") {
247
+ return "authorized"
248
+ }
249
+
250
+ if (["VOIDED", "CANCELLED"].includes(normalized)) {
251
+ return "canceled"
252
+ }
253
+
254
+ if (["CREATED", "SAVED", "PAYER_ACTION_REQUIRED"].includes(normalized)) {
255
+ return "pending"
256
+ }
257
+
258
+ if (["FAILED", "EXPIRED"].includes(normalized)) {
259
+ return "error"
260
+ }
261
+
262
+ return "pending"
263
+ }
264
+
265
+ private async recordFailure(eventType: string, metadata?: Record<string, unknown>) {
266
+ try {
267
+ const paypal = this.resolvePayPalService()
268
+ await paypal.recordPaymentLog(eventType, metadata)
269
+ await paypal.recordAuditEvent(eventType, metadata)
270
+ await paypal.recordMetric(eventType)
271
+ } catch {
272
+ // ignore audit logging failures
273
+ }
274
+ }
275
+
276
+ private async recordSuccess(metricName: string) {
277
+ try {
278
+ const paypal = this.resolvePayPalService()
279
+ await paypal.recordMetric(metricName)
280
+ } catch {
281
+ // ignore metrics failures
282
+ }
283
+ }
284
+
285
+ private async recordPaymentEvent(eventType: string, metadata?: Record<string, unknown>) {
286
+ try {
287
+ const paypal = this.resolvePayPalService()
288
+ await paypal.recordPaymentLog(eventType, metadata)
289
+ } catch {
290
+ // ignore payment logging failures
291
+ }
292
+ }
293
+
294
+ async createAccountHolder(
295
+ input: CreateAccountHolderInput
296
+ ): Promise<CreateAccountHolderOutput> {
297
+ const customerId = input.context?.customer?.id
298
+ const externalId = customerId ? `paypal_${customerId}` : `paypal_${generateSessionId()}`
299
+
300
+ return {
301
+ id: externalId,
302
+ data: {
303
+ email: input.context?.customer?.email || null,
304
+ customer_id: customerId || null,
305
+ },
306
+ }
307
+ }
308
+
309
+ /**
310
+ * Create a payment session when the customer selects PayPal.
311
+ * Must return an object containing an `id` and `data`.
312
+ */
313
+ async initiatePayment(
314
+ input: InitiatePaymentInput
315
+ ): Promise<InitiatePaymentOutput> {
316
+ try {
317
+ const currencyOverride = await this.resolveCurrencyOverride()
318
+ const currencyCode = normalizeCurrencyCode(
319
+ input.currency_code || currencyOverride || "USD"
320
+ )
321
+ assertPayPalCurrencySupported({
322
+ currencyCode,
323
+ paypalCurrencyOverride: currencyOverride,
324
+ })
325
+
326
+ const providerId = normalizeProviderId(
327
+ (input.data as Record<string, any> | undefined)?.provider_id
328
+ )
329
+
330
+ return {
331
+ id: generateSessionId(),
332
+ data: {
333
+ ...(input.data || {}),
334
+ ...(providerId ? { provider_id: providerId } : {}),
335
+ amount: input.amount,
336
+ currency_code: currencyCode,
337
+ },
338
+ }
339
+ } catch (error) {
340
+ await this.recordFailure("initiate_failed", {
341
+ error: this.serializeError(error),
342
+ currency_code: input.currency_code,
343
+ amount: input.amount,
344
+ provider_id: (input.data as Record<string, any> | undefined)?.provider_id,
345
+ data: input.data ?? null,
346
+ })
347
+ throw error
348
+ }
349
+ }
350
+
351
+ async updatePayment(input: UpdatePaymentInput): Promise<UpdatePaymentOutput> {
352
+ const currencyOverride = await this.resolveCurrencyOverride()
353
+ const currencyCode = normalizeCurrencyCode(
354
+ input.currency_code || currencyOverride || "USD"
355
+ )
356
+ assertPayPalCurrencySupported({
357
+ currencyCode,
358
+ paypalCurrencyOverride: currencyOverride,
359
+ })
360
+
361
+ const providerId = normalizeProviderId(
362
+ (input.data as Record<string, any> | undefined)?.provider_id
363
+ )
364
+
365
+ return {
366
+ data: {
367
+ ...(input.data || {}),
368
+ ...(providerId ? { provider_id: providerId } : {}),
369
+ amount: input.amount,
370
+ currency_code: currencyCode,
371
+ },
372
+ }
373
+ }
374
+
375
+ async authorizePayment(
376
+ input: AuthorizePaymentInput
377
+ ): Promise<AuthorizePaymentOutput> {
378
+ const { data, amount, currencyCode } = await this.normalizePaymentData(input)
379
+ const requestId = this.getIdempotencyKey(input, "authorize")
380
+ let debugId: string | null = null
381
+ const { additionalSettings } = await this.resolveSettings()
382
+ const paymentActionRaw =
383
+ typeof additionalSettings.paymentAction === "string"
384
+ ? additionalSettings.paymentAction
385
+ : "capture"
386
+ const orderIntent = paymentActionRaw === "authorize" ? "AUTHORIZE" : "CAPTURE"
387
+
388
+ try {
389
+ const { accessToken, base } = await this.getPayPalAccessToken()
390
+ const existingPayPal = (data.paypal || {}) as Record<string, any>
391
+ let orderId = String(existingPayPal.order_id || data.order_id || "")
392
+ let order: Record<string, any> | null = null
393
+ let authorization: any = null
394
+
395
+ if (!orderId) {
396
+ const value = formatAmountForPayPal(amount, currencyCode || "USD")
397
+
398
+ const orderPayload = {
399
+ intent: orderIntent,
400
+ purchase_units: [
401
+ {
402
+ reference_id: data.cart_id || data.payment_collection_id || undefined,
403
+ custom_id: data.session_id || data.cart_id || data.payment_collection_id || undefined,
404
+ amount: {
405
+ currency_code: currencyCode || "USD",
406
+ value,
407
+ },
408
+ },
409
+ ],
410
+ custom_id: data.session_id || data.cart_id || data.payment_collection_id || undefined,
411
+ }
412
+
413
+ const ppResp = await fetch(`${base}/v2/checkout/orders`, {
414
+ method: "POST",
415
+ headers: {
416
+ Authorization: `Bearer ${accessToken}`,
417
+ "Content-Type": "application/json",
418
+ "PayPal-Request-Id": requestId,
419
+ },
420
+ body: JSON.stringify(orderPayload),
421
+ })
422
+
423
+ const ppText = await ppResp.text()
424
+ debugId = ppResp.headers.get("paypal-debug-id")
425
+ if (!ppResp.ok) {
426
+ throw new Error(
427
+ `PayPal create order error (${ppResp.status}): ${ppText}${
428
+ debugId ? ` debug_id=${debugId}` : ""
429
+ }`
430
+ )
431
+ }
432
+
433
+ order = JSON.parse(ppText) as Record<string, any>
434
+ orderId = String(order.id || "")
435
+ } else {
436
+ order = (await this.getOrderDetails(orderId)) as Record<string, any> | null
437
+ }
438
+
439
+ if (!order || !orderId) {
440
+ throw new Error("Unable to resolve PayPal order details for authorization.")
441
+ }
442
+
443
+ const existingAuthorization =
444
+ order?.purchase_units?.[0]?.payments?.authorizations?.[0] || null
445
+
446
+ if (existingAuthorization) {
447
+ authorization = order
448
+ } else {
449
+ const authorizeResp = await fetch(
450
+ `${base}/v2/checkout/orders/${orderId}/authorize`,
451
+ {
452
+ method: "POST",
453
+ headers: {
454
+ Authorization: `Bearer ${accessToken}`,
455
+ "Content-Type": "application/json",
456
+ "PayPal-Request-Id": `${requestId}-auth`,
457
+ },
458
+ }
459
+ )
460
+
461
+ const authorizeText = await authorizeResp.text()
462
+ const authorizeDebugId = authorizeResp.headers.get("paypal-debug-id")
463
+ if (!authorizeResp.ok) {
464
+ throw new Error(
465
+ `PayPal authorize order error (${authorizeResp.status}): ${authorizeText}${
466
+ authorizeDebugId ? ` debug_id=${authorizeDebugId}` : ""
467
+ }`
468
+ )
469
+ }
470
+
471
+ authorization = JSON.parse(authorizeText)
472
+ }
473
+
474
+ const authorizationId =
475
+ authorization?.purchase_units?.[0]?.payments?.authorizations?.[0]?.id ||
476
+ existingAuthorization?.id
477
+
478
+ console.info("[PayPal] provider authorize", {
479
+ order_id: orderId,
480
+ authorization_id: authorizationId,
481
+ request_id: requestId,
482
+ debug_id: debugId,
483
+ })
484
+ await this.recordSuccess("authorize_success")
485
+ await this.recordPaymentEvent("authorize", {
486
+ order_id: orderId,
487
+ authorization_id: authorizationId,
488
+ amount,
489
+ currency_code: currencyCode,
490
+ request_id: requestId,
491
+ })
492
+
493
+ return {
494
+ status: "authorized",
495
+ data: {
496
+ ...(input.data || {}),
497
+ paypal: {
498
+ ...((input.data || {}).paypal as Record<string, unknown>),
499
+ order_id: orderId,
500
+ order: order || authorization,
501
+ authorization_id: authorizationId,
502
+ authorizations:
503
+ authorization?.purchase_units?.[0]?.payments?.authorizations || [],
504
+ },
505
+ authorized_at: new Date().toISOString(),
506
+ },
507
+ }
508
+ } catch (error: any) {
509
+ await this.recordFailure("authorize_failed", {
510
+ request_id: requestId,
511
+ cart_id: data.cart_id,
512
+ payment_collection_id: data.payment_collection_id,
513
+ debug_id: debugId,
514
+ message: error?.message,
515
+ })
516
+ throw error
517
+ }
518
+ }
519
+
520
+ async retrievePayment(
521
+ input: RetrievePaymentInput
522
+ ): Promise<RetrievePaymentOutput> {
523
+ const data = (input.data || {}) as Record<string, any>
524
+ const paypalData = (data.paypal || {}) as Record<string, any>
525
+ const orderId = String(paypalData.order_id || data.order_id || "")
526
+ if (!orderId) {
527
+ return { data: { ...(input.data || {}) } }
528
+ }
529
+
530
+ const order = await this.getOrderDetails(orderId)
531
+ const capture = order?.purchase_units?.[0]?.payments?.captures?.[0]
532
+ const authorization = order?.purchase_units?.[0]?.payments?.authorizations?.[0]
533
+
534
+ return {
535
+ data: {
536
+ ...(input.data || {}),
537
+ paypal: {
538
+ ...((input.data || {}).paypal as Record<string, unknown>),
539
+ order,
540
+ authorization_id: authorization?.id || paypalData.authorization_id,
541
+ capture_id: capture?.id || paypalData.capture_id,
542
+ },
543
+ },
544
+ }
545
+ }
546
+
547
+ async getPaymentStatus(
548
+ input: GetPaymentStatusInput
549
+ ): Promise<GetPaymentStatusOutput> {
550
+ const data = (input.data || {}) as Record<string, any>
551
+ const paypalData = (data.paypal || {}) as Record<string, any>
552
+ const orderId = String(paypalData.order_id || data.order_id || "")
553
+ if (!orderId) {
554
+ return { status: "pending", data: { ...(input.data || {}) } }
555
+ }
556
+
557
+ try {
558
+ const order = await this.getOrderDetails(orderId)
559
+ const capture = order?.purchase_units?.[0]?.payments?.captures?.[0]
560
+ const authorization = order?.purchase_units?.[0]?.payments?.authorizations?.[0]
561
+ const mappedStatus =
562
+ this.mapCaptureStatus(capture?.status) ||
563
+ this.mapAuthorizationStatus(authorization?.status) ||
564
+ this.mapOrderStatus(order?.status) ||
565
+ "pending"
566
+
567
+ await this.recordSuccess("status_success")
568
+ return {
569
+ status: mappedStatus,
570
+ data: {
571
+ ...(input.data || {}),
572
+ paypal: {
573
+ ...((input.data || {}).paypal as Record<string, unknown>),
574
+ order,
575
+ authorization_id: authorization?.id || paypalData.authorization_id,
576
+ capture_id: capture?.id || paypalData.capture_id,
577
+ },
578
+ },
579
+ }
580
+ } catch (error: any) {
581
+ await this.recordFailure("status_failed", {
582
+ order_id: orderId,
583
+ message: error?.message,
584
+ })
585
+ throw error
586
+ }
587
+ }
588
+
589
+ async capturePayment(
590
+ input: CapturePaymentInput
591
+ ): Promise<CapturePaymentOutput> {
592
+ const data = (input.data || {}) as Record<string, any>
593
+ const paypalData = (data.paypal || {}) as Record<string, any>
594
+ const orderId = String(paypalData.order_id || data.order_id || "")
595
+ let authorizationId = String(
596
+ paypalData.authorization_id || data.authorization_id || ""
597
+ )
598
+ if (!orderId) {
599
+ throw new Error("PayPal order_id is required to capture payment")
600
+ }
601
+
602
+ if (paypalData.capture_id || paypalData.capture) {
603
+ return {
604
+ data: {
605
+ ...(input.data || {}),
606
+ paypal: {
607
+ ...((input.data || {}).paypal as Record<string, unknown>),
608
+ capture_id: paypalData.capture_id,
609
+ capture: paypalData.capture,
610
+ },
611
+ captured_at: new Date().toISOString(),
612
+ },
613
+ }
614
+ }
615
+
616
+ const requestId = this.getIdempotencyKey(input, `capture-${orderId}`)
617
+ const { amount, currencyCode } = await this.normalizePaymentData(input)
618
+ let debugId: string | null = null
619
+
620
+ try {
621
+ const { accessToken, base } = await this.getPayPalAccessToken()
622
+ const order = await this.getOrderDetails(orderId).catch(() => null)
623
+ const existingCapture = order?.purchase_units?.[0]?.payments?.captures?.[0]
624
+ if (existingCapture?.id) {
625
+ return {
626
+ data: {
627
+ ...(input.data || {}),
628
+ paypal: {
629
+ ...((input.data || {}).paypal as Record<string, unknown>),
630
+ capture_id: existingCapture.id,
631
+ capture: existingCapture,
632
+ },
633
+ captured_at: new Date().toISOString(),
634
+ },
635
+ }
636
+ }
637
+ const resolvedIntent = String(
638
+ order?.intent || paypalData.order?.intent || data.intent || ""
639
+ ).toUpperCase()
640
+ if (!authorizationId && resolvedIntent === "AUTHORIZE") {
641
+ const authorizeResp = await fetch(
642
+ `${base}/v2/checkout/orders/${orderId}/authorize`,
643
+ {
644
+ method: "POST",
645
+ headers: {
646
+ Authorization: `Bearer ${accessToken}`,
647
+ "Content-Type": "application/json",
648
+ "PayPal-Request-Id": `${requestId}-auth`,
649
+ },
650
+ }
651
+ )
652
+ const authorizeText = await authorizeResp.text()
653
+ debugId = authorizeResp.headers.get("paypal-debug-id")
654
+ if (!authorizeResp.ok) {
655
+ throw new Error(
656
+ `PayPal authorize order error (${authorizeResp.status}): ${authorizeText}${
657
+ debugId ? ` debug_id=${debugId}` : ""
658
+ }`
659
+ )
660
+ }
661
+ const authorization = JSON.parse(authorizeText)
662
+ authorizationId =
663
+ authorization?.purchase_units?.[0]?.payments?.authorizations?.[0]?.id
664
+ }
665
+
666
+ const isFinalCapture =
667
+ paypalData.is_final_capture ??
668
+ data.is_final_capture ??
669
+ data.final_capture ??
670
+ undefined
671
+ const capturePayload =
672
+ amount > 0
673
+ ? {
674
+ amount: {
675
+ currency_code: currencyCode || "USD",
676
+ value: formatAmountForPayPal(amount, currencyCode || "USD"),
677
+ },
678
+ ...(typeof isFinalCapture === "boolean"
679
+ ? { is_final_capture: isFinalCapture }
680
+ : {}),
681
+ }
682
+ : {
683
+ ...(typeof isFinalCapture === "boolean"
684
+ ? { is_final_capture: isFinalCapture }
685
+ : {}),
686
+ }
687
+
688
+ const captureUrl = authorizationId
689
+ ? `${base}/v2/payments/authorizations/${authorizationId}/capture`
690
+ : `${base}/v2/checkout/orders/${orderId}/capture`
691
+
692
+ const ppResp = await fetch(captureUrl, {
693
+ method: "POST",
694
+ headers: {
695
+ Authorization: `Bearer ${accessToken}`,
696
+ "Content-Type": "application/json",
697
+ "PayPal-Request-Id": requestId,
698
+ },
699
+ body: JSON.stringify(capturePayload),
700
+ })
701
+
702
+ const ppText = await ppResp.text()
703
+ debugId = ppResp.headers.get("paypal-debug-id")
704
+ if (!ppResp.ok) {
705
+ throw new Error(
706
+ `PayPal capture error (${ppResp.status}): ${ppText}${
707
+ debugId ? ` debug_id=${debugId}` : ""
708
+ }`
709
+ )
710
+ }
711
+
712
+ const capture = JSON.parse(ppText)
713
+ const captureId =
714
+ capture?.id || capture?.purchase_units?.[0]?.payments?.captures?.[0]?.id
715
+ const existingCaptures = Array.isArray(paypalData.captures)
716
+ ? paypalData.captures
717
+ : []
718
+ const captureEntry = {
719
+ id: captureId,
720
+ status: capture?.status,
721
+ amount: capture?.amount,
722
+ raw: capture,
723
+ }
724
+
725
+ console.info("[PayPal] provider capture", {
726
+ order_id: orderId,
727
+ capture_id: captureId,
728
+ authorization_id: authorizationId || undefined,
729
+ request_id: requestId,
730
+ debug_id: ppResp.headers.get("paypal-debug-id"),
731
+ })
732
+ await this.recordSuccess("capture_success")
733
+ await this.recordPaymentEvent("capture", {
734
+ order_id: orderId,
735
+ capture_id: captureId,
736
+ authorization_id: authorizationId || undefined,
737
+ amount,
738
+ currency_code: currencyCode,
739
+ request_id: requestId,
740
+ })
741
+
742
+ return {
743
+ data: {
744
+ ...(input.data || {}),
745
+ paypal: {
746
+ ...((input.data || {}).paypal as Record<string, unknown>),
747
+ order_id: orderId,
748
+ capture_id: captureId,
749
+ capture,
750
+ authorization_id: authorizationId || paypalData.authorization_id,
751
+ captures: [...existingCaptures, captureEntry],
752
+ },
753
+ captured_at: new Date().toISOString(),
754
+ },
755
+ }
756
+ } catch (error: any) {
757
+ await this.recordFailure("capture_failed", {
758
+ order_id: orderId,
759
+ request_id: requestId,
760
+ debug_id: debugId,
761
+ message: error?.message,
762
+ })
763
+ throw error
764
+ }
765
+ }
766
+
767
+ async refundPayment(
768
+ input: RefundPaymentInput
769
+ ): Promise<RefundPaymentOutput> {
770
+ const data = (input.data || {}) as Record<string, any>
771
+ const paypalData = (data.paypal || {}) as Record<string, any>
772
+ const captureId = String(paypalData.capture_id || data.capture_id || "")
773
+ const refundReason = String(
774
+ paypalData.refund_reason || data.refund_reason || data.reason || ""
775
+ ).trim()
776
+ const refundReasonCode = String(
777
+ paypalData.refund_reason_code || data.refund_reason_code || data.reason_code || ""
778
+ ).trim()
779
+ if (!captureId) {
780
+ return {
781
+ data: {
782
+ ...(input.data || {}),
783
+ refunded_at: new Date().toISOString(),
784
+ },
785
+ }
786
+ }
787
+
788
+ const requestId = this.getIdempotencyKey(input, `refund-${captureId}`)
789
+ const { amount, currencyCode } = await this.normalizePaymentData(input)
790
+ let debugId: string | null = null
791
+
792
+ try {
793
+ const { accessToken, base } = await this.getPayPalAccessToken()
794
+ const refundPayload: Record<string, any> =
795
+ amount > 0
796
+ ? {
797
+ amount: {
798
+ currency_code: currencyCode || "USD",
799
+ value: formatAmountForPayPal(amount, currencyCode || "USD"),
800
+ },
801
+ }
802
+ : {}
803
+
804
+ if (refundReason) {
805
+ refundPayload.note_to_payer = refundReason
806
+ }
807
+
808
+ const ppResp = await fetch(`${base}/v2/payments/captures/${captureId}/refund`, {
809
+ method: "POST",
810
+ headers: {
811
+ Authorization: `Bearer ${accessToken}`,
812
+ "Content-Type": "application/json",
813
+ "PayPal-Request-Id": requestId,
814
+ },
815
+ body: JSON.stringify(refundPayload),
816
+ })
817
+
818
+ const ppText = await ppResp.text()
819
+ debugId = ppResp.headers.get("paypal-debug-id")
820
+ if (!ppResp.ok) {
821
+ throw new Error(
822
+ `PayPal refund error (${ppResp.status}): ${ppText}${
823
+ debugId ? ` debug_id=${debugId}` : ""
824
+ }`
825
+ )
826
+ }
827
+
828
+ const refund = JSON.parse(ppText)
829
+ const existingRefunds = Array.isArray(paypalData.refunds) ? paypalData.refunds : []
830
+ const refundEntry = {
831
+ id: refund?.id,
832
+ status: refund?.status,
833
+ amount: refund?.amount,
834
+ reason: refundReason || refund?.note_to_payer,
835
+ reason_code: refundReasonCode || refund?.reason_code,
836
+ raw: refund,
837
+ }
838
+
839
+ console.info("[PayPal] provider refund", {
840
+ capture_id: captureId,
841
+ refund_id: refund?.id,
842
+ request_id: requestId,
843
+ debug_id: ppResp.headers.get("paypal-debug-id"),
844
+ })
845
+ await this.recordSuccess("refund_success")
846
+ await this.recordPaymentEvent("refund", {
847
+ capture_id: captureId,
848
+ refund_id: refund?.id,
849
+ amount,
850
+ currency_code: currencyCode,
851
+ request_id: requestId,
852
+ reason: refundReason,
853
+ reason_code: refundReasonCode,
854
+ })
855
+
856
+ return {
857
+ data: {
858
+ ...(input.data || {}),
859
+ paypal: {
860
+ ...((input.data || {}).paypal as Record<string, unknown>),
861
+ refund_id: refund?.id,
862
+ refund_status: refund?.status,
863
+ refund_reason: refundReason || refund?.note_to_payer,
864
+ refund_reason_code: refundReasonCode || refund?.reason_code,
865
+ refunds: [...existingRefunds, refundEntry],
866
+ refund,
867
+ },
868
+ refunded_at: new Date().toISOString(),
869
+ },
870
+ }
871
+ } catch (error: any) {
872
+ await this.recordFailure("refund_failed", {
873
+ capture_id: captureId,
874
+ request_id: requestId,
875
+ debug_id: debugId,
876
+ message: error?.message,
877
+ })
878
+ throw error
879
+ }
880
+ }
881
+
882
+ async cancelPayment(
883
+ input: CancelPaymentInput
884
+ ): Promise<CancelPaymentOutput> {
885
+ const data = (input.data || {}) as Record<string, any>
886
+ const paypalData = (data.paypal || {}) as Record<string, any>
887
+ const orderId = String(paypalData.order_id || data.order_id || "")
888
+ const captureId = String(paypalData.capture_id || data.capture_id || "")
889
+ const storedAuthorizationId = String(
890
+ paypalData.authorization_id || data.authorization_id || ""
891
+ )
892
+ let debugId: string | null = null
893
+
894
+ try {
895
+ const order = orderId ? await this.getOrderDetails(orderId) : null
896
+ const intent = String(order?.intent || "").toUpperCase()
897
+ const authorizationId =
898
+ order?.purchase_units?.[0]?.payments?.authorizations?.[0]?.id ||
899
+ storedAuthorizationId
900
+
901
+ if (intent === "AUTHORIZE" && authorizationId) {
902
+ const { accessToken, base } = await this.getPayPalAccessToken()
903
+ const requestId = this.getIdempotencyKey(input, `void-${authorizationId}`)
904
+
905
+ const resp = await fetch(
906
+ `${base}/v2/payments/authorizations/${authorizationId}/void`,
907
+ {
908
+ method: "POST",
909
+ headers: {
910
+ Authorization: `Bearer ${accessToken}`,
911
+ "Content-Type": "application/json",
912
+ "PayPal-Request-Id": requestId,
913
+ },
914
+ }
915
+ )
916
+
917
+ if (!resp.ok) {
918
+ const text = await resp.text()
919
+ debugId = resp.headers.get("paypal-debug-id")
920
+ throw new Error(
921
+ `PayPal void error (${resp.status}): ${text}${
922
+ debugId ? ` debug_id=${debugId}` : ""
923
+ }`
924
+ )
925
+ }
926
+
927
+ console.info("[PayPal] provider void", {
928
+ order_id: orderId,
929
+ authorization_id: authorizationId,
930
+ request_id: requestId,
931
+ debug_id: resp.headers.get("paypal-debug-id"),
932
+ })
933
+ await this.recordSuccess("void_success")
934
+ await this.recordPaymentEvent("void", {
935
+ order_id: orderId,
936
+ authorization_id: authorizationId,
937
+ request_id: requestId,
938
+ })
939
+ } else if (captureId) {
940
+ const { accessToken, base } = await this.getPayPalAccessToken()
941
+ const requestId = this.getIdempotencyKey(input, `refund-${captureId}`)
942
+
943
+ const resp = await fetch(`${base}/v2/payments/captures/${captureId}/refund`, {
944
+ method: "POST",
945
+ headers: {
946
+ Authorization: `Bearer ${accessToken}`,
947
+ "Content-Type": "application/json",
948
+ "PayPal-Request-Id": requestId,
949
+ },
950
+ body: JSON.stringify({}),
951
+ })
952
+
953
+ if (!resp.ok) {
954
+ const text = await resp.text()
955
+ debugId = resp.headers.get("paypal-debug-id")
956
+ throw new Error(
957
+ `PayPal refund error (${resp.status}): ${text}${
958
+ debugId ? ` debug_id=${debugId}` : ""
959
+ }`
960
+ )
961
+ }
962
+
963
+ const refund = await resp.json().catch(() => ({}))
964
+ const existingRefunds = Array.isArray(paypalData.refunds) ? paypalData.refunds : []
965
+ const refundEntry = {
966
+ id: refund?.id,
967
+ status: refund?.status,
968
+ amount: refund?.amount,
969
+ raw: refund,
970
+ }
971
+ paypalData.refund_id = refund?.id
972
+ paypalData.refund_status = refund?.status
973
+ paypalData.refunds = [...existingRefunds, refundEntry]
974
+
975
+ console.info("[PayPal] provider refund", {
976
+ order_id: orderId,
977
+ capture_id: captureId,
978
+ refund_id: refund?.id,
979
+ request_id: requestId,
980
+ debug_id: resp.headers.get("paypal-debug-id"),
981
+ })
982
+ await this.recordSuccess("cancel_refund_success")
983
+ await this.recordPaymentEvent("cancel_refund", {
984
+ order_id: orderId,
985
+ capture_id: captureId,
986
+ refund_id: refund?.id,
987
+ request_id: requestId,
988
+ })
989
+ }
990
+
991
+ return {
992
+ data: {
993
+ ...(input.data || {}),
994
+ paypal: {
995
+ ...((input.data || {}).paypal as Record<string, unknown>),
996
+ order: order || undefined,
997
+ authorization_id: authorizationId || storedAuthorizationId,
998
+ capture_id: captureId || paypalData.capture_id,
999
+ refund_id: paypalData.refund_id,
1000
+ refund_status: paypalData.refund_status,
1001
+ refunds: paypalData.refunds,
1002
+ },
1003
+ canceled_at: new Date().toISOString(),
1004
+ },
1005
+ }
1006
+ } catch (error: any) {
1007
+ await this.recordFailure("cancel_failed", {
1008
+ order_id: orderId,
1009
+ capture_id: captureId,
1010
+ debug_id: debugId,
1011
+ message: error?.message,
1012
+ })
1013
+ throw error
1014
+ }
1015
+ }
1016
+
1017
+ async deletePayment(
1018
+ _input: DeletePaymentInput
1019
+ ): Promise<DeletePaymentOutput> {
1020
+ return { data: {} }
1021
+ }
1022
+
1023
+ /**
1024
+ * Required by AbstractPaymentProvider in Medusa v2.
1025
+ * This is used by /hooks/payment/{identifier}_{providerId}
1026
+ */
1027
+ async getWebhookActionAndData(
1028
+ payload: ProviderWebhookPayload["payload"]
1029
+ ): Promise<WebhookActionResult> {
1030
+ return getPayPalWebhookActionAndData(payload)
1031
+ }
1032
+ }
1033
+
1034
+ export default PayPalPaymentProvider
1035
+ export { PayPalPaymentProvider }