@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.
- package/.medusa/server/src/admin/index.js +2127 -0
- package/.medusa/server/src/admin/index.mjs +2128 -0
- package/.medusa/server/src/api/admin/payment-collections/[id]/payment-sessions/route.d.ts +3 -0
- package/.medusa/server/src/api/admin/payment-collections/[id]/payment-sessions/route.d.ts.map +1 -0
- package/.medusa/server/src/api/admin/payment-collections/[id]/payment-sessions/route.js +25 -0
- package/.medusa/server/src/api/admin/payment-collections/[id]/payment-sessions/route.js.map +1 -0
- package/.medusa/server/src/api/admin/paypal/audit-logs/route.d.ts +3 -0
- package/.medusa/server/src/api/admin/paypal/audit-logs/route.d.ts.map +1 -0
- package/.medusa/server/src/api/admin/paypal/audit-logs/route.js +12 -0
- package/.medusa/server/src/api/admin/paypal/audit-logs/route.js.map +1 -0
- package/.medusa/server/src/api/admin/paypal/disconnect/route.d.ts +3 -0
- package/.medusa/server/src/api/admin/paypal/disconnect/route.d.ts.map +1 -0
- package/.medusa/server/src/api/admin/paypal/disconnect/route.js +9 -0
- package/.medusa/server/src/api/admin/paypal/disconnect/route.js.map +1 -0
- package/.medusa/server/src/api/admin/paypal/disputes/[id]/route.d.ts +3 -0
- package/.medusa/server/src/api/admin/paypal/disputes/[id]/route.d.ts.map +1 -0
- package/.medusa/server/src/api/admin/paypal/disputes/[id]/route.js +17 -0
- package/.medusa/server/src/api/admin/paypal/disputes/[id]/route.js.map +1 -0
- package/.medusa/server/src/api/admin/paypal/disputes/route.d.ts +3 -0
- package/.medusa/server/src/api/admin/paypal/disputes/route.d.ts.map +1 -0
- package/.medusa/server/src/api/admin/paypal/disputes/route.js +27 -0
- package/.medusa/server/src/api/admin/paypal/disputes/route.js.map +1 -0
- package/.medusa/server/src/api/admin/paypal/disputes/summary/route.d.ts +3 -0
- package/.medusa/server/src/api/admin/paypal/disputes/summary/route.d.ts.map +1 -0
- package/.medusa/server/src/api/admin/paypal/disputes/summary/route.js +17 -0
- package/.medusa/server/src/api/admin/paypal/disputes/summary/route.js.map +1 -0
- package/.medusa/server/src/api/admin/paypal/environment/route.d.ts +4 -0
- package/.medusa/server/src/api/admin/paypal/environment/route.d.ts.map +1 -0
- package/.medusa/server/src/api/admin/paypal/environment/route.js +23 -0
- package/.medusa/server/src/api/admin/paypal/environment/route.js.map +1 -0
- package/.medusa/server/src/api/admin/paypal/onboard-complete/route.d.ts +8 -0
- package/.medusa/server/src/api/admin/paypal/onboard-complete/route.d.ts.map +1 -0
- package/.medusa/server/src/api/admin/paypal/onboard-complete/route.js +41 -0
- package/.medusa/server/src/api/admin/paypal/onboard-complete/route.js.map +1 -0
- package/.medusa/server/src/api/admin/paypal/onboarding-link/route.d.ts +4 -0
- package/.medusa/server/src/api/admin/paypal/onboarding-link/route.d.ts.map +1 -0
- package/.medusa/server/src/api/admin/paypal/onboarding-link/route.js +35 -0
- package/.medusa/server/src/api/admin/paypal/onboarding-link/route.js.map +1 -0
- package/.medusa/server/src/api/admin/paypal/onboarding-status/route.d.ts +3 -0
- package/.medusa/server/src/api/admin/paypal/onboarding-status/route.d.ts.map +1 -0
- package/.medusa/server/src/api/admin/paypal/onboarding-status/route.js +20 -0
- package/.medusa/server/src/api/admin/paypal/onboarding-status/route.js.map +1 -0
- package/.medusa/server/src/api/admin/paypal/reconciliation-status/route.d.ts +3 -0
- package/.medusa/server/src/api/admin/paypal/reconciliation-status/route.d.ts.map +1 -0
- package/.medusa/server/src/api/admin/paypal/reconciliation-status/route.js +8 -0
- package/.medusa/server/src/api/admin/paypal/reconciliation-status/route.js.map +1 -0
- package/.medusa/server/src/api/admin/paypal/rotate-credentials/route.d.ts +3 -0
- package/.medusa/server/src/api/admin/paypal/rotate-credentials/route.d.ts.map +1 -0
- package/.medusa/server/src/api/admin/paypal/rotate-credentials/route.js +9 -0
- package/.medusa/server/src/api/admin/paypal/rotate-credentials/route.js.map +1 -0
- package/.medusa/server/src/api/admin/paypal/save-credentials/route.d.ts +3 -0
- package/.medusa/server/src/api/admin/paypal/save-credentials/route.d.ts.map +1 -0
- package/.medusa/server/src/api/admin/paypal/save-credentials/route.js +13 -0
- package/.medusa/server/src/api/admin/paypal/save-credentials/route.js.map +1 -0
- package/.medusa/server/src/api/admin/paypal/settings/route.d.ts +4 -0
- package/.medusa/server/src/api/admin/paypal/settings/route.d.ts.map +1 -0
- package/.medusa/server/src/api/admin/paypal/settings/route.js +14 -0
- package/.medusa/server/src/api/admin/paypal/settings/route.js.map +1 -0
- package/.medusa/server/src/api/admin/paypal/status/route.d.ts +3 -0
- package/.medusa/server/src/api/admin/paypal/status/route.d.ts.map +1 -0
- package/.medusa/server/src/api/admin/paypal/status/route.js +11 -0
- package/.medusa/server/src/api/admin/paypal/status/route.js.map +1 -0
- package/.medusa/server/src/api/store/payment-collections/[id]/payment-sessions/route.d.ts +3 -0
- package/.medusa/server/src/api/store/payment-collections/[id]/payment-sessions/route.d.ts.map +1 -0
- package/.medusa/server/src/api/store/payment-collections/[id]/payment-sessions/route.js +43 -0
- package/.medusa/server/src/api/store/payment-collections/[id]/payment-sessions/route.js.map +1 -0
- package/.medusa/server/src/api/store/paypal/capture-order/route.d.ts +3 -0
- package/.medusa/server/src/api/store/paypal/capture-order/route.d.ts.map +1 -0
- package/.medusa/server/src/api/store/paypal/capture-order/route.js +215 -0
- package/.medusa/server/src/api/store/paypal/capture-order/route.js.map +1 -0
- package/.medusa/server/src/api/store/paypal/config/route.d.ts +3 -0
- package/.medusa/server/src/api/store/paypal/config/route.d.ts.map +1 -0
- package/.medusa/server/src/api/store/paypal/config/route.js +45 -0
- package/.medusa/server/src/api/store/paypal/config/route.js.map +1 -0
- package/.medusa/server/src/api/store/paypal/create-order/route.d.ts +3 -0
- package/.medusa/server/src/api/store/paypal/create-order/route.d.ts.map +1 -0
- package/.medusa/server/src/api/store/paypal/create-order/route.js +305 -0
- package/.medusa/server/src/api/store/paypal/create-order/route.js.map +1 -0
- package/.medusa/server/src/api/store/paypal/disputes/route.d.ts +3 -0
- package/.medusa/server/src/api/store/paypal/disputes/route.d.ts.map +1 -0
- package/.medusa/server/src/api/store/paypal/disputes/route.js +46 -0
- package/.medusa/server/src/api/store/paypal/disputes/route.js.map +1 -0
- package/.medusa/server/src/api/store/paypal/settings/route.d.ts +3 -0
- package/.medusa/server/src/api/store/paypal/settings/route.d.ts.map +1 -0
- package/.medusa/server/src/api/store/paypal/settings/route.js +14 -0
- package/.medusa/server/src/api/store/paypal/settings/route.js.map +1 -0
- package/.medusa/server/src/api/store/paypal/webhook/route.d.ts +3 -0
- package/.medusa/server/src/api/store/paypal/webhook/route.d.ts.map +1 -0
- package/.medusa/server/src/api/store/paypal/webhook/route.js +203 -0
- package/.medusa/server/src/api/store/paypal/webhook/route.js.map +1 -0
- package/.medusa/server/src/jobs/paypal-reconcile.d.ts +7 -0
- package/.medusa/server/src/jobs/paypal-reconcile.d.ts.map +1 -0
- package/.medusa/server/src/jobs/paypal-reconcile.js +131 -0
- package/.medusa/server/src/jobs/paypal-reconcile.js.map +1 -0
- package/.medusa/server/src/jobs/paypal-webhook-retry.d.ts +7 -0
- package/.medusa/server/src/jobs/paypal-webhook-retry.d.ts.map +1 -0
- package/.medusa/server/src/jobs/paypal-webhook-retry.js +78 -0
- package/.medusa/server/src/jobs/paypal-webhook-retry.js.map +1 -0
- package/.medusa/server/src/modules/paypal/clients/paypal-seller.client.d.ts +14 -0
- package/.medusa/server/src/modules/paypal/clients/paypal-seller.client.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/clients/paypal-seller.client.js +65 -0
- package/.medusa/server/src/modules/paypal/clients/paypal-seller.client.js.map +1 -0
- package/.medusa/server/src/modules/paypal/index.d.ts +92 -0
- package/.medusa/server/src/modules/paypal/index.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/index.js +13 -0
- package/.medusa/server/src/modules/paypal/index.js.map +1 -0
- package/.medusa/server/src/modules/paypal/migrations/20260115120000_create_paypal_connection.d.ts +6 -0
- package/.medusa/server/src/modules/paypal/migrations/20260115120000_create_paypal_connection.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/migrations/20260115120000_create_paypal_connection.js +36 -0
- package/.medusa/server/src/modules/paypal/migrations/20260115120000_create_paypal_connection.js.map +1 -0
- package/.medusa/server/src/modules/paypal/migrations/20260123090000_create_paypal_settings.d.ts +6 -0
- package/.medusa/server/src/modules/paypal/migrations/20260123090000_create_paypal_settings.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/migrations/20260123090000_create_paypal_settings.js +25 -0
- package/.medusa/server/src/modules/paypal/migrations/20260123090000_create_paypal_settings.js.map +1 -0
- package/.medusa/server/src/modules/paypal/migrations/20260201090000_create_paypal_webhook_event.d.ts +6 -0
- package/.medusa/server/src/modules/paypal/migrations/20260201090000_create_paypal_webhook_event.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/migrations/20260201090000_create_paypal_webhook_event.js +32 -0
- package/.medusa/server/src/modules/paypal/migrations/20260201090000_create_paypal_webhook_event.js.map +1 -0
- package/.medusa/server/src/modules/paypal/migrations/20260301090000_create_paypal_audit_log.d.ts +6 -0
- package/.medusa/server/src/modules/paypal/migrations/20260301090000_create_paypal_audit_log.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/migrations/20260301090000_create_paypal_audit_log.js +29 -0
- package/.medusa/server/src/modules/paypal/migrations/20260301090000_create_paypal_audit_log.js.map +1 -0
- package/.medusa/server/src/modules/paypal/migrations/20260401090000_create_paypal_metric.d.ts +6 -0
- package/.medusa/server/src/modules/paypal/migrations/20260401090000_create_paypal_metric.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/migrations/20260401090000_create_paypal_metric.js +30 -0
- package/.medusa/server/src/modules/paypal/migrations/20260401090000_create_paypal_metric.js.map +1 -0
- package/.medusa/server/src/modules/paypal/migrations/20260501090000_create_paypal_dispute.d.ts +6 -0
- package/.medusa/server/src/modules/paypal/migrations/20260501090000_create_paypal_dispute.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/migrations/20260501090000_create_paypal_dispute.js +43 -0
- package/.medusa/server/src/modules/paypal/migrations/20260501090000_create_paypal_dispute.js.map +1 -0
- package/.medusa/server/src/modules/paypal/migrations/20260701090000_add_paypal_webhook_event_processing.d.ts +6 -0
- package/.medusa/server/src/modules/paypal/migrations/20260701090000_add_paypal_webhook_event_processing.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/migrations/20260701090000_add_paypal_webhook_event_processing.js +34 -0
- package/.medusa/server/src/modules/paypal/migrations/20260701090000_add_paypal_webhook_event_processing.js.map +1 -0
- package/.medusa/server/src/modules/paypal/models/paypal_audit_log.d.ts +7 -0
- package/.medusa/server/src/modules/paypal/models/paypal_audit_log.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/models/paypal_audit_log.js +10 -0
- package/.medusa/server/src/modules/paypal/models/paypal_audit_log.js.map +1 -0
- package/.medusa/server/src/modules/paypal/models/paypal_connection.d.ts +14 -0
- package/.medusa/server/src/modules/paypal/models/paypal_connection.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/models/paypal_connection.js +17 -0
- package/.medusa/server/src/modules/paypal/models/paypal_connection.js.map +1 -0
- package/.medusa/server/src/modules/paypal/models/paypal_dispute.d.ts +16 -0
- package/.medusa/server/src/modules/paypal/models/paypal_dispute.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/models/paypal_dispute.js +19 -0
- package/.medusa/server/src/modules/paypal/models/paypal_dispute.js.map +1 -0
- package/.medusa/server/src/modules/paypal/models/paypal_metric.d.ts +7 -0
- package/.medusa/server/src/modules/paypal/models/paypal_metric.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/models/paypal_metric.js +10 -0
- package/.medusa/server/src/modules/paypal/models/paypal_metric.js.map +1 -0
- package/.medusa/server/src/modules/paypal/models/paypal_settings.d.ts +6 -0
- package/.medusa/server/src/modules/paypal/models/paypal_settings.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/models/paypal_settings.js +9 -0
- package/.medusa/server/src/modules/paypal/models/paypal_settings.js.map +1 -0
- package/.medusa/server/src/modules/paypal/models/paypal_webhook_event.d.ts +17 -0
- package/.medusa/server/src/modules/paypal/models/paypal_webhook_event.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/models/paypal_webhook_event.js +20 -0
- package/.medusa/server/src/modules/paypal/models/paypal_webhook_event.js.map +1 -0
- package/.medusa/server/src/modules/paypal/payment-provider/card-service.d.ts +35 -0
- package/.medusa/server/src/modules/paypal/payment-provider/card-service.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/payment-provider/card-service.js +569 -0
- package/.medusa/server/src/modules/paypal/payment-provider/card-service.js.map +1 -0
- package/.medusa/server/src/modules/paypal/payment-provider/index.d.ts +10 -0
- package/.medusa/server/src/modules/paypal/payment-provider/index.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/payment-provider/index.js +22 -0
- package/.medusa/server/src/modules/paypal/payment-provider/index.js.map +1 -0
- package/.medusa/server/src/modules/paypal/payment-provider/service.d.ts +44 -0
- package/.medusa/server/src/modules/paypal/payment-provider/service.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/payment-provider/service.js +825 -0
- package/.medusa/server/src/modules/paypal/payment-provider/service.js.map +1 -0
- package/.medusa/server/src/modules/paypal/payment-provider/webhook-utils.d.ts +3 -0
- package/.medusa/server/src/modules/paypal/payment-provider/webhook-utils.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/payment-provider/webhook-utils.js +74 -0
- package/.medusa/server/src/modules/paypal/payment-provider/webhook-utils.js.map +1 -0
- package/.medusa/server/src/modules/paypal/service.d.ts +362 -0
- package/.medusa/server/src/modules/paypal/service.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/service.js +1180 -0
- package/.medusa/server/src/modules/paypal/service.js.map +1 -0
- package/.medusa/server/src/modules/paypal/types/config.d.ts +14 -0
- package/.medusa/server/src/modules/paypal/types/config.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/types/config.js +33 -0
- package/.medusa/server/src/modules/paypal/types/config.js.map +1 -0
- package/.medusa/server/src/modules/paypal/utils/amounts.d.ts +3 -0
- package/.medusa/server/src/modules/paypal/utils/amounts.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/utils/amounts.js +40 -0
- package/.medusa/server/src/modules/paypal/utils/amounts.js.map +1 -0
- package/.medusa/server/src/modules/paypal/utils/crypto.d.ts +4 -0
- package/.medusa/server/src/modules/paypal/utils/crypto.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/utils/crypto.js +47 -0
- package/.medusa/server/src/modules/paypal/utils/crypto.js.map +1 -0
- package/.medusa/server/src/modules/paypal/utils/currencies.d.ts +19 -0
- package/.medusa/server/src/modules/paypal/utils/currencies.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/utils/currencies.js +69 -0
- package/.medusa/server/src/modules/paypal/utils/currencies.js.map +1 -0
- package/.medusa/server/src/modules/paypal/utils/provider-ids.d.ts +9 -0
- package/.medusa/server/src/modules/paypal/utils/provider-ids.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/utils/provider-ids.js +50 -0
- package/.medusa/server/src/modules/paypal/utils/provider-ids.js.map +1 -0
- package/.medusa/server/src/modules/paypal/webhook-processor.d.ts +38 -0
- package/.medusa/server/src/modules/paypal/webhook-processor.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/webhook-processor.js +265 -0
- package/.medusa/server/src/modules/paypal/webhook-processor.js.map +1 -0
- package/LICENSE +21 -0
- package/README.md +67 -0
- package/package.json +61 -0
- package/postcss.config.cjs +3 -0
- package/src/admin/index.ts +7 -0
- package/src/admin/routes/settings/paypal/_components/Tabs.tsx +55 -0
- package/src/admin/routes/settings/paypal/_components/Toast.tsx +51 -0
- package/src/admin/routes/settings/paypal/additional-settings/page.tsx +346 -0
- package/src/admin/routes/settings/paypal/advanced-card-payments/page.tsx +381 -0
- package/src/admin/routes/settings/paypal/apple-pay/page.tsx +5 -0
- package/src/admin/routes/settings/paypal/audit-logs/page.tsx +131 -0
- package/src/admin/routes/settings/paypal/connection/page.tsx +750 -0
- package/src/admin/routes/settings/paypal/disputes/page.tsx +259 -0
- package/src/admin/routes/settings/paypal/google-pay/page.tsx +5 -0
- package/src/admin/routes/settings/paypal/page.tsx +16 -0
- package/src/admin/routes/settings/paypal/pay-later-messaging/page.tsx +5 -0
- package/src/admin/routes/settings/paypal/paypal-settings/page.tsx +557 -0
- package/src/admin/routes/settings/paypal/reconciliation-status/page.tsx +165 -0
- package/src/api/admin/payment-collections/[id]/payment-sessions/route.ts +32 -0
- package/src/api/admin/paypal/audit-logs/route.ts +13 -0
- package/src/api/admin/paypal/disconnect/route.ts +8 -0
- package/src/api/admin/paypal/disputes/[id]/route.ts +19 -0
- package/src/api/admin/paypal/disputes/route.ts +30 -0
- package/src/api/admin/paypal/disputes/summary/route.ts +18 -0
- package/src/api/admin/paypal/environment/route.ts +25 -0
- package/src/api/admin/paypal/onboard-complete/route.ts +44 -0
- package/src/api/admin/paypal/onboarding-link/route.ts +45 -0
- package/src/api/admin/paypal/onboarding-status/route.ts +18 -0
- package/src/api/admin/paypal/reconciliation-status/route.ts +7 -0
- package/src/api/admin/paypal/rotate-credentials/route.ts +8 -0
- package/src/api/admin/paypal/save-credentials/route.ts +14 -0
- package/src/api/admin/paypal/settings/route.ts +14 -0
- package/src/api/admin/paypal/status/route.ts +12 -0
- package/src/api/store/payment-collections/[id]/payment-sessions/route.ts +51 -0
- package/src/api/store/paypal/capture-order/route.ts +270 -0
- package/src/api/store/paypal/config/route.ts +59 -0
- package/src/api/store/paypal/create-order/route.ts +374 -0
- package/src/api/store/paypal/disputes/route.ts +67 -0
- package/src/api/store/paypal/settings/route.ts +12 -0
- package/src/api/store/paypal/webhook/route.ts +247 -0
- package/src/jobs/paypal-reconcile.ts +135 -0
- package/src/jobs/paypal-webhook-retry.ts +86 -0
- package/src/modules/paypal/clients/paypal-seller.client.ts +59 -0
- package/src/modules/paypal/index.ts +8 -0
- package/src/modules/paypal/migrations/20260115120000_create_paypal_connection.ts +33 -0
- package/src/modules/paypal/migrations/20260123090000_create_paypal_settings.ts +22 -0
- package/src/modules/paypal/migrations/20260201090000_create_paypal_webhook_event.ts +29 -0
- package/src/modules/paypal/migrations/20260301090000_create_paypal_audit_log.ts +26 -0
- package/src/modules/paypal/migrations/20260401090000_create_paypal_metric.ts +27 -0
- package/src/modules/paypal/migrations/20260501090000_create_paypal_dispute.ts +40 -0
- package/src/modules/paypal/migrations/20260701090000_add_paypal_webhook_event_processing.ts +31 -0
- package/src/modules/paypal/models/paypal_audit_log.ts +9 -0
- package/src/modules/paypal/models/paypal_connection.ts +21 -0
- package/src/modules/paypal/models/paypal_dispute.ts +18 -0
- package/src/modules/paypal/models/paypal_metric.ts +9 -0
- package/src/modules/paypal/models/paypal_settings.ts +8 -0
- package/src/modules/paypal/models/paypal_webhook_event.ts +19 -0
- package/src/modules/paypal/payment-provider/README.md +22 -0
- package/src/modules/paypal/payment-provider/card-service.ts +710 -0
- package/src/modules/paypal/payment-provider/index.ts +19 -0
- package/src/modules/paypal/payment-provider/service.ts +1035 -0
- package/src/modules/paypal/payment-provider/webhook-utils.ts +88 -0
- package/src/modules/paypal/service.ts +1422 -0
- package/src/modules/paypal/types/config.ts +47 -0
- package/src/modules/paypal/utils/amounts.ts +41 -0
- package/src/modules/paypal/utils/crypto.ts +51 -0
- package/src/modules/paypal/utils/currencies.ts +84 -0
- package/src/modules/paypal/utils/provider-ids.ts +53 -0
- package/src/modules/paypal/webhook-processor.ts +313 -0
- package/tsconfig.json +31 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
|
2
|
+
import type PayPalModuleService from "../../../../modules/paypal/service"
|
|
3
|
+
|
|
4
|
+
type DisputeRecord = {
|
|
5
|
+
id: string
|
|
6
|
+
dispute_id: string
|
|
7
|
+
status?: string | null
|
|
8
|
+
reason?: string | null
|
|
9
|
+
stage?: string | null
|
|
10
|
+
amount?: string | null
|
|
11
|
+
currency_code?: string | null
|
|
12
|
+
transaction_id?: string | null
|
|
13
|
+
seller_transaction_id?: string | null
|
|
14
|
+
order_id?: string | null
|
|
15
|
+
cart_id?: string | null
|
|
16
|
+
created_at?: string
|
|
17
|
+
updated_at?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function toPublicDispute(dispute: any): DisputeRecord {
|
|
21
|
+
return {
|
|
22
|
+
id: dispute.id,
|
|
23
|
+
dispute_id: dispute.dispute_id,
|
|
24
|
+
status: dispute.status ?? null,
|
|
25
|
+
reason: dispute.reason ?? null,
|
|
26
|
+
stage: dispute.stage ?? null,
|
|
27
|
+
amount: dispute.amount ?? null,
|
|
28
|
+
currency_code: dispute.currency_code ?? null,
|
|
29
|
+
transaction_id: dispute.transaction_id ?? null,
|
|
30
|
+
seller_transaction_id: dispute.seller_transaction_id ?? null,
|
|
31
|
+
order_id: dispute.order_id ?? null,
|
|
32
|
+
cart_id: dispute.cart_id ?? null,
|
|
33
|
+
created_at: dispute.created_at ?? null,
|
|
34
|
+
updated_at: dispute.updated_at ?? null,
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function GET(req: MedusaRequest, res: MedusaResponse) {
|
|
39
|
+
const paypal = req.scope.resolve<PayPalModuleService>("paypal_onboarding")
|
|
40
|
+
|
|
41
|
+
const query = (req.query || {}) as Record<string, string | string[] | undefined>
|
|
42
|
+
const filters: Record<string, string> = {}
|
|
43
|
+
|
|
44
|
+
const addFilter = (key: string) => {
|
|
45
|
+
const value = query[key]
|
|
46
|
+
if (Array.isArray(value)) {
|
|
47
|
+
if (value[0]) {
|
|
48
|
+
filters[key] = value[0]
|
|
49
|
+
}
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
if (typeof value === "string" && value.trim()) {
|
|
53
|
+
filters[key] = value.trim()
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
addFilter("order_id")
|
|
58
|
+
addFilter("cart_id")
|
|
59
|
+
addFilter("dispute_id")
|
|
60
|
+
|
|
61
|
+
if (Object.keys(filters).length === 0) {
|
|
62
|
+
return res.status(400).json({ message: "order_id, cart_id, or dispute_id is required" })
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const disputes = await paypal.listPayPalDisputes(filters)
|
|
66
|
+
return res.json({ disputes: (disputes || []).map(toPublicDispute) })
|
|
67
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
|
2
|
+
import type PayPalModuleService from "../../../../modules/paypal/service"
|
|
3
|
+
|
|
4
|
+
export async function GET(req: MedusaRequest, res: MedusaResponse) {
|
|
5
|
+
const paypal = req.scope.resolve<PayPalModuleService>("paypal_onboarding")
|
|
6
|
+
try {
|
|
7
|
+
const settings = await paypal.getSettings()
|
|
8
|
+
return res.json(settings)
|
|
9
|
+
} catch (e: any) {
|
|
10
|
+
return res.status(500).json({ message: e?.message || "Failed to load PayPal settings" })
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
|
2
|
+
import type PayPalModuleService from "../../../../modules/paypal/service"
|
|
3
|
+
import {
|
|
4
|
+
computeNextRetryAt,
|
|
5
|
+
isAllowedEventType,
|
|
6
|
+
normalizeEventVersion,
|
|
7
|
+
processPayPalWebhookEvent,
|
|
8
|
+
} from "../../../../modules/paypal/webhook-processor"
|
|
9
|
+
|
|
10
|
+
const REPLAY_WINDOW_MINUTES = (() => {
|
|
11
|
+
const configured = Number(process.env.PAYPAL_WEBHOOK_REPLAY_WINDOW_MINUTES)
|
|
12
|
+
return Number.isFinite(configured) ? configured : 60
|
|
13
|
+
})()
|
|
14
|
+
|
|
15
|
+
function getHeader(headers: Record<string, string | string[] | undefined>, name: string) {
|
|
16
|
+
const direct = headers[name]
|
|
17
|
+
if (Array.isArray(direct)) {
|
|
18
|
+
return direct[0]
|
|
19
|
+
}
|
|
20
|
+
if (typeof direct === "string") {
|
|
21
|
+
return direct
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const needle = name.toLowerCase()
|
|
25
|
+
const key = Object.keys(headers).find((header) => header.toLowerCase() === needle)
|
|
26
|
+
const value = key ? headers[key] : undefined
|
|
27
|
+
if (Array.isArray(value)) {
|
|
28
|
+
return value[0]
|
|
29
|
+
}
|
|
30
|
+
return value
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function isReplay(headers: Record<string, string | string[] | undefined>) {
|
|
34
|
+
const transmissionTime = getHeader(headers, "paypal-transmission-time")
|
|
35
|
+
if (!transmissionTime) {
|
|
36
|
+
throw new Error("Missing PayPal transmission time header")
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const parsed = Date.parse(transmissionTime)
|
|
40
|
+
if (!Number.isFinite(parsed)) {
|
|
41
|
+
throw new Error("Invalid PayPal transmission time header")
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const deltaMs = Math.abs(Date.now() - parsed)
|
|
45
|
+
return deltaMs > REPLAY_WINDOW_MINUTES * 60 * 1000
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function resolveWebhookId(
|
|
49
|
+
environment: string,
|
|
50
|
+
settings?: Record<string, unknown>
|
|
51
|
+
) {
|
|
52
|
+
const ids = (settings?.webhook_ids || {}) as Record<string, string | undefined>
|
|
53
|
+
const legacyLive = settings?.webhook_id_live as string | undefined
|
|
54
|
+
const legacySandbox = settings?.webhook_id_sandbox as string | undefined
|
|
55
|
+
|
|
56
|
+
if (environment === "live") {
|
|
57
|
+
return ids.live || legacyLive || process.env.PAYPAL_WEBHOOK_ID_LIVE
|
|
58
|
+
}
|
|
59
|
+
return ids.sandbox || legacySandbox || process.env.PAYPAL_WEBHOOK_ID_SANDBOX
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function verifyWebhookSignature(
|
|
63
|
+
paypal: PayPalModuleService,
|
|
64
|
+
environment: string,
|
|
65
|
+
body: Record<string, unknown>,
|
|
66
|
+
headers: Record<string, string | string[] | undefined>
|
|
67
|
+
) {
|
|
68
|
+
if (isReplay(headers)) {
|
|
69
|
+
throw new Error("PayPal webhook replay protection triggered")
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const settings = await paypal.getSettings().catch(() => ({ data: {} }))
|
|
73
|
+
const webhookId = resolveWebhookId(
|
|
74
|
+
environment,
|
|
75
|
+
(settings?.data as Record<string, unknown>) || {}
|
|
76
|
+
)
|
|
77
|
+
if (!webhookId) {
|
|
78
|
+
throw new Error(`Missing PayPal webhook ID for environment "${environment}"`)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const base =
|
|
82
|
+
environment === "live"
|
|
83
|
+
? "https://api-m.paypal.com"
|
|
84
|
+
: "https://api-m.sandbox.paypal.com"
|
|
85
|
+
const accessToken = await paypal.getAppAccessToken()
|
|
86
|
+
|
|
87
|
+
const verifyPayload = {
|
|
88
|
+
auth_algo: getHeader(headers, "paypal-auth-algo"),
|
|
89
|
+
cert_url: getHeader(headers, "paypal-cert-url"),
|
|
90
|
+
transmission_id: getHeader(headers, "paypal-transmission-id"),
|
|
91
|
+
transmission_sig: getHeader(headers, "paypal-transmission-sig"),
|
|
92
|
+
transmission_time: getHeader(headers, "paypal-transmission-time"),
|
|
93
|
+
webhook_id: webhookId,
|
|
94
|
+
webhook_event: body,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const requiredHeaders = [
|
|
98
|
+
verifyPayload.auth_algo,
|
|
99
|
+
verifyPayload.cert_url,
|
|
100
|
+
verifyPayload.transmission_id,
|
|
101
|
+
verifyPayload.transmission_sig,
|
|
102
|
+
verifyPayload.transmission_time,
|
|
103
|
+
]
|
|
104
|
+
if (requiredHeaders.some((value) => !value)) {
|
|
105
|
+
throw new Error("Missing required PayPal webhook signature headers")
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const resp = await fetch(`${base}/v1/notifications/verify-webhook-signature`, {
|
|
109
|
+
method: "POST",
|
|
110
|
+
headers: {
|
|
111
|
+
Authorization: `Bearer ${accessToken}`,
|
|
112
|
+
"Content-Type": "application/json",
|
|
113
|
+
},
|
|
114
|
+
body: JSON.stringify(verifyPayload),
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
const json = await resp.json().catch(() => ({}))
|
|
118
|
+
if (!resp.ok || json?.verification_status !== "VERIFIED") {
|
|
119
|
+
const debugId = resp.headers.get("paypal-debug-id") || json?.debug_id
|
|
120
|
+
throw new Error(
|
|
121
|
+
`PayPal webhook verification failed (${resp.status}): ${JSON.stringify(json)}${
|
|
122
|
+
debugId ? ` debug_id=${debugId}` : ""
|
|
123
|
+
}`
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function POST(req: MedusaRequest, res: MedusaResponse) {
|
|
129
|
+
const paypal = req.scope.resolve<PayPalModuleService>("paypal_onboarding")
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
const payload = (req.body || {}) as Record<string, any>
|
|
133
|
+
const eventId = String(payload?.id || payload?.event_id || "")
|
|
134
|
+
const eventType = String(payload?.event_type || payload?.eventType || "")
|
|
135
|
+
|
|
136
|
+
if (!eventId || !eventType) {
|
|
137
|
+
return res.status(400).json({ message: "Missing PayPal event id or type" })
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const creds = await paypal.getActiveCredentials()
|
|
141
|
+
await verifyWebhookSignature(paypal, creds.environment, payload, req.headers)
|
|
142
|
+
const transmissionId = getHeader(req.headers, "paypal-transmission-id") || null
|
|
143
|
+
const transmissionTimeHeader = getHeader(req.headers, "paypal-transmission-time")
|
|
144
|
+
const transmissionTime = transmissionTimeHeader ? new Date(transmissionTimeHeader) : null
|
|
145
|
+
const eventVersion = normalizeEventVersion(payload)
|
|
146
|
+
|
|
147
|
+
if (transmissionId) {
|
|
148
|
+
const existingByTransmission = await paypal.listPayPalWebhookEvents({
|
|
149
|
+
transmission_id: transmissionId,
|
|
150
|
+
})
|
|
151
|
+
if ((existingByTransmission || []).length > 0) {
|
|
152
|
+
return res.json({ ok: true, duplicate: true })
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const recordResult = await paypal.createWebhookEventRecord({
|
|
157
|
+
event_id: eventId,
|
|
158
|
+
event_type: eventType,
|
|
159
|
+
payload,
|
|
160
|
+
event_version: eventVersion,
|
|
161
|
+
transmission_id: transmissionId,
|
|
162
|
+
transmission_time: transmissionTime,
|
|
163
|
+
status: "processing",
|
|
164
|
+
attempt_count: 1,
|
|
165
|
+
})
|
|
166
|
+
if (!recordResult.created) {
|
|
167
|
+
return res.json({ ok: true, duplicate: true })
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!isAllowedEventType(eventType)) {
|
|
171
|
+
await paypal.recordAuditEvent("webhook_unsupported_event", {
|
|
172
|
+
event_id: eventId,
|
|
173
|
+
event_type: eventType,
|
|
174
|
+
})
|
|
175
|
+
if (recordResult.event?.id) {
|
|
176
|
+
await paypal.updateWebhookEventRecord({
|
|
177
|
+
id: recordResult.event.id,
|
|
178
|
+
status: "ignored",
|
|
179
|
+
processed_at: new Date(),
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
return res.json({ ok: true, ignored: true })
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const processed = await processPayPalWebhookEvent(req.scope, {
|
|
186
|
+
eventType,
|
|
187
|
+
payload,
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
if (recordResult.event?.id) {
|
|
191
|
+
await paypal.updateWebhookEventRecord({
|
|
192
|
+
id: recordResult.event.id,
|
|
193
|
+
status: "processed",
|
|
194
|
+
processed_at: new Date(),
|
|
195
|
+
resource_id:
|
|
196
|
+
processed.disputeId || processed.refundId || processed.captureId || processed.orderId || null,
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
console.info("[PayPal] webhook", {
|
|
201
|
+
event_id: eventId,
|
|
202
|
+
event_type: eventType,
|
|
203
|
+
order_id: processed.orderId,
|
|
204
|
+
capture_id: processed.captureId,
|
|
205
|
+
refund_id: processed.refundId,
|
|
206
|
+
cart_id: processed.cartId,
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
await paypal.recordMetric("webhook_success")
|
|
211
|
+
} catch {
|
|
212
|
+
// ignore metrics failures
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return res.json({ ok: true })
|
|
216
|
+
} catch (e: any) {
|
|
217
|
+
try {
|
|
218
|
+
const payload = (req.body || {}) as Record<string, any>
|
|
219
|
+
const eventId = String(payload?.id || payload?.event_id || "")
|
|
220
|
+
const eventType = String(payload?.event_type || payload?.eventType || "")
|
|
221
|
+
if (eventId) {
|
|
222
|
+
const existing = await paypal.listPayPalWebhookEvents({ event_id: eventId })
|
|
223
|
+
const record = existing?.[0]
|
|
224
|
+
if (record?.id) {
|
|
225
|
+
const attemptCount = Number(record.attempt_count || 0) + 1
|
|
226
|
+
await paypal.updateWebhookEventRecord({
|
|
227
|
+
id: record.id,
|
|
228
|
+
status: "failed",
|
|
229
|
+
attempt_count: attemptCount,
|
|
230
|
+
next_retry_at: computeNextRetryAt(attemptCount),
|
|
231
|
+
last_error: e?.message || String(e),
|
|
232
|
+
})
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
await paypal.recordAuditEvent("webhook_failed", {
|
|
236
|
+
event_id: payload?.id || payload?.event_id,
|
|
237
|
+
event_type: payload?.event_type || payload?.eventType,
|
|
238
|
+
message: e?.message || String(e),
|
|
239
|
+
})
|
|
240
|
+
await paypal.recordMetric("webhook_failed")
|
|
241
|
+
} catch {
|
|
242
|
+
// ignore audit logging failures
|
|
243
|
+
}
|
|
244
|
+
console.error("[PayPal] webhook error", e?.message || e)
|
|
245
|
+
return res.status(500).json({ message: e?.message || "PayPal webhook error" })
|
|
246
|
+
}
|
|
247
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { MedusaContainer } from "@medusajs/framework/types"
|
|
2
|
+
import type PayPalModuleService from "../modules/paypal/service"
|
|
3
|
+
|
|
4
|
+
const STATUS_MAP: Record<string, "pending" | "authorized" | "captured" | "canceled"> = {
|
|
5
|
+
CREATED: "pending",
|
|
6
|
+
APPROVED: "authorized",
|
|
7
|
+
COMPLETED: "captured",
|
|
8
|
+
VOIDED: "canceled",
|
|
9
|
+
CANCELLED: "canceled",
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default async function paypalReconcile(container: MedusaContainer) {
|
|
13
|
+
const paymentSessionService = container.resolve("payment_session") as any
|
|
14
|
+
const paypal = container.resolve<PayPalModuleService>("paypal_onboarding")
|
|
15
|
+
|
|
16
|
+
const sessions = await paymentSessionService.list({
|
|
17
|
+
provider_id: ["paypal", "paypal_card"],
|
|
18
|
+
status: ["pending", "authorized"],
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
let sessionsChecked = 0
|
|
22
|
+
let sessionsUpdated = 0
|
|
23
|
+
let driftCount = 0
|
|
24
|
+
let lastDriftAt: string | null = null
|
|
25
|
+
let lastDriftOrderId: string | null = null
|
|
26
|
+
let hadFailure = false
|
|
27
|
+
let lastErrorMessage: string | null = null
|
|
28
|
+
|
|
29
|
+
for (const session of sessions || []) {
|
|
30
|
+
const data = (session?.data || {}) as Record<string, any>
|
|
31
|
+
const paypalData = (data.paypal || {}) as Record<string, any>
|
|
32
|
+
const orderId = String(paypalData.order_id || data.order_id || "")
|
|
33
|
+
if (!orderId) {
|
|
34
|
+
continue
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
sessionsChecked += 1
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const order = await paypal.getOrderDetails(orderId)
|
|
41
|
+
const status = STATUS_MAP[String(order?.status || "").toUpperCase()]
|
|
42
|
+
if (!status) {
|
|
43
|
+
continue
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const previousStatus = session?.status
|
|
47
|
+
if (previousStatus && previousStatus !== status) {
|
|
48
|
+
driftCount += 1
|
|
49
|
+
lastDriftAt = new Date().toISOString()
|
|
50
|
+
lastDriftOrderId = orderId
|
|
51
|
+
await paypal.recordPaymentLog("reconcile_drift", {
|
|
52
|
+
session_id: session.id,
|
|
53
|
+
order_id: orderId,
|
|
54
|
+
previous_status: previousStatus,
|
|
55
|
+
next_status: status,
|
|
56
|
+
})
|
|
57
|
+
await paypal.recordMetric("reconcile_drift")
|
|
58
|
+
await paypal.sendAlert({
|
|
59
|
+
type: "reconcile_drift",
|
|
60
|
+
message: `PayPal reconciliation drift detected for order ${orderId}.`,
|
|
61
|
+
metadata: {
|
|
62
|
+
session_id: session.id,
|
|
63
|
+
previous_status: previousStatus,
|
|
64
|
+
next_status: status,
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
await paymentSessionService.update(session.id, {
|
|
70
|
+
status,
|
|
71
|
+
data: {
|
|
72
|
+
...(session.data || {}),
|
|
73
|
+
paypal: {
|
|
74
|
+
...((session.data || {}).paypal || {}),
|
|
75
|
+
order,
|
|
76
|
+
reconciliation: {
|
|
77
|
+
last_reconciled_at: new Date().toISOString(),
|
|
78
|
+
last_status: status,
|
|
79
|
+
order_status: order?.status,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
})
|
|
84
|
+
sessionsUpdated += 1
|
|
85
|
+
try {
|
|
86
|
+
await paypal.recordMetric("reconcile_success")
|
|
87
|
+
} catch {
|
|
88
|
+
// ignore metrics failures
|
|
89
|
+
}
|
|
90
|
+
} catch (error) {
|
|
91
|
+
hadFailure = true
|
|
92
|
+
lastErrorMessage = error instanceof Error ? error.message : String(error)
|
|
93
|
+
try {
|
|
94
|
+
await paypal.recordMetric("reconcile_failed")
|
|
95
|
+
} catch {
|
|
96
|
+
// ignore metrics failures
|
|
97
|
+
}
|
|
98
|
+
await paypal.sendAlert({
|
|
99
|
+
type: "reconcile_failed",
|
|
100
|
+
message: `PayPal reconciliation failed for order ${orderId}.`,
|
|
101
|
+
metadata: {
|
|
102
|
+
session_id: session.id,
|
|
103
|
+
},
|
|
104
|
+
})
|
|
105
|
+
console.error("[PayPal] reconcile error", error)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
await paypal.updateReconciliationStatus({
|
|
111
|
+
status: hadFailure ? "failed" : "success",
|
|
112
|
+
sessions_checked: sessionsChecked,
|
|
113
|
+
sessions_updated: sessionsUpdated,
|
|
114
|
+
drift_count: driftCount,
|
|
115
|
+
last_drift_at: lastDriftAt,
|
|
116
|
+
last_drift_order_id: lastDriftOrderId,
|
|
117
|
+
error_message: hadFailure ? lastErrorMessage : null,
|
|
118
|
+
})
|
|
119
|
+
} catch (error: any) {
|
|
120
|
+
await paypal.updateReconciliationStatus({
|
|
121
|
+
status: "failed",
|
|
122
|
+
sessions_checked: sessionsChecked,
|
|
123
|
+
sessions_updated: sessionsUpdated,
|
|
124
|
+
drift_count: driftCount,
|
|
125
|
+
last_drift_at: lastDriftAt,
|
|
126
|
+
last_drift_order_id: lastDriftOrderId,
|
|
127
|
+
error_message: error?.message,
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const config = {
|
|
133
|
+
name: "paypal-reconcile",
|
|
134
|
+
schedule: "*/15 * * * *",
|
|
135
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { MedusaContainer } from "@medusajs/framework/types"
|
|
2
|
+
import type PayPalModuleService from "../modules/paypal/service"
|
|
3
|
+
import {
|
|
4
|
+
computeNextRetryAt,
|
|
5
|
+
isAllowedEventType,
|
|
6
|
+
processPayPalWebhookEvent,
|
|
7
|
+
} from "../modules/paypal/webhook-processor"
|
|
8
|
+
|
|
9
|
+
const MAX_WEBHOOK_ATTEMPTS = 5
|
|
10
|
+
|
|
11
|
+
export default async function paypalWebhookRetry(container: MedusaContainer) {
|
|
12
|
+
const paypal = container.resolve<PayPalModuleService>("paypal_onboarding")
|
|
13
|
+
const now = Date.now()
|
|
14
|
+
const candidates = await paypal.listPayPalWebhookEvents({ status: "failed" })
|
|
15
|
+
|
|
16
|
+
for (const event of candidates || []) {
|
|
17
|
+
const nextRetryAt = event?.next_retry_at ? new Date(event.next_retry_at).getTime() : null
|
|
18
|
+
if (!nextRetryAt || nextRetryAt > now) {
|
|
19
|
+
continue
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const attemptCount = Number(event.attempt_count || 0) + 1
|
|
23
|
+
if (attemptCount > MAX_WEBHOOK_ATTEMPTS) {
|
|
24
|
+
continue
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
await paypal.updateWebhookEventRecord({
|
|
28
|
+
id: event.id,
|
|
29
|
+
status: "processing",
|
|
30
|
+
attempt_count: attemptCount,
|
|
31
|
+
next_retry_at: null,
|
|
32
|
+
last_error: null,
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const eventType = String(event.event_type || "")
|
|
37
|
+
if (!isAllowedEventType(eventType)) {
|
|
38
|
+
await paypal.updateWebhookEventRecord({
|
|
39
|
+
id: event.id,
|
|
40
|
+
status: "ignored",
|
|
41
|
+
processed_at: new Date(),
|
|
42
|
+
})
|
|
43
|
+
continue
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const payload = (event.payload || {}) as Record<string, any>
|
|
47
|
+
const processed = await processPayPalWebhookEvent(container, {
|
|
48
|
+
eventType,
|
|
49
|
+
payload,
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
await paypal.updateWebhookEventRecord({
|
|
53
|
+
id: event.id,
|
|
54
|
+
status: "processed",
|
|
55
|
+
processed_at: new Date(),
|
|
56
|
+
resource_id:
|
|
57
|
+
processed.disputeId || processed.refundId || processed.captureId || processed.orderId || null,
|
|
58
|
+
})
|
|
59
|
+
try {
|
|
60
|
+
await paypal.recordMetric("webhook_retry_success")
|
|
61
|
+
} catch {
|
|
62
|
+
// ignore metrics failures
|
|
63
|
+
}
|
|
64
|
+
} catch (error: any) {
|
|
65
|
+
const nextRetry = attemptCount >= MAX_WEBHOOK_ATTEMPTS ? null : computeNextRetryAt(attemptCount)
|
|
66
|
+
await paypal.updateWebhookEventRecord({
|
|
67
|
+
id: event.id,
|
|
68
|
+
status: "failed",
|
|
69
|
+
attempt_count: attemptCount,
|
|
70
|
+
next_retry_at: nextRetry,
|
|
71
|
+
last_error: error?.message || String(error),
|
|
72
|
+
})
|
|
73
|
+
try {
|
|
74
|
+
await paypal.recordMetric("webhook_retry_failed")
|
|
75
|
+
} catch {
|
|
76
|
+
// ignore metrics failures
|
|
77
|
+
}
|
|
78
|
+
console.error("[PayPal] webhook retry error", error)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const config = {
|
|
84
|
+
name: "paypal-webhook-retry",
|
|
85
|
+
schedule: "*/10 * * * *",
|
|
86
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export class PayPalSellerClient {
|
|
2
|
+
constructor(private opts: { environment: "sandbox" | "live"; accessToken: string }) {}
|
|
3
|
+
|
|
4
|
+
private baseUrl() {
|
|
5
|
+
return this.opts.environment === "live"
|
|
6
|
+
? "https://api-m.paypal.com"
|
|
7
|
+
: "https://api-m.sandbox.paypal.com"
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
private headers(extra?: Record<string, string>) {
|
|
11
|
+
return {
|
|
12
|
+
"Content-Type": "application/json",
|
|
13
|
+
Authorization: `Bearer ${this.opts.accessToken}`,
|
|
14
|
+
...(extra ?? {}),
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async createOrder(body: any) {
|
|
19
|
+
const res = await fetch(`${this.baseUrl()}/v2/checkout/orders`, {
|
|
20
|
+
method: "POST",
|
|
21
|
+
headers: this.headers(),
|
|
22
|
+
body: JSON.stringify(body),
|
|
23
|
+
})
|
|
24
|
+
const json = await res.json().catch(() => ({}))
|
|
25
|
+
if (!res.ok) throw new Error(`PayPal createOrder failed (${res.status}): ${JSON.stringify(json)}`)
|
|
26
|
+
return json
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async getOrder(orderId: string) {
|
|
30
|
+
const res = await fetch(`${this.baseUrl()}/v2/checkout/orders/${orderId}`, {
|
|
31
|
+
method: "GET",
|
|
32
|
+
headers: this.headers(),
|
|
33
|
+
})
|
|
34
|
+
const json = await res.json().catch(() => ({}))
|
|
35
|
+
if (!res.ok) throw new Error(`PayPal getOrder failed (${res.status}): ${JSON.stringify(json)}`)
|
|
36
|
+
return json
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async captureOrder(orderId: string) {
|
|
40
|
+
const res = await fetch(`${this.baseUrl()}/v2/checkout/orders/${orderId}/capture`, {
|
|
41
|
+
method: "POST",
|
|
42
|
+
headers: this.headers(),
|
|
43
|
+
})
|
|
44
|
+
const json = await res.json().catch(() => ({}))
|
|
45
|
+
if (!res.ok) throw new Error(`PayPal captureOrder failed (${res.status}): ${JSON.stringify(json)}`)
|
|
46
|
+
return json
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async refundCapture(captureId: string, body?: any) {
|
|
50
|
+
const res = await fetch(`${this.baseUrl()}/v2/payments/captures/${captureId}/refund`, {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: this.headers(),
|
|
53
|
+
body: body ? JSON.stringify(body) : "{}",
|
|
54
|
+
})
|
|
55
|
+
const json = await res.json().catch(() => ({}))
|
|
56
|
+
if (!res.ok) throw new Error(`PayPal refund failed (${res.status}): ${JSON.stringify(json)}`)
|
|
57
|
+
return json
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Migration } from "@medusajs/framework/mikro-orm/migrations"
|
|
2
|
+
|
|
3
|
+
export class Migration20260115120000 extends Migration {
|
|
4
|
+
async up(): Promise<void> {
|
|
5
|
+
this.addSql(`
|
|
6
|
+
CREATE TABLE IF NOT EXISTS "paypal_connection" (
|
|
7
|
+
"id" text NOT NULL,
|
|
8
|
+
"environment" text NOT NULL DEFAULT 'sandbox',
|
|
9
|
+
"status" text NOT NULL DEFAULT 'disconnected',
|
|
10
|
+
"shared_id" text NULL,
|
|
11
|
+
"auth_code" text NULL,
|
|
12
|
+
"seller_client_id" text NULL,
|
|
13
|
+
"seller_client_secret" text NULL,
|
|
14
|
+
"app_access_token" text NULL,
|
|
15
|
+
"app_access_token_expires_at" timestamptz NULL,
|
|
16
|
+
"metadata" jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
17
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
18
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
19
|
+
"deleted_at" timestamptz NULL,
|
|
20
|
+
CONSTRAINT "paypal_connection_pkey" PRIMARY KEY ("id")
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
CREATE INDEX IF NOT EXISTS "idx_paypal_connection_deleted_at"
|
|
24
|
+
ON "paypal_connection" ("deleted_at");
|
|
25
|
+
`)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async down(): Promise<void> {
|
|
29
|
+
this.addSql(`
|
|
30
|
+
DROP TABLE IF EXISTS "paypal_connection" CASCADE;
|
|
31
|
+
`)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Migration } from "@medusajs/framework/mikro-orm/migrations"
|
|
2
|
+
|
|
3
|
+
export class Migration20260123090000 extends Migration {
|
|
4
|
+
async up(): Promise<void> {
|
|
5
|
+
this.addSql(`
|
|
6
|
+
CREATE TABLE IF NOT EXISTS "paypal_settings" (
|
|
7
|
+
"id" text NOT NULL,
|
|
8
|
+
"data" jsonb NULL,
|
|
9
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
10
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
11
|
+
"deleted_at" timestamptz NULL,
|
|
12
|
+
CONSTRAINT "paypal_settings_pkey" PRIMARY KEY ("id")
|
|
13
|
+
);
|
|
14
|
+
CREATE INDEX IF NOT EXISTS "idx_paypal_settings_deleted_at"
|
|
15
|
+
ON "paypal_settings" ("deleted_at");
|
|
16
|
+
`)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async down(): Promise<void> {
|
|
20
|
+
this.addSql(`DROP TABLE IF EXISTS "paypal_settings" CASCADE;`)
|
|
21
|
+
}
|
|
22
|
+
}
|