@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,270 @@
|
|
|
1
|
+
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
|
2
|
+
import { randomUUID } from "crypto"
|
|
3
|
+
import type PayPalModuleService from "../../../../modules/paypal/service"
|
|
4
|
+
import { isPayPalProviderId } from "../../../../modules/paypal/utils/provider-ids"
|
|
5
|
+
|
|
6
|
+
type Body = {
|
|
7
|
+
cart_id: string
|
|
8
|
+
order_id: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function getPayPalApiBase(environment: string) {
|
|
12
|
+
return environment === "live"
|
|
13
|
+
? "https://api-m.paypal.com"
|
|
14
|
+
: "https://api-m.sandbox.paypal.com"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function getPayPalAccessToken(opts: {
|
|
18
|
+
environment: string
|
|
19
|
+
client_id: string
|
|
20
|
+
client_secret: string
|
|
21
|
+
}) {
|
|
22
|
+
const base = await getPayPalApiBase(opts.environment)
|
|
23
|
+
const auth = Buffer.from(`${opts.client_id}:${opts.client_secret}`).toString("base64")
|
|
24
|
+
|
|
25
|
+
const resp = await fetch(`${base}/v1/oauth2/token`, {
|
|
26
|
+
method: "POST",
|
|
27
|
+
headers: {
|
|
28
|
+
Authorization: `Basic ${auth}`,
|
|
29
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
30
|
+
},
|
|
31
|
+
body: "grant_type=client_credentials",
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const text = await resp.text()
|
|
35
|
+
if (!resp.ok) {
|
|
36
|
+
throw new Error(`PayPal token error (${resp.status}): ${text}`)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const json = JSON.parse(text)
|
|
40
|
+
return { accessToken: String(json.access_token), base }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function resolveIdempotencyKey(req: MedusaRequest, suffix: string, fallback: string) {
|
|
44
|
+
const header =
|
|
45
|
+
req.headers["idempotency-key"] ||
|
|
46
|
+
req.headers["Idempotency-Key"] ||
|
|
47
|
+
req.headers["x-idempotency-key"] ||
|
|
48
|
+
req.headers["X-Idempotency-Key"]
|
|
49
|
+
const key = Array.isArray(header) ? header[0] : header
|
|
50
|
+
if (key && String(key).trim()) {
|
|
51
|
+
return `${String(key).trim()}-${suffix}`
|
|
52
|
+
}
|
|
53
|
+
return fallback || `pp-${suffix}-${randomUUID()}`
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function attachPayPalCaptureToSession(
|
|
57
|
+
req: MedusaRequest,
|
|
58
|
+
cartId: string,
|
|
59
|
+
orderId: string,
|
|
60
|
+
capture: any
|
|
61
|
+
) {
|
|
62
|
+
try {
|
|
63
|
+
const paymentCollectionService = req.scope.resolve("payment_collection") as any
|
|
64
|
+
const paymentSessionService = req.scope.resolve("payment_session") as any
|
|
65
|
+
|
|
66
|
+
const pc = await paymentCollectionService.retrieveByCartId(cartId).catch(() => null)
|
|
67
|
+
if (!pc?.id) {
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const sessions = await paymentSessionService.list({ payment_collection_id: pc.id })
|
|
72
|
+
const paypalSession = sessions?.find((s: any) => isPayPalProviderId(s.provider_id))
|
|
73
|
+
if (!paypalSession) {
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await paymentSessionService.update(paypalSession.id, {
|
|
78
|
+
status: "captured",
|
|
79
|
+
data: {
|
|
80
|
+
...(paypalSession.data || {}),
|
|
81
|
+
paypal: {
|
|
82
|
+
...((paypalSession.data || {}).paypal || {}),
|
|
83
|
+
order_id: orderId,
|
|
84
|
+
capture_id: capture?.id || capture?.purchase_units?.[0]?.payments?.captures?.[0]?.id,
|
|
85
|
+
capture,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
})
|
|
89
|
+
} catch {
|
|
90
|
+
// ignore
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function attachPayPalAuthorizationToSession(
|
|
95
|
+
req: MedusaRequest,
|
|
96
|
+
cartId: string,
|
|
97
|
+
orderId: string,
|
|
98
|
+
authorization: any
|
|
99
|
+
) {
|
|
100
|
+
try {
|
|
101
|
+
const paymentCollectionService = req.scope.resolve("payment_collection") as any
|
|
102
|
+
const paymentSessionService = req.scope.resolve("payment_session") as any
|
|
103
|
+
|
|
104
|
+
const pc = await paymentCollectionService.retrieveByCartId(cartId).catch(() => null)
|
|
105
|
+
if (!pc?.id) {
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const sessions = await paymentSessionService.list({ payment_collection_id: pc.id })
|
|
110
|
+
const paypalSession = sessions?.find((s: any) => isPayPalProviderId(s.provider_id))
|
|
111
|
+
if (!paypalSession) {
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
await paymentSessionService.update(paypalSession.id, {
|
|
116
|
+
status: "authorized",
|
|
117
|
+
data: {
|
|
118
|
+
...(paypalSession.data || {}),
|
|
119
|
+
paypal: {
|
|
120
|
+
...((paypalSession.data || {}).paypal || {}),
|
|
121
|
+
order_id: orderId,
|
|
122
|
+
authorization_id:
|
|
123
|
+
authorization?.purchase_units?.[0]?.payments?.authorizations?.[0]?.id,
|
|
124
|
+
authorization,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
})
|
|
128
|
+
} catch {
|
|
129
|
+
// ignore
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async function getExistingCapture(
|
|
133
|
+
req: MedusaRequest,
|
|
134
|
+
cartId: string,
|
|
135
|
+
orderId: string
|
|
136
|
+
) {
|
|
137
|
+
try {
|
|
138
|
+
const paymentCollectionService = req.scope.resolve("payment_collection") as any
|
|
139
|
+
const paymentSessionService = req.scope.resolve("payment_session") as any
|
|
140
|
+
|
|
141
|
+
const pc = await paymentCollectionService.retrieveByCartId(cartId).catch(() => null)
|
|
142
|
+
if (!pc?.id) {
|
|
143
|
+
return null
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const sessions = await paymentSessionService.list({ payment_collection_id: pc.id })
|
|
147
|
+
const paypalSession = sessions?.find((s: any) => isPayPalProviderId(s.provider_id))
|
|
148
|
+
if (!paypalSession) {
|
|
149
|
+
return null
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const paypalData = (paypalSession.data || {}).paypal || {}
|
|
153
|
+
const existingOrderId = String(paypalData.order_id || "")
|
|
154
|
+
if (existingOrderId && existingOrderId !== orderId) {
|
|
155
|
+
return null
|
|
156
|
+
}
|
|
157
|
+
if (paypalData.capture) {
|
|
158
|
+
return paypalData.capture
|
|
159
|
+
}
|
|
160
|
+
if (paypalData.capture_id) {
|
|
161
|
+
return { id: paypalData.capture_id }
|
|
162
|
+
}
|
|
163
|
+
return null
|
|
164
|
+
} catch {
|
|
165
|
+
return null
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
export async function POST(req: MedusaRequest, res: MedusaResponse) {
|
|
171
|
+
const paypal = req.scope.resolve<PayPalModuleService>("paypal_onboarding")
|
|
172
|
+
let debugId: string | null = null
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const body = (req.body || {}) as Body
|
|
176
|
+
const cartId = body.cart_id
|
|
177
|
+
const orderId = body.order_id
|
|
178
|
+
|
|
179
|
+
if (!cartId || !orderId) {
|
|
180
|
+
return res.status(400).json({ message: "cart_id and order_id are required" })
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const existingCapture = await getExistingCapture(req, cartId, orderId)
|
|
184
|
+
if (existingCapture) {
|
|
185
|
+
return res.json({ capture: existingCapture })
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const creds = await paypal.getActiveCredentials()
|
|
189
|
+
const { accessToken, base } = await getPayPalAccessToken(creds)
|
|
190
|
+
const settings = await paypal.getSettings().catch(() => ({}))
|
|
191
|
+
const data =
|
|
192
|
+
settings && typeof settings === "object" && "data" in settings
|
|
193
|
+
? ((settings as { data?: Record<string, any> }).data ?? {})
|
|
194
|
+
: {}
|
|
195
|
+
const additionalSettings = (data.additional_settings || {}) as Record<string, any>
|
|
196
|
+
const paymentAction =
|
|
197
|
+
typeof additionalSettings.paymentAction === "string"
|
|
198
|
+
? additionalSettings.paymentAction
|
|
199
|
+
: "capture"
|
|
200
|
+
|
|
201
|
+
const requestId = resolveIdempotencyKey(req, "capture-order", `pp-capture-${orderId}`)
|
|
202
|
+
const endpoint =
|
|
203
|
+
paymentAction === "authorize"
|
|
204
|
+
? `${base}/v2/checkout/orders/${orderId}/authorize`
|
|
205
|
+
: `${base}/v2/checkout/orders/${orderId}/capture`
|
|
206
|
+
|
|
207
|
+
const ppResp = await fetch(endpoint, {
|
|
208
|
+
method: "POST",
|
|
209
|
+
headers: {
|
|
210
|
+
Authorization: `Bearer ${accessToken}`,
|
|
211
|
+
"Content-Type": "application/json",
|
|
212
|
+
"PayPal-Request-Id": requestId,
|
|
213
|
+
},
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
const ppText = await ppResp.text()
|
|
217
|
+
debugId = ppResp.headers.get("paypal-debug-id")
|
|
218
|
+
if (!ppResp.ok) {
|
|
219
|
+
throw new Error(
|
|
220
|
+
`PayPal capture error (${ppResp.status}): ${ppText}${
|
|
221
|
+
debugId ? ` debug_id=${debugId}` : ""
|
|
222
|
+
}`
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const payload = JSON.parse(ppText)
|
|
227
|
+
if (paymentAction === "authorize") {
|
|
228
|
+
await attachPayPalAuthorizationToSession(req, cartId, orderId, payload)
|
|
229
|
+
} else {
|
|
230
|
+
await attachPayPalCaptureToSession(req, cartId, orderId, payload)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
console.info("[PayPal] capture-order", {
|
|
234
|
+
cart_id: cartId,
|
|
235
|
+
order_id: orderId,
|
|
236
|
+
request_id: requestId,
|
|
237
|
+
debug_id: ppResp.headers.get("paypal-debug-id"),
|
|
238
|
+
capture_id: payload?.id,
|
|
239
|
+
})
|
|
240
|
+
try {
|
|
241
|
+
await paypal.recordMetric(
|
|
242
|
+
paymentAction === "authorize" ? "authorize_order_success" : "capture_order_success"
|
|
243
|
+
)
|
|
244
|
+
} catch {
|
|
245
|
+
// ignore metrics failures
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
// Clear storefront cart cookie (httpOnly) so user gets a fresh cart after successful payment
|
|
250
|
+
// Note: works when storefront and backend share the same cookie domain (e.g. localhost)
|
|
251
|
+
res.setHeader("Set-Cookie", "_medusa_cart_id=; Path=/; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly; SameSite=Strict")
|
|
252
|
+
return paymentAction === "authorize"
|
|
253
|
+
? res.json({ authorization: payload })
|
|
254
|
+
: res.json({ capture: payload })
|
|
255
|
+
} catch (e: any) {
|
|
256
|
+
try {
|
|
257
|
+
const body = (req.body || {}) as Body
|
|
258
|
+
await paypal.recordAuditEvent("capture_order_failed", {
|
|
259
|
+
cart_id: body.cart_id,
|
|
260
|
+
order_id: body.order_id,
|
|
261
|
+
debug_id: debugId,
|
|
262
|
+
message: e?.message || String(e),
|
|
263
|
+
})
|
|
264
|
+
await paypal.recordMetric("capture_order_failed")
|
|
265
|
+
} catch {
|
|
266
|
+
// ignore audit logging failures
|
|
267
|
+
}
|
|
268
|
+
return res.status(500).json({ message: e?.message || "Failed to capture PayPal order" })
|
|
269
|
+
}
|
|
270
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
|
2
|
+
import type PayPalModuleService from "../../../../modules/paypal/service"
|
|
3
|
+
import {
|
|
4
|
+
getPayPalCurrencyCompatibility,
|
|
5
|
+
getPayPalSupportedCurrencies,
|
|
6
|
+
normalizeCurrencyCode,
|
|
7
|
+
} from "../../../../modules/paypal/utils/currencies"
|
|
8
|
+
|
|
9
|
+
export async function GET(req: MedusaRequest, res: MedusaResponse) {
|
|
10
|
+
const paypal = req.scope.resolve<PayPalModuleService>("paypal_onboarding")
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const creds = await paypal.getActiveCredentials()
|
|
14
|
+
const apiDetails = await paypal.getApiDetails().catch(() => null)
|
|
15
|
+
|
|
16
|
+
// CardFields/PaymentFields require a client token on the script tag.
|
|
17
|
+
// Generate it server-side and return it with config.
|
|
18
|
+
const client_token = await paypal.generateClientToken({ locale: "en_US" }).catch(() => "")
|
|
19
|
+
|
|
20
|
+
const cartId = (req.query?.cart_id as string) || ""
|
|
21
|
+
const query = req.scope.resolve("query")
|
|
22
|
+
|
|
23
|
+
let currency = normalizeCurrencyCode(
|
|
24
|
+
apiDetails?.apiDetails?.currency_code || process.env.PAYPAL_CURRENCY || "USD"
|
|
25
|
+
)
|
|
26
|
+
if (cartId) {
|
|
27
|
+
const { data: carts } = await query.graph({
|
|
28
|
+
entity: "cart",
|
|
29
|
+
fields: ["id", "currency_code", "region.currency_code"],
|
|
30
|
+
filters: { id: cartId },
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const cart = carts?.[0]
|
|
34
|
+
if (cart) {
|
|
35
|
+
currency = normalizeCurrencyCode(
|
|
36
|
+
cart.region?.currency_code || cart.currency_code || currency
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const compatibility = getPayPalCurrencyCompatibility({
|
|
42
|
+
currencyCode: currency,
|
|
43
|
+
paypalCurrencyOverride:
|
|
44
|
+
apiDetails?.apiDetails?.currency_code || process.env.PAYPAL_CURRENCY,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
return res.json({
|
|
48
|
+
environment: creds.environment,
|
|
49
|
+
client_id: creds.client_id,
|
|
50
|
+
currency: compatibility.currency,
|
|
51
|
+
currency_supported: compatibility.supported,
|
|
52
|
+
currency_errors: compatibility.errors,
|
|
53
|
+
supported_currencies: getPayPalSupportedCurrencies(),
|
|
54
|
+
client_token,
|
|
55
|
+
})
|
|
56
|
+
} catch (e: any) {
|
|
57
|
+
return res.status(500).json({ message: e?.message || "Failed to load PayPal config" })
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
|
2
|
+
import { randomUUID } from "crypto"
|
|
3
|
+
import { formatAmountForPayPal } from "../../../../modules/paypal/utils/amounts"
|
|
4
|
+
import {
|
|
5
|
+
assertPayPalCurrencySupported,
|
|
6
|
+
normalizeCurrencyCode,
|
|
7
|
+
} from "../../../../modules/paypal/utils/currencies"
|
|
8
|
+
import type PayPalModuleService from "../../../../modules/paypal/service"
|
|
9
|
+
import { isPayPalProviderId } from "../../../../modules/paypal/utils/provider-ids"
|
|
10
|
+
|
|
11
|
+
type Body = {
|
|
12
|
+
cart_id: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function getPayPalApiBase(environment: string) {
|
|
16
|
+
return environment === "live"
|
|
17
|
+
? "https://api-m.paypal.com"
|
|
18
|
+
: "https://api-m.sandbox.paypal.com"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function getPayPalAccessToken(opts: {
|
|
22
|
+
environment: string
|
|
23
|
+
client_id: string
|
|
24
|
+
client_secret: string
|
|
25
|
+
}) {
|
|
26
|
+
const base = await getPayPalApiBase(opts.environment)
|
|
27
|
+
const auth = Buffer.from(`${opts.client_id}:${opts.client_secret}`).toString("base64")
|
|
28
|
+
|
|
29
|
+
const resp = await fetch(`${base}/v1/oauth2/token`, {
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers: {
|
|
32
|
+
Authorization: `Basic ${auth}`,
|
|
33
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
34
|
+
},
|
|
35
|
+
body: "grant_type=client_credentials",
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const text = await resp.text()
|
|
39
|
+
if (!resp.ok) {
|
|
40
|
+
throw new Error(`PayPal token error (${resp.status}): ${text}`)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const json = JSON.parse(text)
|
|
44
|
+
return { accessToken: String(json.access_token), base }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function resolveIdempotencyKey(req: MedusaRequest, suffix: string, fallback: string) {
|
|
48
|
+
const header =
|
|
49
|
+
req.headers["idempotency-key"] ||
|
|
50
|
+
req.headers["Idempotency-Key"] ||
|
|
51
|
+
req.headers["x-idempotency-key"] ||
|
|
52
|
+
req.headers["X-Idempotency-Key"]
|
|
53
|
+
const key = Array.isArray(header) ? header[0] : header
|
|
54
|
+
if (key && String(key).trim()) {
|
|
55
|
+
return `${String(key).trim()}-${suffix}`
|
|
56
|
+
}
|
|
57
|
+
return fallback || `pp-${suffix}-${randomUUID()}`
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function attachPayPalOrderToSession(
|
|
61
|
+
req: MedusaRequest,
|
|
62
|
+
cartId: string,
|
|
63
|
+
orderId: string
|
|
64
|
+
) {
|
|
65
|
+
try {
|
|
66
|
+
const paymentCollectionService = req.scope.resolve("payment_collection") as any
|
|
67
|
+
const paymentSessionService = req.scope.resolve("payment_session") as any
|
|
68
|
+
|
|
69
|
+
const pc = await paymentCollectionService.retrieveByCartId(cartId).catch(() => null)
|
|
70
|
+
if (!pc?.id) {
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const sessions = await paymentSessionService.list({ payment_collection_id: pc.id })
|
|
75
|
+
const paypalSession = sessions?.find((s: any) => isPayPalProviderId(s.provider_id))
|
|
76
|
+
if (!paypalSession) {
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
await paymentSessionService.update(paypalSession.id, {
|
|
81
|
+
data: {
|
|
82
|
+
...(paypalSession.data || {}),
|
|
83
|
+
paypal: {
|
|
84
|
+
...((paypalSession.data || {}).paypal || {}),
|
|
85
|
+
order_id: orderId,
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
})
|
|
89
|
+
} catch {
|
|
90
|
+
// ignore best-effort session update
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function getExistingPayPalOrderId(req: MedusaRequest, cartId: string) {
|
|
95
|
+
try {
|
|
96
|
+
const paymentCollectionService = req.scope.resolve("payment_collection") as any
|
|
97
|
+
const paymentSessionService = req.scope.resolve("payment_session") as any
|
|
98
|
+
|
|
99
|
+
const pc = await paymentCollectionService.retrieveByCartId(cartId).catch(() => null)
|
|
100
|
+
if (!pc?.id) {
|
|
101
|
+
return null
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const sessions = await paymentSessionService.list({ payment_collection_id: pc.id })
|
|
105
|
+
const paypalSession = sessions?.find((s: any) => isPayPalProviderId(s.provider_id))
|
|
106
|
+
if (!paypalSession) {
|
|
107
|
+
return null
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const paypalData = (paypalSession.data || {}).paypal || {}
|
|
111
|
+
return paypalData.order_id ? String(paypalData.order_id) : null
|
|
112
|
+
} catch {
|
|
113
|
+
return null
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function resolveReturnUrl(req: MedusaRequest) {
|
|
118
|
+
const configured = process.env.STOREFRONT_URL || process.env.STORE_URL
|
|
119
|
+
if (!configured) {
|
|
120
|
+
return undefined
|
|
121
|
+
}
|
|
122
|
+
return `${configured.replace(/\/$/, "")}/checkout`
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function resolveCancelUrl(req: MedusaRequest) {
|
|
126
|
+
const configured = process.env.STOREFRONT_URL || process.env.STORE_URL
|
|
127
|
+
if (!configured) {
|
|
128
|
+
return undefined
|
|
129
|
+
}
|
|
130
|
+
return `${configured.replace(/\/$/, "")}/cart`
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function POST(req: MedusaRequest, res: MedusaResponse) {
|
|
134
|
+
const paypal = req.scope.resolve<PayPalModuleService>("paypal_onboarding")
|
|
135
|
+
let debugId: string | null = null
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const body = (req.body || {}) as Body
|
|
139
|
+
const cartId = body.cart_id
|
|
140
|
+
|
|
141
|
+
if (!cartId) {
|
|
142
|
+
return res.status(400).json({ message: "cart_id is required" })
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const existingOrderId = await getExistingPayPalOrderId(req, cartId)
|
|
146
|
+
if (existingOrderId) {
|
|
147
|
+
return res.json({ id: existingOrderId })
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* ✅ Medusa v2 cart retrieval via Query Graph
|
|
152
|
+
*/
|
|
153
|
+
const query = req.scope.resolve("query")
|
|
154
|
+
|
|
155
|
+
const { data } = await query.graph({
|
|
156
|
+
entity: "cart",
|
|
157
|
+
fields: [
|
|
158
|
+
"id",
|
|
159
|
+
"total",
|
|
160
|
+
"subtotal",
|
|
161
|
+
"shipping_total",
|
|
162
|
+
"tax_total",
|
|
163
|
+
"discount_total",
|
|
164
|
+
"gift_card_total",
|
|
165
|
+
"currency_code",
|
|
166
|
+
"region.currency_code",
|
|
167
|
+
],
|
|
168
|
+
filters: { id: cartId },
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
const cart = (data?.[0] as any) || null
|
|
172
|
+
|
|
173
|
+
if (!cart) {
|
|
174
|
+
return res.status(404).json({ message: "Cart not found" })
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const creds = await paypal.getActiveCredentials()
|
|
178
|
+
|
|
179
|
+
type PayPalSettingsResponse = {
|
|
180
|
+
data?: { additional_settings?: Record<string, unknown>; api_details?: Record<string, unknown> }
|
|
181
|
+
}
|
|
182
|
+
const settings = await paypal
|
|
183
|
+
.getSettings()
|
|
184
|
+
.catch((): PayPalSettingsResponse => ({}))
|
|
185
|
+
const additionalSettings = settings.data?.additional_settings || {}
|
|
186
|
+
const apiDetails = settings.data?.api_details || {}
|
|
187
|
+
const configuredCurrency =
|
|
188
|
+
typeof apiDetails.currency_code === "string"
|
|
189
|
+
? normalizeCurrencyCode(apiDetails.currency_code)
|
|
190
|
+
: normalizeCurrencyCode(process.env.PAYPAL_CURRENCY || "USD")
|
|
191
|
+
|
|
192
|
+
const currency = normalizeCurrencyCode(
|
|
193
|
+
cart.region?.currency_code || cart.currency_code || configuredCurrency
|
|
194
|
+
)
|
|
195
|
+
assertPayPalCurrencySupported({
|
|
196
|
+
currencyCode: currency,
|
|
197
|
+
paypalCurrencyOverride: configuredCurrency,
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
const totalMinor = Number(cart.total || 0)
|
|
201
|
+
const value = formatAmountForPayPal(totalMinor, currency)
|
|
202
|
+
|
|
203
|
+
const paymentActionRaw =
|
|
204
|
+
typeof additionalSettings.paymentAction === "string"
|
|
205
|
+
? additionalSettings.paymentAction
|
|
206
|
+
: "capture"
|
|
207
|
+
const paymentAction = paymentActionRaw === "authorize" ? "AUTHORIZE" : "CAPTURE"
|
|
208
|
+
const brandName =
|
|
209
|
+
typeof additionalSettings.brandName === "string"
|
|
210
|
+
? additionalSettings.brandName
|
|
211
|
+
: undefined
|
|
212
|
+
const landingPageRaw =
|
|
213
|
+
typeof additionalSettings.landingPage === "string"
|
|
214
|
+
? additionalSettings.landingPage
|
|
215
|
+
: undefined
|
|
216
|
+
const landingPage =
|
|
217
|
+
landingPageRaw === "login"
|
|
218
|
+
? "LOGIN"
|
|
219
|
+
: landingPageRaw === "billing"
|
|
220
|
+
? "BILLING"
|
|
221
|
+
: landingPageRaw === "no_preference"
|
|
222
|
+
? "NO_PREFERENCE"
|
|
223
|
+
: undefined
|
|
224
|
+
const skipReview =
|
|
225
|
+
typeof additionalSettings.skipOrderReviewPage === "boolean"
|
|
226
|
+
? additionalSettings.skipOrderReviewPage
|
|
227
|
+
: undefined
|
|
228
|
+
const requireInstantPayment =
|
|
229
|
+
typeof additionalSettings.requireInstantPayment === "boolean"
|
|
230
|
+
? additionalSettings.requireInstantPayment
|
|
231
|
+
: undefined
|
|
232
|
+
const invoicePrefix =
|
|
233
|
+
typeof additionalSettings.invoicePrefix === "string"
|
|
234
|
+
? additionalSettings.invoicePrefix
|
|
235
|
+
: ""
|
|
236
|
+
const invoiceId = `${invoicePrefix}${cart.id}`.trim() || cart.id
|
|
237
|
+
const returnUrl =
|
|
238
|
+
(typeof apiDetails.storefront_url === "string" && apiDetails.storefront_url.trim()
|
|
239
|
+
? `${apiDetails.storefront_url.replace(/\/$/, "")}/checkout`
|
|
240
|
+
: resolveReturnUrl(req))
|
|
241
|
+
const cancelUrl =
|
|
242
|
+
(typeof apiDetails.storefront_url === "string" && apiDetails.storefront_url.trim()
|
|
243
|
+
? `${apiDetails.storefront_url.replace(/\/$/, "")}/cart`
|
|
244
|
+
: resolveCancelUrl(req))
|
|
245
|
+
|
|
246
|
+
const applicationContext: Record<string, any> = {
|
|
247
|
+
...(brandName ? { brand_name: brandName } : {}),
|
|
248
|
+
...(landingPage ? { landing_page: landingPage } : {}),
|
|
249
|
+
...(typeof skipReview === "boolean"
|
|
250
|
+
? { user_action: skipReview ? "PAY_NOW" : "CONTINUE" }
|
|
251
|
+
: {}),
|
|
252
|
+
...(requireInstantPayment ? { payment_method_preference: "IMMEDIATE_PAYMENT_REQUIRED" } : {}),
|
|
253
|
+
...(returnUrl ? { return_url: returnUrl } : {}),
|
|
254
|
+
...(cancelUrl ? { cancel_url: cancelUrl } : {}),
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const breakdown: Record<string, any> = {}
|
|
258
|
+
const subtotalMinor = Number(cart.subtotal || 0)
|
|
259
|
+
const shippingMinor = Number(cart.shipping_total || 0)
|
|
260
|
+
const taxMinor = Number(cart.tax_total || 0)
|
|
261
|
+
const discountMinor = Number(cart.discount_total || 0)
|
|
262
|
+
const giftCardMinor = Number(cart.gift_card_total || 0)
|
|
263
|
+
const adjustmentMinor = Math.max(
|
|
264
|
+
0,
|
|
265
|
+
subtotalMinor + shippingMinor + taxMinor - totalMinor
|
|
266
|
+
)
|
|
267
|
+
if (subtotalMinor > 0) {
|
|
268
|
+
breakdown.item_total = {
|
|
269
|
+
currency_code: currency,
|
|
270
|
+
value: formatAmountForPayPal(subtotalMinor, currency),
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (shippingMinor > 0) {
|
|
274
|
+
breakdown.shipping = {
|
|
275
|
+
currency_code: currency,
|
|
276
|
+
value: formatAmountForPayPal(shippingMinor, currency),
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (taxMinor > 0) {
|
|
280
|
+
breakdown.tax_total = {
|
|
281
|
+
currency_code: currency,
|
|
282
|
+
value: formatAmountForPayPal(taxMinor, currency),
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const discountValue = Math.max(0, discountMinor + giftCardMinor, adjustmentMinor)
|
|
286
|
+
if (discountValue > 0) {
|
|
287
|
+
breakdown.discount = {
|
|
288
|
+
currency_code: currency,
|
|
289
|
+
value: formatAmountForPayPal(discountValue, currency),
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const { accessToken, base } = await getPayPalAccessToken({
|
|
294
|
+
environment: creds.environment,
|
|
295
|
+
client_id: creds.client_id,
|
|
296
|
+
client_secret: creds.client_secret,
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
const requestId = resolveIdempotencyKey(req, "create-order", `pp-create-${cart.id}`)
|
|
300
|
+
|
|
301
|
+
const ppResp = await fetch(`${base}/v2/checkout/orders`, {
|
|
302
|
+
method: "POST",
|
|
303
|
+
headers: {
|
|
304
|
+
Authorization: `Bearer ${accessToken}`,
|
|
305
|
+
"Content-Type": "application/json",
|
|
306
|
+
"PayPal-Request-Id": requestId,
|
|
307
|
+
},
|
|
308
|
+
body: JSON.stringify({
|
|
309
|
+
intent: paymentAction,
|
|
310
|
+
purchase_units: [
|
|
311
|
+
{
|
|
312
|
+
reference_id: cart.id,
|
|
313
|
+
invoice_id: invoiceId,
|
|
314
|
+
amount: {
|
|
315
|
+
currency_code: currency,
|
|
316
|
+
value,
|
|
317
|
+
...(Object.keys(breakdown).length > 0 ? { breakdown } : {}),
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
],
|
|
321
|
+
custom_id: cart.id,
|
|
322
|
+
...(Object.keys(applicationContext).length > 0
|
|
323
|
+
? { application_context: applicationContext }
|
|
324
|
+
: {}),
|
|
325
|
+
}),
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
const ppText = await ppResp.text()
|
|
329
|
+
debugId = ppResp.headers.get("paypal-debug-id")
|
|
330
|
+
if (!ppResp.ok) {
|
|
331
|
+
throw new Error(
|
|
332
|
+
`PayPal create order error (${ppResp.status}): ${ppText}${
|
|
333
|
+
debugId ? ` debug_id=${debugId}` : ""
|
|
334
|
+
}`
|
|
335
|
+
)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const order = JSON.parse(ppText)
|
|
339
|
+
|
|
340
|
+
await attachPayPalOrderToSession(req, cart.id, order.id)
|
|
341
|
+
|
|
342
|
+
console.info("[PayPal] create-order", {
|
|
343
|
+
cart_id: cart.id,
|
|
344
|
+
order_id: order.id,
|
|
345
|
+
request_id: requestId,
|
|
346
|
+
debug_id: ppResp.headers.get("paypal-debug-id"),
|
|
347
|
+
})
|
|
348
|
+
try {
|
|
349
|
+
await paypal.recordMetric("create_order_success")
|
|
350
|
+
} catch {
|
|
351
|
+
// ignore metrics failures
|
|
352
|
+
}
|
|
353
|
+
return res.json({ id: order.id })
|
|
354
|
+
} catch (e: any) {
|
|
355
|
+
try {
|
|
356
|
+
const body = (req.body || {}) as Body
|
|
357
|
+
await paypal.recordAuditEvent("create_order_failed", {
|
|
358
|
+
cart_id: body.cart_id,
|
|
359
|
+
debug_id: debugId,
|
|
360
|
+
message: e?.message || String(e),
|
|
361
|
+
})
|
|
362
|
+
await paypal.recordMetric("create_order_failed")
|
|
363
|
+
} catch {
|
|
364
|
+
// ignore audit logging failures
|
|
365
|
+
}
|
|
366
|
+
const message = e?.message || "Failed to create PayPal order"
|
|
367
|
+
const status = message.includes("PayPal does not support currency")
|
|
368
|
+
? 400
|
|
369
|
+
: message.includes("PayPal is configured for")
|
|
370
|
+
? 400
|
|
371
|
+
: 500
|
|
372
|
+
return res.status(status).json({ message })
|
|
373
|
+
}
|
|
374
|
+
}
|