@easypayment/medusa-payment-paypal 0.7.4
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 +1267 -0
- package/.medusa/server/src/admin/index.mjs +1268 -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 +18 -0
- package/.medusa/server/src/api/admin/payment-collections/[id]/payment-sessions/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/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 +4 -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 +31 -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 +19 -0
- package/.medusa/server/src/api/admin/paypal/onboarding-status/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 +17 -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/middlewares.d.ts +3 -0
- package/.medusa/server/src/api/middlewares.d.ts.map +1 -0
- package/.medusa/server/src/api/middlewares.js +16 -0
- package/.medusa/server/src/api/middlewares.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 +204 -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 +77 -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 +361 -0
- package/.medusa/server/src/api/store/paypal/create-order/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 +20 -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 +250 -0
- package/.medusa/server/src/api/store/paypal/webhook/route.js.map +1 -0
- package/.medusa/server/src/api/store/paypal-complete/route.d.ts +3 -0
- package/.medusa/server/src/api/store/paypal-complete/route.d.ts.map +1 -0
- package/.medusa/server/src/api/store/paypal-complete/route.js +76 -0
- package/.medusa/server/src/api/store/paypal-complete/route.js.map +1 -0
- package/.medusa/server/src/index.d.ts +2 -0
- package/.medusa/server/src/index.d.ts.map +1 -0
- package/.medusa/server/src/index.js +9 -0
- package/.medusa/server/src/index.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 +132 -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 +15 -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 +67 -0
- package/.medusa/server/src/modules/paypal/clients/paypal-seller.client.js.map +1 -0
- package/.medusa/server/src/modules/paypal/index.d.ts +64 -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/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/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/migrations/20261101090000_remove_paypal_reconciliation_status.d.ts +6 -0
- package/.medusa/server/src/modules/paypal/migrations/20261101090000_remove_paypal_reconciliation_status.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/migrations/20261101090000_remove_paypal_reconciliation_status.js +28 -0
- package/.medusa/server/src/modules/paypal/migrations/20261101090000_remove_paypal_reconciliation_status.js.map +1 -0
- package/.medusa/server/src/modules/paypal/migrations/20261201090000_remove_paypal_audit_log.d.ts +6 -0
- package/.medusa/server/src/modules/paypal/migrations/20261201090000_remove_paypal_audit_log.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/migrations/20261201090000_remove_paypal_audit_log.js +29 -0
- package/.medusa/server/src/modules/paypal/migrations/20261201090000_remove_paypal_audit_log.js.map +1 -0
- package/.medusa/server/src/modules/paypal/migrations/20270101090000_set_paypal_environment_default_live.d.ts +6 -0
- package/.medusa/server/src/modules/paypal/migrations/20270101090000_set_paypal_environment_default_live.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/migrations/20270101090000_set_paypal_environment_default_live.js +14 -0
- package/.medusa/server/src/modules/paypal/migrations/20270101090000_set_paypal_environment_default_live.js.map +1 -0
- package/.medusa/server/src/modules/paypal/migrations/20270102090000_add_paypal_seller_email.d.ts +6 -0
- package/.medusa/server/src/modules/paypal/migrations/20270102090000_add_paypal_seller_email.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/migrations/20270102090000_add_paypal_seller_email.js +14 -0
- package/.medusa/server/src/modules/paypal/migrations/20270102090000_add_paypal_seller_email.js.map +1 -0
- package/.medusa/server/src/modules/paypal/migrations/20270103090000_add_paypal_seller_merchant_id.d.ts +6 -0
- package/.medusa/server/src/modules/paypal/migrations/20270103090000_add_paypal_seller_merchant_id.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/migrations/20270103090000_add_paypal_seller_merchant_id.js +14 -0
- package/.medusa/server/src/modules/paypal/migrations/20270103090000_add_paypal_seller_merchant_id.js.map +1 -0
- package/.medusa/server/src/modules/paypal/migrations/20270201000000_add_webhook_dead_letter.d.ts +6 -0
- package/.medusa/server/src/modules/paypal/migrations/20270201000000_add_webhook_dead_letter.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/migrations/20270201000000_add_webhook_dead_letter.js +20 -0
- package/.medusa/server/src/modules/paypal/migrations/20270201000000_add_webhook_dead_letter.js.map +1 -0
- package/.medusa/server/src/modules/paypal/models/paypal_connection.d.ts +16 -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 +19 -0
- package/.medusa/server/src/modules/paypal/models/paypal_connection.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 +32 -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 +625 -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 +6 -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 +18 -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 +39 -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 +869 -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 +266 -0
- package/.medusa/server/src/modules/paypal/service.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/service.js +1149 -0
- package/.medusa/server/src/modules/paypal/service.js.map +1 -0
- package/.medusa/server/src/modules/paypal/types/config.d.ts +12 -0
- package/.medusa/server/src/modules/paypal/types/config.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/types/config.js +24 -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/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/paypal-auth.d.ts +10 -0
- package/.medusa/server/src/modules/paypal/utils/paypal-auth.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/utils/paypal-auth.js +30 -0
- package/.medusa/server/src/modules/paypal/utils/paypal-auth.js.map +1 -0
- package/.medusa/server/src/modules/paypal/utils/provider-ids.d.ts +5 -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 +16 -0
- package/.medusa/server/src/modules/paypal/utils/provider-ids.js.map +1 -0
- package/.medusa/server/src/modules/paypal/webhook-processor.d.ts +29 -0
- package/.medusa/server/src/modules/paypal/webhook-processor.d.ts.map +1 -0
- package/.medusa/server/src/modules/paypal/webhook-processor.js +267 -0
- package/.medusa/server/src/modules/paypal/webhook-processor.js.map +1 -0
- package/.medusa/server/src/providers/paypal/index.d.ts +5 -0
- package/.medusa/server/src/providers/paypal/index.d.ts.map +1 -0
- package/.medusa/server/src/providers/paypal/index.js +10 -0
- package/.medusa/server/src/providers/paypal/index.js.map +1 -0
- package/.medusa/server/src/providers/paypal_card/index.d.ts +5 -0
- package/.medusa/server/src/providers/paypal_card/index.d.ts.map +1 -0
- package/.medusa/server/src/providers/paypal_card/index.js +10 -0
- package/.medusa/server/src/providers/paypal_card/index.js.map +1 -0
- package/.medusa/server/src/subscribers/paypal-order-invoice.d.ts +6 -0
- package/.medusa/server/src/subscribers/paypal-order-invoice.d.ts.map +1 -0
- package/.medusa/server/src/subscribers/paypal-order-invoice.js +124 -0
- package/.medusa/server/src/subscribers/paypal-order-invoice.js.map +1 -0
- package/LICENSE +21 -0
- package/README.md +172 -0
- package/package.json +80 -0
- package/src/admin/index.ts +7 -0
- package/src/admin/routes/settings/paypal/_components/Tabs.tsx +48 -0
- package/src/admin/routes/settings/paypal/_components/Toast.tsx +51 -0
- package/src/admin/routes/settings/paypal/additional-settings/page.tsx +199 -0
- package/src/admin/routes/settings/paypal/advanced-card-payments/page.tsx +183 -0
- package/src/admin/routes/settings/paypal/apple-pay/page.tsx +5 -0
- package/src/admin/routes/settings/paypal/connection/page.tsx +699 -0
- package/src/admin/routes/settings/paypal/google-pay/page.tsx +5 -0
- package/src/admin/routes/settings/paypal/page.tsx +13 -0
- package/src/admin/routes/settings/paypal/pay-later-messaging/page.tsx +5 -0
- package/src/admin/routes/settings/paypal/paypal-settings/page.tsx +351 -0
- package/src/api/admin/payment-collections/[id]/payment-sessions/route.ts +24 -0
- package/src/api/admin/paypal/disconnect/route.ts +8 -0
- package/src/api/admin/paypal/environment/route.ts +25 -0
- package/src/api/admin/paypal/onboard-complete/route.ts +34 -0
- package/src/api/admin/paypal/onboarding-link/route.ts +45 -0
- package/src/api/admin/paypal/onboarding-status/route.ts +17 -0
- package/src/api/admin/paypal/save-credentials/route.ts +22 -0
- package/src/api/admin/paypal/settings/route.ts +14 -0
- package/src/api/admin/paypal/status/route.ts +12 -0
- package/src/api/middlewares.ts +14 -0
- package/src/api/store/payment-collections/[id]/payment-sessions/route.ts +56 -0
- package/src/api/store/paypal/capture-order/route.ts +265 -0
- package/src/api/store/paypal/config/route.ts +100 -0
- package/src/api/store/paypal/create-order/route.ts +449 -0
- package/src/api/store/paypal/settings/route.ts +19 -0
- package/src/api/store/paypal/webhook/route.ts +325 -0
- package/src/api/store/paypal-complete/route.ts +95 -0
- package/src/index.ts +1 -0
- package/src/jobs/paypal-webhook-retry.ts +149 -0
- package/src/modules/paypal/clients/paypal-seller.client.ts +62 -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/20260401090000_create_paypal_metric.ts +27 -0
- package/src/modules/paypal/migrations/20260701090000_add_paypal_webhook_event_processing.ts +31 -0
- package/src/modules/paypal/migrations/20261101090000_remove_paypal_reconciliation_status.ts +25 -0
- package/src/modules/paypal/migrations/20261201090000_remove_paypal_audit_log.ts +26 -0
- package/src/modules/paypal/migrations/20270101090000_set_paypal_environment_default_live.ts +11 -0
- package/src/modules/paypal/migrations/20270102090000_add_paypal_seller_email.ts +11 -0
- package/src/modules/paypal/migrations/20270103090000_add_paypal_seller_merchant_id.ts +11 -0
- package/src/modules/paypal/migrations/20270201000000_add_webhook_dead_letter.ts +17 -0
- package/src/modules/paypal/models/paypal_connection.ts +23 -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 +767 -0
- package/src/modules/paypal/payment-provider/index.ts +15 -0
- package/src/modules/paypal/payment-provider/service.ts +1039 -0
- package/src/modules/paypal/payment-provider/webhook-utils.ts +88 -0
- package/src/modules/paypal/service.ts +1416 -0
- package/src/modules/paypal/types/config.ts +33 -0
- package/src/modules/paypal/utils/amounts.ts +41 -0
- package/src/modules/paypal/utils/currencies.ts +84 -0
- package/src/modules/paypal/utils/paypal-auth.ts +31 -0
- package/src/modules/paypal/utils/provider-ids.ts +15 -0
- package/src/modules/paypal/webhook-processor.ts +363 -0
- package/src/providers/paypal/index.ts +8 -0
- package/src/providers/paypal_card/index.ts +8 -0
- package/src/subscribers/paypal-order-invoice.ts +171 -0
- package/tsconfig.json +31 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
|
2
|
+
import type PayPalModuleService from "../../../../modules/paypal/service"
|
|
3
|
+
import {
|
|
4
|
+
computeNextRetryAt,
|
|
5
|
+
isAllowedEventType,
|
|
6
|
+
isRetryableError,
|
|
7
|
+
normalizeEventVersion,
|
|
8
|
+
processPayPalWebhookEvent,
|
|
9
|
+
} from "../../../../modules/paypal/webhook-processor"
|
|
10
|
+
|
|
11
|
+
const REPLAY_WINDOW_MINUTES = (() => {
|
|
12
|
+
const v = Number(process.env.PAYPAL_WEBHOOK_REPLAY_WINDOW_MINUTES)
|
|
13
|
+
return Number.isFinite(v) && v > 0 ? v : 60
|
|
14
|
+
})()
|
|
15
|
+
|
|
16
|
+
function getHeader(
|
|
17
|
+
headers: Record<string, string | string[] | undefined>,
|
|
18
|
+
name: string
|
|
19
|
+
): string | undefined {
|
|
20
|
+
const direct = headers[name]
|
|
21
|
+
if (Array.isArray(direct)) return direct[0]
|
|
22
|
+
if (typeof direct === "string") return direct
|
|
23
|
+
const lower = name.toLowerCase()
|
|
24
|
+
const key = Object.keys(headers).find((h) => h.toLowerCase() === lower)
|
|
25
|
+
if (!key) return undefined
|
|
26
|
+
const val = headers[key]
|
|
27
|
+
return Array.isArray(val) ? val[0] : val
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface ValidationFail {
|
|
31
|
+
ok: false
|
|
32
|
+
status: number
|
|
33
|
+
message: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface ValidationPass {
|
|
37
|
+
ok: true
|
|
38
|
+
eventId: string
|
|
39
|
+
eventType: string
|
|
40
|
+
transmissionId: string | null
|
|
41
|
+
transmissionTime: Date | null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function validateRequest(req: MedusaRequest): ValidationFail | ValidationPass {
|
|
45
|
+
const payload = (req.body || {}) as Record<string, any>
|
|
46
|
+
const eventId = String(payload?.id || payload?.event_id || "").trim()
|
|
47
|
+
const eventType = String(payload?.event_type || payload?.eventType || "").trim()
|
|
48
|
+
|
|
49
|
+
if (!eventId || !eventType) {
|
|
50
|
+
return { ok: false, status: 400, message: "Missing required fields: id and event_type" }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const transmissionTimeHeader = getHeader(req.headers, "paypal-transmission-time")
|
|
54
|
+
if (!transmissionTimeHeader) {
|
|
55
|
+
return {
|
|
56
|
+
ok: false,
|
|
57
|
+
status: 400,
|
|
58
|
+
message: "Missing required header: paypal-transmission-time",
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const transmissionMs = Date.parse(transmissionTimeHeader)
|
|
63
|
+
if (!Number.isFinite(transmissionMs)) {
|
|
64
|
+
return {
|
|
65
|
+
ok: false,
|
|
66
|
+
status: 400,
|
|
67
|
+
message: "Invalid paypal-transmission-time header value",
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const ageMs = Math.abs(Date.now() - transmissionMs)
|
|
72
|
+
if (ageMs > REPLAY_WINDOW_MINUTES * 60 * 1000) {
|
|
73
|
+
return {
|
|
74
|
+
ok: false,
|
|
75
|
+
status: 400,
|
|
76
|
+
message: `Webhook rejected: outside ${REPLAY_WINDOW_MINUTES}-minute replay window`,
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
ok: true,
|
|
82
|
+
eventId,
|
|
83
|
+
eventType,
|
|
84
|
+
transmissionId: getHeader(req.headers, "paypal-transmission-id") || null,
|
|
85
|
+
transmissionTime: new Date(transmissionMs),
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function resolveWebhookId(
|
|
90
|
+
environment: string,
|
|
91
|
+
settings: Record<string, unknown>
|
|
92
|
+
): string | undefined {
|
|
93
|
+
const ids = (settings?.webhook_ids || {}) as Record<string, string | undefined>
|
|
94
|
+
if (environment === "live") {
|
|
95
|
+
return (
|
|
96
|
+
ids.live ||
|
|
97
|
+
(settings?.webhook_id_live as string) ||
|
|
98
|
+
process.env.PAYPAL_WEBHOOK_ID_LIVE
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
return (
|
|
102
|
+
ids.sandbox ||
|
|
103
|
+
(settings?.webhook_id_sandbox as string) ||
|
|
104
|
+
process.env.PAYPAL_WEBHOOK_ID_SANDBOX
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function verifyWebhookSignature(
|
|
109
|
+
paypal: PayPalModuleService,
|
|
110
|
+
environment: string,
|
|
111
|
+
body: Record<string, unknown>,
|
|
112
|
+
headers: Record<string, string | string[] | undefined>
|
|
113
|
+
): Promise<void> {
|
|
114
|
+
const settings = await paypal.getSettings().catch(() => ({ data: {} }))
|
|
115
|
+
const webhookId = resolveWebhookId(
|
|
116
|
+
environment,
|
|
117
|
+
(settings?.data as Record<string, unknown>) || {}
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
if (!webhookId) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
`PayPal webhook ID not configured for environment "${environment}". Set PAYPAL_WEBHOOK_ID_${environment.toUpperCase()} or configure it in admin settings.`
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const base =
|
|
127
|
+
environment === "live"
|
|
128
|
+
? "https://api-m.paypal.com"
|
|
129
|
+
: "https://api-m.sandbox.paypal.com"
|
|
130
|
+
|
|
131
|
+
const accessToken = await paypal.getAppAccessToken()
|
|
132
|
+
|
|
133
|
+
const verifyPayload = {
|
|
134
|
+
auth_algo: getHeader(headers, "paypal-auth-algo"),
|
|
135
|
+
cert_url: getHeader(headers, "paypal-cert-url"),
|
|
136
|
+
transmission_id: getHeader(headers, "paypal-transmission-id"),
|
|
137
|
+
transmission_sig: getHeader(headers, "paypal-transmission-sig"),
|
|
138
|
+
transmission_time: getHeader(headers, "paypal-transmission-time"),
|
|
139
|
+
webhook_id: webhookId,
|
|
140
|
+
webhook_event: body,
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const missing = Object.entries(verifyPayload)
|
|
144
|
+
.filter(([k, v]) => k !== "webhook_id" && k !== "webhook_event" && !v)
|
|
145
|
+
.map(([k]) => k)
|
|
146
|
+
|
|
147
|
+
if (missing.length > 0) {
|
|
148
|
+
throw new Error(`Missing required PayPal webhook headers: ${missing.join(", ")}`)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const resp = await fetch(`${base}/v1/notifications/verify-webhook-signature`, {
|
|
152
|
+
method: "POST",
|
|
153
|
+
headers: {
|
|
154
|
+
Authorization: `Bearer ${accessToken}`,
|
|
155
|
+
"Content-Type": "application/json",
|
|
156
|
+
},
|
|
157
|
+
body: JSON.stringify(verifyPayload),
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
const json = await resp.json().catch(() => ({}))
|
|
161
|
+
const debugId = resp.headers.get("paypal-debug-id") || json?.debug_id
|
|
162
|
+
|
|
163
|
+
if (!resp.ok) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`PayPal signature verification API error (${resp.status}): ${JSON.stringify(json)}` +
|
|
166
|
+
(debugId ? ` debug_id=${debugId}` : "")
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
if (json?.verification_status !== "VERIFIED") {
|
|
170
|
+
throw new Error(
|
|
171
|
+
`PayPal webhook signature not verified. Status: ${json?.verification_status}` +
|
|
172
|
+
(debugId ? ` debug_id=${debugId}` : "")
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export async function POST(req: MedusaRequest, res: MedusaResponse) {
|
|
178
|
+
const paypal = req.scope.resolve<PayPalModuleService>("paypal_onboarding")
|
|
179
|
+
|
|
180
|
+
const validation = validateRequest(req)
|
|
181
|
+
if (!validation.ok) {
|
|
182
|
+
console.warn("[PayPal] webhook: validation failed:", validation.message)
|
|
183
|
+
return res.status(validation.status).json({ message: validation.message })
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const { eventId, eventType, transmissionId, transmissionTime } = validation
|
|
187
|
+
const payload = (req.body || {}) as Record<string, any>
|
|
188
|
+
|
|
189
|
+
if (transmissionId) {
|
|
190
|
+
try {
|
|
191
|
+
const existing = await paypal.listPayPalWebhookEvents({ transmission_id: transmissionId })
|
|
192
|
+
if ((existing || []).length > 0) {
|
|
193
|
+
console.info("[PayPal] webhook: duplicate transmission_id", {
|
|
194
|
+
transmissionId,
|
|
195
|
+
eventId,
|
|
196
|
+
})
|
|
197
|
+
return res.json({ ok: true, duplicate: true })
|
|
198
|
+
}
|
|
199
|
+
} catch (e: any) {
|
|
200
|
+
console.warn("[PayPal] webhook: transmission_id dedup check failed:", e?.message)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
const creds = await paypal.getActiveCredentials()
|
|
206
|
+
await verifyWebhookSignature(paypal, creds.environment, payload, req.headers)
|
|
207
|
+
} catch (e: any) {
|
|
208
|
+
console.error("[PayPal] webhook: signature verification failed:", e?.message)
|
|
209
|
+
return res
|
|
210
|
+
.status(401)
|
|
211
|
+
.json({ message: e?.message || "Webhook signature verification failed" })
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const eventVersion = normalizeEventVersion(payload)
|
|
215
|
+
let recordId: string | null = null
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
const recordResult = await paypal.createWebhookEventRecord({
|
|
219
|
+
event_id: eventId,
|
|
220
|
+
event_type: eventType,
|
|
221
|
+
payload,
|
|
222
|
+
event_version: eventVersion,
|
|
223
|
+
transmission_id: transmissionId,
|
|
224
|
+
transmission_time: transmissionTime,
|
|
225
|
+
status: "processing",
|
|
226
|
+
attempt_count: 1,
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
if (!recordResult.created) {
|
|
230
|
+
console.info("[PayPal] webhook: duplicate event_id", { eventId, eventType })
|
|
231
|
+
return res.json({ ok: true, duplicate: true })
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
recordId = recordResult.event?.id ?? null
|
|
235
|
+
} catch (e: any) {
|
|
236
|
+
console.error("[PayPal] webhook: failed to create DB record:", e?.message)
|
|
237
|
+
return res.status(500).json({ message: "Failed to record webhook event" })
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (!isAllowedEventType(eventType)) {
|
|
241
|
+
console.info("[PayPal] webhook: unsupported event type, ignoring", { eventType })
|
|
242
|
+
await paypal.recordAuditEvent("webhook_unsupported_event", {
|
|
243
|
+
event_id: eventId,
|
|
244
|
+
event_type: eventType,
|
|
245
|
+
})
|
|
246
|
+
if (recordId) {
|
|
247
|
+
await paypal
|
|
248
|
+
.updateWebhookEventRecord({
|
|
249
|
+
id: recordId,
|
|
250
|
+
status: "ignored",
|
|
251
|
+
processed_at: new Date(),
|
|
252
|
+
})
|
|
253
|
+
.catch(() => {})
|
|
254
|
+
}
|
|
255
|
+
return res.json({ ok: true, ignored: true })
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
const processed = await processPayPalWebhookEvent(req.scope, { eventType, payload })
|
|
260
|
+
|
|
261
|
+
if (recordId) {
|
|
262
|
+
await paypal
|
|
263
|
+
.updateWebhookEventRecord({
|
|
264
|
+
id: recordId,
|
|
265
|
+
status: "processed",
|
|
266
|
+
processed_at: new Date(),
|
|
267
|
+
resource_id:
|
|
268
|
+
processed.refundId || processed.captureId || processed.orderId || null,
|
|
269
|
+
})
|
|
270
|
+
.catch(() => {})
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
console.info("[PayPal] webhook: processed", {
|
|
274
|
+
event_id: eventId,
|
|
275
|
+
event_type: eventType,
|
|
276
|
+
order_id: processed.orderId,
|
|
277
|
+
capture_id: processed.captureId,
|
|
278
|
+
refund_id: processed.refundId,
|
|
279
|
+
cart_id: processed.cartId,
|
|
280
|
+
session_updated: processed.sessionUpdated,
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
await paypal.recordMetric("webhook_success").catch(() => {})
|
|
284
|
+
return res.json({ ok: true })
|
|
285
|
+
} catch (e: any) {
|
|
286
|
+
console.error("[PayPal] webhook: processing failed", {
|
|
287
|
+
event_id: eventId,
|
|
288
|
+
event_type: eventType,
|
|
289
|
+
error: e?.message,
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
const retryable = isRetryableError(e)
|
|
293
|
+
const nextStatus = retryable ? "failed" : "dead_letter"
|
|
294
|
+
|
|
295
|
+
if (recordId) {
|
|
296
|
+
await paypal
|
|
297
|
+
.updateWebhookEventRecord({
|
|
298
|
+
id: recordId,
|
|
299
|
+
status: nextStatus,
|
|
300
|
+
attempt_count: 1,
|
|
301
|
+
next_retry_at: retryable ? computeNextRetryAt(1) : null,
|
|
302
|
+
last_error: e?.message || String(e),
|
|
303
|
+
})
|
|
304
|
+
.catch(() => {})
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
await paypal
|
|
308
|
+
.recordAuditEvent("webhook_processing_failed", {
|
|
309
|
+
event_id: eventId,
|
|
310
|
+
event_type: eventType,
|
|
311
|
+
retryable,
|
|
312
|
+
message: e?.message || String(e),
|
|
313
|
+
})
|
|
314
|
+
.catch(() => {})
|
|
315
|
+
|
|
316
|
+
await paypal.recordMetric("webhook_failed").catch(() => {})
|
|
317
|
+
|
|
318
|
+
if (!retryable) {
|
|
319
|
+
return res.status(200).json({ ok: false, message: e?.message })
|
|
320
|
+
}
|
|
321
|
+
return res
|
|
322
|
+
.status(500)
|
|
323
|
+
.json({ message: e?.message || "PayPal webhook processing error" })
|
|
324
|
+
}
|
|
325
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
|
2
|
+
import type { IPaymentModuleService } from "@medusajs/framework/types"
|
|
3
|
+
import { Modules } from "@medusajs/framework/utils"
|
|
4
|
+
import type PayPalModuleService from "../../../modules/paypal/service"
|
|
5
|
+
|
|
6
|
+
export async function POST(req: MedusaRequest, res: MedusaResponse) {
|
|
7
|
+
const { cart_id } = req.body as { cart_id: string }
|
|
8
|
+
|
|
9
|
+
if (!cart_id) {
|
|
10
|
+
return res.status(400).json({ error: "cart_id is required" })
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const query = req.scope.resolve("query")
|
|
15
|
+
const { data: carts } = await query.graph({
|
|
16
|
+
entity: "cart",
|
|
17
|
+
fields: [
|
|
18
|
+
"id",
|
|
19
|
+
"payment_collection.payment_sessions.id",
|
|
20
|
+
"payment_collection.payment_sessions.data",
|
|
21
|
+
"payment_collection.payment_sessions.status",
|
|
22
|
+
"payment_collection.payment_sessions.provider_id",
|
|
23
|
+
"payment_collection.payment_sessions.created_at",
|
|
24
|
+
"payment_collection.payment_sessions.amount",
|
|
25
|
+
"payment_collection.payment_sessions.currency_code",
|
|
26
|
+
],
|
|
27
|
+
filters: { id: cart_id },
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const sessions = carts?.[0]?.payment_collection?.payment_sessions || []
|
|
31
|
+
const session = sessions
|
|
32
|
+
.filter((s: any) => String(s.provider_id || "").includes("paypal"))
|
|
33
|
+
.sort(
|
|
34
|
+
(a: any, b: any) =>
|
|
35
|
+
new Date(b.created_at || 0).getTime() - new Date(a.created_at || 0).getTime()
|
|
36
|
+
)[0]
|
|
37
|
+
|
|
38
|
+
if (!session) {
|
|
39
|
+
return res.status(400).json({ error: "No PayPal payment session found for cart" })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const currentStatus = String(session.status || "")
|
|
43
|
+
if (currentStatus === "authorized" || currentStatus === "captured") {
|
|
44
|
+
console.info("[paypal-complete] session already in terminal status:", currentStatus)
|
|
45
|
+
return res.json({ success: true, session_id: session.id, status: currentStatus })
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const paymentModule = req.scope.resolve(Modules.PAYMENT) as IPaymentModuleService
|
|
49
|
+
|
|
50
|
+
const [liveSession] = await paymentModule.listPaymentSessions(
|
|
51
|
+
{ id: [session.id] },
|
|
52
|
+
{ take: 1 }
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
const liveData = (liveSession?.data || {}) as Record<string, any>
|
|
56
|
+
|
|
57
|
+
const paypal = req.scope.resolve<PayPalModuleService>("paypal_onboarding")
|
|
58
|
+
const settings = await paypal.getSettings().catch(() => ({}))
|
|
59
|
+
const settingsData =
|
|
60
|
+
settings && typeof settings === "object" && "data" in settings
|
|
61
|
+
? ((settings as { data?: Record<string, any> }).data ?? {})
|
|
62
|
+
: {}
|
|
63
|
+
const additionalSettings = (settingsData.additional_settings || {}) as Record<string, any>
|
|
64
|
+
const paymentAction =
|
|
65
|
+
typeof additionalSettings.paymentAction === "string"
|
|
66
|
+
? additionalSettings.paymentAction
|
|
67
|
+
: "capture"
|
|
68
|
+
|
|
69
|
+
const timestampKey = paymentAction === "authorize" ? "authorized_at" : "captured_at"
|
|
70
|
+
|
|
71
|
+
if (!liveData[timestampKey]) {
|
|
72
|
+
await (paymentModule as any).updatePaymentSession({
|
|
73
|
+
id: session.id,
|
|
74
|
+
data: {
|
|
75
|
+
...liveData,
|
|
76
|
+
[timestampKey]: new Date().toISOString(),
|
|
77
|
+
},
|
|
78
|
+
amount: liveSession?.amount ?? session.amount,
|
|
79
|
+
currency_code: liveSession?.currency_code ?? session.currency_code,
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
await (paymentModule as any).authorizePaymentSession(session.id, {})
|
|
85
|
+
console.info("[paypal-complete] authorizePaymentSession succeeded for session", session.id)
|
|
86
|
+
} catch (e: any) {
|
|
87
|
+
console.warn("[paypal-complete] authorizePaymentSession non-fatal:", e?.message)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return res.json({ success: true, session_id: session.id })
|
|
91
|
+
} catch (e: any) {
|
|
92
|
+
console.error("[paypal-complete] error:", e?.message || e)
|
|
93
|
+
return res.status(500).json({ error: e?.message || "Internal error" })
|
|
94
|
+
}
|
|
95
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./modules/paypal"
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { MedusaContainer } from "@medusajs/framework/types"
|
|
2
|
+
import type PayPalModuleService from "../modules/paypal/service"
|
|
3
|
+
import {
|
|
4
|
+
computeNextRetryAt,
|
|
5
|
+
isAllowedEventType,
|
|
6
|
+
isRetryableError,
|
|
7
|
+
MAX_WEBHOOK_ATTEMPTS,
|
|
8
|
+
processPayPalWebhookEvent,
|
|
9
|
+
} from "../modules/paypal/webhook-processor"
|
|
10
|
+
|
|
11
|
+
export default async function paypalWebhookRetry(container: MedusaContainer) {
|
|
12
|
+
const paypal = container.resolve<PayPalModuleService>("paypal_onboarding")
|
|
13
|
+
const now = Date.now()
|
|
14
|
+
|
|
15
|
+
const candidates = await paypal.listPayPalWebhookEvents({ status: "failed" })
|
|
16
|
+
if (!candidates?.length) return
|
|
17
|
+
|
|
18
|
+
console.info(
|
|
19
|
+
`[PayPal] webhook-retry: evaluating ${candidates.length} failed event(s)`
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
for (const event of candidates) {
|
|
23
|
+
const nextRetryAt = event?.next_retry_at
|
|
24
|
+
? new Date(event.next_retry_at).getTime()
|
|
25
|
+
: null
|
|
26
|
+
if (!nextRetryAt || nextRetryAt > now) continue
|
|
27
|
+
|
|
28
|
+
const attemptCount = Number(event.attempt_count || 0)
|
|
29
|
+
|
|
30
|
+
if (attemptCount >= MAX_WEBHOOK_ATTEMPTS) {
|
|
31
|
+
await paypal
|
|
32
|
+
.updateWebhookEventRecord({
|
|
33
|
+
id: event.id,
|
|
34
|
+
status: "dead_letter",
|
|
35
|
+
next_retry_at: null,
|
|
36
|
+
last_error: `Exceeded max attempts (${MAX_WEBHOOK_ATTEMPTS})`,
|
|
37
|
+
})
|
|
38
|
+
.catch(() => {})
|
|
39
|
+
console.warn("[PayPal] webhook-retry: dead-lettered (max attempts)", {
|
|
40
|
+
id: event.id,
|
|
41
|
+
event_type: event.event_type,
|
|
42
|
+
attempts: attemptCount,
|
|
43
|
+
})
|
|
44
|
+
await paypal.recordMetric("webhook_dead_letter").catch(() => {})
|
|
45
|
+
continue
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
await paypal
|
|
49
|
+
.updateWebhookEventRecord({
|
|
50
|
+
id: event.id,
|
|
51
|
+
status: "processing",
|
|
52
|
+
attempt_count: attemptCount + 1,
|
|
53
|
+
next_retry_at: null,
|
|
54
|
+
last_error: null,
|
|
55
|
+
})
|
|
56
|
+
.catch(() => {})
|
|
57
|
+
|
|
58
|
+
const eventType = String(event.event_type || "")
|
|
59
|
+
|
|
60
|
+
if (!isAllowedEventType(eventType)) {
|
|
61
|
+
await paypal
|
|
62
|
+
.updateWebhookEventRecord({
|
|
63
|
+
id: event.id,
|
|
64
|
+
status: "ignored",
|
|
65
|
+
processed_at: new Date(),
|
|
66
|
+
})
|
|
67
|
+
.catch(() => {})
|
|
68
|
+
console.info("[PayPal] webhook-retry: ignored unsupported event type", {
|
|
69
|
+
id: event.id,
|
|
70
|
+
event_type: eventType,
|
|
71
|
+
})
|
|
72
|
+
continue
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const payload = (event.payload || {}) as Record<string, any>
|
|
77
|
+
const processed = await processPayPalWebhookEvent(container, { eventType, payload })
|
|
78
|
+
|
|
79
|
+
await paypal
|
|
80
|
+
.updateWebhookEventRecord({
|
|
81
|
+
id: event.id,
|
|
82
|
+
status: "processed",
|
|
83
|
+
processed_at: new Date(),
|
|
84
|
+
resource_id:
|
|
85
|
+
processed.refundId || processed.captureId || processed.orderId || null,
|
|
86
|
+
})
|
|
87
|
+
.catch(() => {})
|
|
88
|
+
|
|
89
|
+
console.info("[PayPal] webhook-retry: processed successfully", {
|
|
90
|
+
id: event.id,
|
|
91
|
+
event_type: eventType,
|
|
92
|
+
attempt: attemptCount + 1,
|
|
93
|
+
order_id: processed.orderId,
|
|
94
|
+
capture_id: processed.captureId,
|
|
95
|
+
cart_id: processed.cartId,
|
|
96
|
+
session_updated: processed.sessionUpdated,
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
await paypal.recordMetric("webhook_retry_success").catch(() => {})
|
|
100
|
+
} catch (error: any) {
|
|
101
|
+
const retryable = isRetryableError(error)
|
|
102
|
+
const nextAttempt = attemptCount + 1
|
|
103
|
+
|
|
104
|
+
if (!retryable || nextAttempt >= MAX_WEBHOOK_ATTEMPTS) {
|
|
105
|
+
await paypal
|
|
106
|
+
.updateWebhookEventRecord({
|
|
107
|
+
id: event.id,
|
|
108
|
+
status: "dead_letter",
|
|
109
|
+
attempt_count: nextAttempt,
|
|
110
|
+
next_retry_at: null,
|
|
111
|
+
last_error: error?.message || String(error),
|
|
112
|
+
})
|
|
113
|
+
.catch(() => {})
|
|
114
|
+
console.error("[PayPal] webhook-retry: dead-lettered after error", {
|
|
115
|
+
id: event.id,
|
|
116
|
+
event_type: eventType,
|
|
117
|
+
attempt: nextAttempt,
|
|
118
|
+
retryable,
|
|
119
|
+
error: error?.message,
|
|
120
|
+
})
|
|
121
|
+
await paypal.recordMetric("webhook_dead_letter").catch(() => {})
|
|
122
|
+
} else {
|
|
123
|
+
const nextRetry = computeNextRetryAt(nextAttempt)
|
|
124
|
+
await paypal
|
|
125
|
+
.updateWebhookEventRecord({
|
|
126
|
+
id: event.id,
|
|
127
|
+
status: "failed",
|
|
128
|
+
attempt_count: nextAttempt,
|
|
129
|
+
next_retry_at: nextRetry,
|
|
130
|
+
last_error: error?.message || String(error),
|
|
131
|
+
})
|
|
132
|
+
.catch(() => {})
|
|
133
|
+
console.warn("[PayPal] webhook-retry: scheduled retry", {
|
|
134
|
+
id: event.id,
|
|
135
|
+
event_type: eventType,
|
|
136
|
+
attempt: nextAttempt,
|
|
137
|
+
next_retry_at: nextRetry?.toISOString(),
|
|
138
|
+
error: error?.message,
|
|
139
|
+
})
|
|
140
|
+
await paypal.recordMetric("webhook_retry_failed").catch(() => {})
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export const config = {
|
|
147
|
+
name: "paypal-webhook-retry",
|
|
148
|
+
schedule: "*/10 * * * *",
|
|
149
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export class PayPalSellerClient {
|
|
2
|
+
private static readonly BN_CODE = "MBJTechnolabs_SI_SPB"
|
|
3
|
+
|
|
4
|
+
constructor(private opts: { environment: "sandbox" | "live"; accessToken: string }) {}
|
|
5
|
+
|
|
6
|
+
private baseUrl() {
|
|
7
|
+
return this.opts.environment === "live"
|
|
8
|
+
? "https://api-m.paypal.com"
|
|
9
|
+
: "https://api-m.sandbox.paypal.com"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
private headers(extra?: Record<string, string>) {
|
|
13
|
+
return {
|
|
14
|
+
"Content-Type": "application/json",
|
|
15
|
+
Authorization: `Bearer ${this.opts.accessToken}`,
|
|
16
|
+
"PayPal-Partner-Attribution-Id": PayPalSellerClient.BN_CODE,
|
|
17
|
+
...(extra ?? {}),
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async createOrder(body: any) {
|
|
22
|
+
const res = await fetch(`${this.baseUrl()}/v2/checkout/orders`, {
|
|
23
|
+
method: "POST",
|
|
24
|
+
headers: this.headers(),
|
|
25
|
+
body: JSON.stringify(body),
|
|
26
|
+
})
|
|
27
|
+
const json = await res.json().catch(() => ({}))
|
|
28
|
+
if (!res.ok) throw new Error(`PayPal createOrder failed (${res.status}): ${JSON.stringify(json)}`)
|
|
29
|
+
return json
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async getOrder(orderId: string) {
|
|
33
|
+
const res = await fetch(`${this.baseUrl()}/v2/checkout/orders/${orderId}`, {
|
|
34
|
+
method: "GET",
|
|
35
|
+
headers: this.headers(),
|
|
36
|
+
})
|
|
37
|
+
const json = await res.json().catch(() => ({}))
|
|
38
|
+
if (!res.ok) throw new Error(`PayPal getOrder failed (${res.status}): ${JSON.stringify(json)}`)
|
|
39
|
+
return json
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async captureOrder(orderId: string) {
|
|
43
|
+
const res = await fetch(`${this.baseUrl()}/v2/checkout/orders/${orderId}/capture`, {
|
|
44
|
+
method: "POST",
|
|
45
|
+
headers: this.headers(),
|
|
46
|
+
})
|
|
47
|
+
const json = await res.json().catch(() => ({}))
|
|
48
|
+
if (!res.ok) throw new Error(`PayPal captureOrder failed (${res.status}): ${JSON.stringify(json)}`)
|
|
49
|
+
return json
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async refundCapture(captureId: string, body?: any) {
|
|
53
|
+
const res = await fetch(`${this.baseUrl()}/v2/payments/captures/${captureId}/refund`, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
headers: this.headers(),
|
|
56
|
+
body: body ? JSON.stringify(body) : "{}",
|
|
57
|
+
})
|
|
58
|
+
const json = await res.json().catch(() => ({}))
|
|
59
|
+
if (!res.ok) throw new Error(`PayPal refund failed (${res.status}): ${JSON.stringify(json)}`)
|
|
60
|
+
return json
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -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
|
+
}
|