@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,1422 @@
|
|
|
1
|
+
import { MedusaService } from "@medusajs/framework/utils"
|
|
2
|
+
import PayPalConnection from "./models/paypal_connection"
|
|
3
|
+
import PayPalDispute from "./models/paypal_dispute"
|
|
4
|
+
import PayPalAuditLog from "./models/paypal_audit_log"
|
|
5
|
+
import PayPalMetric from "./models/paypal_metric"
|
|
6
|
+
import PayPalSettings from "./models/paypal_settings"
|
|
7
|
+
import PayPalWebhookEvent from "./models/paypal_webhook_event"
|
|
8
|
+
import { getPayPalConfig } from "./types/config"
|
|
9
|
+
import { decryptSecret, encryptSecret, isEncryptedSecret } from "./utils/crypto"
|
|
10
|
+
import { normalizeCurrencyCode } from "./utils/currencies"
|
|
11
|
+
|
|
12
|
+
type Environment = "sandbox" | "live"
|
|
13
|
+
|
|
14
|
+
type Status =
|
|
15
|
+
| "disconnected"
|
|
16
|
+
| "pending"
|
|
17
|
+
| "pending_credentials"
|
|
18
|
+
| "connected"
|
|
19
|
+
| "revoked"
|
|
20
|
+
|
|
21
|
+
class PayPalModuleService extends MedusaService({
|
|
22
|
+
PayPalAuditLog,
|
|
23
|
+
PayPalConnection,
|
|
24
|
+
PayPalDispute,
|
|
25
|
+
PayPalMetric,
|
|
26
|
+
PayPalSettings,
|
|
27
|
+
PayPalWebhookEvent,
|
|
28
|
+
}) {
|
|
29
|
+
protected cfg = getPayPalConfig()
|
|
30
|
+
|
|
31
|
+
private async getSettingsData() {
|
|
32
|
+
const settings = await this.getSettings()
|
|
33
|
+
return (settings?.data || {}) as Record<string, any>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private async ensureSettingsDefaults() {
|
|
37
|
+
const data = await this.getSettingsData()
|
|
38
|
+
const onboarding = { ...(data.onboarding_config || {}) } as Record<string, any>
|
|
39
|
+
const apiDetails = { ...(data.api_details || {}) } as Record<string, any>
|
|
40
|
+
let changed = false
|
|
41
|
+
|
|
42
|
+
if (!onboarding.partner_service_url) {
|
|
43
|
+
onboarding.partner_service_url = this.cfg.partnerServiceUrl
|
|
44
|
+
changed = true
|
|
45
|
+
}
|
|
46
|
+
if (!onboarding.partner_js_url) {
|
|
47
|
+
onboarding.partner_js_url = this.cfg.partnerJsUrl
|
|
48
|
+
changed = true
|
|
49
|
+
}
|
|
50
|
+
if (!onboarding.backend_url) {
|
|
51
|
+
onboarding.backend_url = this.cfg.backendUrl
|
|
52
|
+
changed = true
|
|
53
|
+
}
|
|
54
|
+
if (!onboarding.seller_nonce) {
|
|
55
|
+
onboarding.seller_nonce = this.cfg.sellerNonce
|
|
56
|
+
changed = true
|
|
57
|
+
}
|
|
58
|
+
if (!onboarding.bn_code && this.cfg.bnCode) {
|
|
59
|
+
onboarding.bn_code = this.cfg.bnCode
|
|
60
|
+
changed = true
|
|
61
|
+
}
|
|
62
|
+
if (!onboarding.partner_merchant_id_sandbox) {
|
|
63
|
+
onboarding.partner_merchant_id_sandbox = this.cfg.partnerMerchantIdSandbox
|
|
64
|
+
changed = true
|
|
65
|
+
}
|
|
66
|
+
if (!onboarding.partner_merchant_id_live) {
|
|
67
|
+
onboarding.partner_merchant_id_live = this.cfg.partnerMerchantIdLive
|
|
68
|
+
changed = true
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!apiDetails.currency_code) {
|
|
72
|
+
const raw = (process.env.PAYPAL_CURRENCY || "").trim()
|
|
73
|
+
apiDetails.currency_code = raw ? normalizeCurrencyCode(raw) : "USD"
|
|
74
|
+
changed = true
|
|
75
|
+
}
|
|
76
|
+
if (!apiDetails.storefront_url) {
|
|
77
|
+
const storeUrl = process.env.STOREFRONT_URL || process.env.STORE_URL
|
|
78
|
+
if (storeUrl) {
|
|
79
|
+
apiDetails.storefront_url = storeUrl
|
|
80
|
+
changed = true
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (changed) {
|
|
85
|
+
await this.saveSettings({
|
|
86
|
+
onboarding_config: onboarding,
|
|
87
|
+
api_details: apiDetails,
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { onboarding, apiDetails }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async getApiDetails() {
|
|
95
|
+
const { onboarding, apiDetails } = await this.ensureSettingsDefaults()
|
|
96
|
+
return {
|
|
97
|
+
onboarding,
|
|
98
|
+
apiDetails,
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async getLoggingPreference() {
|
|
103
|
+
const data = await this.getSettingsData()
|
|
104
|
+
const additional = (data.additional_settings || {}) as Record<string, any>
|
|
105
|
+
if (typeof additional.enableLogging === "boolean") {
|
|
106
|
+
return additional.enableLogging
|
|
107
|
+
}
|
|
108
|
+
return true
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private getAlertWebhookUrls() {
|
|
112
|
+
return (this.cfg.alertWebhookUrls || []).map((url) => url.trim()).filter(Boolean)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private getEncryptionKey() {
|
|
116
|
+
return (this.cfg.credentialsEncryptionKey || "").trim()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private getDecryptionKeys() {
|
|
120
|
+
const current = this.getEncryptionKey()
|
|
121
|
+
const previous = this.cfg.credentialsEncryptionKeyPrevious || []
|
|
122
|
+
const keys = [current, ...previous].map((key) => (key || "").trim()).filter(Boolean)
|
|
123
|
+
return Array.from(new Set(keys))
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private decryptSecretWithKeys(secret: string, keys: string[]) {
|
|
127
|
+
let lastError: unknown
|
|
128
|
+
for (const key of keys) {
|
|
129
|
+
try {
|
|
130
|
+
return decryptSecret(secret, key)
|
|
131
|
+
} catch (err) {
|
|
132
|
+
lastError = err
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (lastError) {
|
|
136
|
+
throw lastError
|
|
137
|
+
}
|
|
138
|
+
return secret
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private maybeEncryptSecret(secret: string) {
|
|
142
|
+
const key = this.getEncryptionKey()
|
|
143
|
+
if (!key) {
|
|
144
|
+
return secret
|
|
145
|
+
}
|
|
146
|
+
return encryptSecret(secret, key)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private maybeDecryptSecret(secret?: string | null) {
|
|
150
|
+
if (!secret) {
|
|
151
|
+
return ""
|
|
152
|
+
}
|
|
153
|
+
const keys = this.getDecryptionKeys()
|
|
154
|
+
if (keys.length === 0) {
|
|
155
|
+
if (isEncryptedSecret(secret)) {
|
|
156
|
+
throw new Error(
|
|
157
|
+
"PayPal client secret is encrypted. Set PAYPAL_CREDENTIALS_ENCRYPTION_KEY to decrypt."
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
return secret
|
|
161
|
+
}
|
|
162
|
+
if (!isEncryptedSecret(secret)) {
|
|
163
|
+
return secret
|
|
164
|
+
}
|
|
165
|
+
return this.decryptSecretWithKeys(secret, keys)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private async getPartnerMerchantId(env: Environment) {
|
|
169
|
+
const { onboarding } = await this.ensureSettingsDefaults()
|
|
170
|
+
return env === "live" ? onboarding.partner_merchant_id_live : onboarding.partner_merchant_id_sandbox
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* We keep a single row in DB and store the currently selected environment there.
|
|
175
|
+
* If no row exists yet, default to sandbox.
|
|
176
|
+
*/
|
|
177
|
+
private async getCurrentRow(): Promise<any | null> {
|
|
178
|
+
const rows = await this.listPayPalConnections({})
|
|
179
|
+
return rows?.[0] ?? null
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private async getCurrentEnvironment(): Promise<Environment> {
|
|
183
|
+
try {
|
|
184
|
+
const row = await this.getCurrentRow()
|
|
185
|
+
const env = (row?.environment as Environment) || "sandbox"
|
|
186
|
+
return env === "live" ? "live" : "sandbox"
|
|
187
|
+
} catch {
|
|
188
|
+
return "sandbox"
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private getEnvCreds(
|
|
193
|
+
row: any,
|
|
194
|
+
env: Environment
|
|
195
|
+
): { clientId?: string; clientSecret?: string; merchantId?: string } {
|
|
196
|
+
const meta = (row?.metadata || {}) as any
|
|
197
|
+
const creds = meta?.credentials?.[env] || {}
|
|
198
|
+
return {
|
|
199
|
+
clientId: creds.client_id || creds.clientId || undefined,
|
|
200
|
+
clientSecret: creds.client_secret || creds.clientSecret || undefined,
|
|
201
|
+
merchantId: creds.merchant_id || creds.merchantId || undefined,
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private getMerchantId(row: any, env: Environment) {
|
|
206
|
+
const meta = (row?.metadata || {}) as any
|
|
207
|
+
const creds = this.getEnvCreds(row, env)
|
|
208
|
+
return creds.merchantId || meta?.merchant_id || meta?.merchantId || undefined
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private async fetchMerchantIntegrationDetails(env: Environment, merchantId: string) {
|
|
212
|
+
const partnerMerchantId = await this.getPartnerMerchantId(env)
|
|
213
|
+
if (!partnerMerchantId) {
|
|
214
|
+
throw new Error("Missing PayPal partner merchant id configuration.")
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const { onboarding } = await this.ensureSettingsDefaults()
|
|
218
|
+
const baseUrl = env === "live" ? "https://api-m.paypal.com" : "https://api-m.sandbox.paypal.com"
|
|
219
|
+
const accessToken = await this.getAppAccessToken()
|
|
220
|
+
|
|
221
|
+
const resp = await fetch(
|
|
222
|
+
`${baseUrl}/v1/customer/partners/${encodeURIComponent(
|
|
223
|
+
partnerMerchantId
|
|
224
|
+
)}/merchant-integrations/${encodeURIComponent(merchantId)}`,
|
|
225
|
+
{
|
|
226
|
+
method: "GET",
|
|
227
|
+
headers: {
|
|
228
|
+
"Content-Type": "application/json",
|
|
229
|
+
Authorization: `Bearer ${accessToken}`,
|
|
230
|
+
...(onboarding.bn_code ? { "PayPal-Partner-Attribution-Id": onboarding.bn_code } : {}),
|
|
231
|
+
},
|
|
232
|
+
}
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
const text = await resp.text().catch(() => "")
|
|
236
|
+
let json: any = {}
|
|
237
|
+
try {
|
|
238
|
+
json = text ? JSON.parse(text) : {}
|
|
239
|
+
} catch {}
|
|
240
|
+
|
|
241
|
+
if (!resp.ok) {
|
|
242
|
+
throw new Error(
|
|
243
|
+
`PayPal merchant integration lookup failed (${resp.status}): ${text || JSON.stringify(json)}`
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return json
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private async syncRowFieldsFromMetadata(row: any, env: Environment) {
|
|
251
|
+
const c = this.getEnvCreds(row, env)
|
|
252
|
+
await this.updatePayPalConnections({
|
|
253
|
+
id: row.id,
|
|
254
|
+
status: c.clientId && c.clientSecret ? "connected" : "disconnected",
|
|
255
|
+
seller_client_id: c.clientId || null,
|
|
256
|
+
seller_client_secret: c.clientSecret || null,
|
|
257
|
+
metadata: {
|
|
258
|
+
...(row.metadata || {}),
|
|
259
|
+
active_environment: env,
|
|
260
|
+
},
|
|
261
|
+
})
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Set environment based on admin UI selection (WooCommerce-style).
|
|
266
|
+
* Switching environment clears stored credentials and requires re-onboarding.
|
|
267
|
+
*/
|
|
268
|
+
|
|
269
|
+
async setEnvironment(env: Environment) {
|
|
270
|
+
const nextEnv: Environment = env === "live" ? "live" : "sandbox"
|
|
271
|
+
const row = await this.getCurrentRow()
|
|
272
|
+
const previousEnv = (row?.environment as Environment) || "sandbox"
|
|
273
|
+
|
|
274
|
+
if (!row) {
|
|
275
|
+
// Create a row with no credentials yet for either environment
|
|
276
|
+
const created = await this.createPayPalConnections({
|
|
277
|
+
environment: nextEnv,
|
|
278
|
+
status: "disconnected",
|
|
279
|
+
shared_id: null,
|
|
280
|
+
auth_code: null,
|
|
281
|
+
seller_client_id: null,
|
|
282
|
+
seller_client_secret: null,
|
|
283
|
+
app_access_token: null,
|
|
284
|
+
app_access_token_expires_at: null,
|
|
285
|
+
metadata: { credentials: {}, active_environment: nextEnv },
|
|
286
|
+
})
|
|
287
|
+
await this.recordAuditEvent("environment_switched", {
|
|
288
|
+
previous_environment: previousEnv,
|
|
289
|
+
environment: nextEnv,
|
|
290
|
+
})
|
|
291
|
+
return created
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Just switch the active environment (do NOT wipe other env credentials)
|
|
295
|
+
await this.updatePayPalConnections({
|
|
296
|
+
id: row.id,
|
|
297
|
+
environment: nextEnv,
|
|
298
|
+
app_access_token: null,
|
|
299
|
+
app_access_token_expires_at: null,
|
|
300
|
+
metadata: {
|
|
301
|
+
...(row.metadata || {}),
|
|
302
|
+
active_environment: nextEnv,
|
|
303
|
+
},
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
// Sync top-level fields/status for the active env so existing code keeps working
|
|
307
|
+
const updated = await this.getCurrentRow()
|
|
308
|
+
if (updated) {
|
|
309
|
+
await this.syncRowFieldsFromMetadata(updated, nextEnv)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
await this.recordAuditEvent("environment_switched", {
|
|
313
|
+
previous_environment: previousEnv,
|
|
314
|
+
environment: nextEnv,
|
|
315
|
+
})
|
|
316
|
+
return await this.getCurrentRow()
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* ✅ WooCommerce-style signup link generation (your service returns PayPal partner-referrals JSON)
|
|
321
|
+
*
|
|
322
|
+
* - POST to your PHP service (WPG_ONBOARDING_URL)
|
|
323
|
+
* - Content-Type: application/x-www-form-urlencoded
|
|
324
|
+
* - fields: email, sandbox, return_url, return_url_description, products[], partner_merchant_id
|
|
325
|
+
*
|
|
326
|
+
* Response formats supported:
|
|
327
|
+
* 1) PayPal partner-referrals JSON: { links: [ { rel: "action_url", href: "..." }, ... ] }
|
|
328
|
+
* 2) Custom JSON: { onboarding_url: "..." }
|
|
329
|
+
* 3) Plain URL string
|
|
330
|
+
*/
|
|
331
|
+
async createOnboardingLink(input?: { email?: string; products?: string[] }) {
|
|
332
|
+
const { onboarding } = await this.ensureSettingsDefaults()
|
|
333
|
+
const return_url = `${String(onboarding.backend_url || "").replace(/\/$/, "")}/admin/paypal/onboard-complete`
|
|
334
|
+
const env = await this.getCurrentEnvironment()
|
|
335
|
+
const partner_merchant_id = await this.getPartnerMerchantId(env)
|
|
336
|
+
|
|
337
|
+
// Match WooCommerce behavior: prefer the current admin user email when available.
|
|
338
|
+
// If it's missing, continue without it (some services can infer or ignore the field).
|
|
339
|
+
const email = (input?.email || "").trim()
|
|
340
|
+
|
|
341
|
+
if (!partner_merchant_id) {
|
|
342
|
+
throw new Error("Missing PAYPAL_PARTNER_MERCHANT_ID_* env for current environment")
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// NOTE:
|
|
346
|
+
// We intentionally avoid DB access here because Medusa v2 has a known issue where
|
|
347
|
+
// MedusaService-generated DB methods can throw 'fork' errors when called outside a transaction context.
|
|
348
|
+
// We store onboarding state later when the JS callback returns authCode/sharedId.
|
|
349
|
+
const form = new URLSearchParams()
|
|
350
|
+
if (email) {
|
|
351
|
+
form.set("email", email)
|
|
352
|
+
}
|
|
353
|
+
form.set("sandbox", env === "live" ? "no" : "yes")
|
|
354
|
+
form.set("return_url", return_url)
|
|
355
|
+
form.set("return_url_description", "Return to your shop.")
|
|
356
|
+
form.set("partner_merchant_id", partner_merchant_id)
|
|
357
|
+
|
|
358
|
+
const products = input?.products?.length ? input.products : ["PPCP"]
|
|
359
|
+
|
|
360
|
+
// WooCommerce/wp_remote_request encodes PHP arrays like products[0]=PPCP.
|
|
361
|
+
// To maximize compatibility with your existing PHP bridge, we send BOTH:
|
|
362
|
+
// - products[0], products[1], ...
|
|
363
|
+
// - products[] (common PHP convention)
|
|
364
|
+
products.forEach((p, i) => {
|
|
365
|
+
form.append(`products[${i}]`, p)
|
|
366
|
+
form.append("products[]", p)
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
const res = await fetch(onboarding.partner_service_url, {
|
|
370
|
+
method: "POST",
|
|
371
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
372
|
+
body: form.toString(),
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
const text = await res.text().catch(() => "")
|
|
376
|
+
if (!res.ok) {
|
|
377
|
+
throw new Error(`Onboarding service failed (${res.status}): ${text}`)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const trimmed = text.trim()
|
|
381
|
+
|
|
382
|
+
// plain URL
|
|
383
|
+
if (trimmed.startsWith("http")) {
|
|
384
|
+
return { onboarding_url: trimmed, return_url }
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
let json: any
|
|
388
|
+
try {
|
|
389
|
+
json = JSON.parse(trimmed)
|
|
390
|
+
} catch {
|
|
391
|
+
throw new Error(`Invalid onboarding link response (not JSON / URL): ${trimmed.slice(0, 200)}`)
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* ✅ WooCommerce-style wrapper support
|
|
396
|
+
*
|
|
397
|
+
* Your PHP service (same as WooCommerce) often returns a wrapper like:
|
|
398
|
+
* { result, http_code, headers, body }
|
|
399
|
+
* where `body` is the actual PayPal partner-referrals JSON string.
|
|
400
|
+
*/
|
|
401
|
+
if (json?.body) {
|
|
402
|
+
const inner = typeof json.body === "string" ? json.body.trim() : json.body
|
|
403
|
+
|
|
404
|
+
// body is a plain URL
|
|
405
|
+
if (typeof inner === "string" && inner.startsWith("http")) {
|
|
406
|
+
return { onboarding_url: inner, return_url }
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// body is JSON string/object
|
|
410
|
+
try {
|
|
411
|
+
json = typeof inner === "string" ? JSON.parse(inner) : inner
|
|
412
|
+
} catch {
|
|
413
|
+
throw new Error(
|
|
414
|
+
`Onboarding wrapper JSON 'body' is not valid JSON / URL: ${
|
|
415
|
+
typeof inner === "string" ? inner.slice(0, 200) : "[object]"
|
|
416
|
+
}`
|
|
417
|
+
)
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// ✅ If PayPal returned an error object, surface it clearly.
|
|
422
|
+
// Typical error shape: { name, message, debug_id, details: [...], links: [...] }
|
|
423
|
+
if (json?.name && json?.message && (json?.debug_id || json?.details || json?.links)) {
|
|
424
|
+
const debug = json.debug_id ? ` debug_id=${json.debug_id}` : ""
|
|
425
|
+
const details = Array.isArray(json.details)
|
|
426
|
+
? json.details
|
|
427
|
+
.slice(0, 3)
|
|
428
|
+
.map((d: any) => {
|
|
429
|
+
const issue = d?.issue ? String(d.issue) : ""
|
|
430
|
+
const desc = d?.description ? String(d.description) : ""
|
|
431
|
+
const field = d?.field ? String(d.field) : ""
|
|
432
|
+
return [issue, desc, field].filter(Boolean).join(" | ")
|
|
433
|
+
})
|
|
434
|
+
.filter(Boolean)
|
|
435
|
+
.join("; ")
|
|
436
|
+
: ""
|
|
437
|
+
|
|
438
|
+
throw new Error(`PayPal onboarding error: ${json.name}: ${json.message}.${debug}${details ? ` Details: ${details}` : ""}`)
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// custom json
|
|
442
|
+
if (json?.onboarding_url && String(json.onboarding_url).startsWith("http")) {
|
|
443
|
+
return { onboarding_url: String(json.onboarding_url), return_url }
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// PayPal partner-referrals format: links[] rel=action_url
|
|
447
|
+
const links = Array.isArray(json?.links) ? json.links : null
|
|
448
|
+
if (links) {
|
|
449
|
+
const action = links.find(
|
|
450
|
+
(l: any) => l?.rel === "action_url" || l?.rel === "actionUrl" || l?.rel === "action-url"
|
|
451
|
+
)
|
|
452
|
+
const href = action?.href ? String(action.href) : null
|
|
453
|
+
if (href && href.startsWith("http")) {
|
|
454
|
+
return { onboarding_url: href, return_url }
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
throw new Error(
|
|
459
|
+
`Onboarding JSON missing action_url link. Keys: ${Object.keys(json || {}).join(", ")}`
|
|
460
|
+
)
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
async startOnboarding() {
|
|
464
|
+
const row = await this.getCurrentRow()
|
|
465
|
+
const env = await this.getCurrentEnvironment()
|
|
466
|
+
|
|
467
|
+
if (row) {
|
|
468
|
+
// MedusaService-generated update methods expect an object that includes the entity id
|
|
469
|
+
await this.updatePayPalConnections({ id: row.id, status: "pending" })
|
|
470
|
+
return
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
await this.createPayPalConnections({
|
|
474
|
+
environment: env,
|
|
475
|
+
status: "pending",
|
|
476
|
+
metadata: {},
|
|
477
|
+
})
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async saveOnboardCallback(input: { authCode: string; sharedId: string }) {
|
|
481
|
+
const row = await this.getCurrentRow()
|
|
482
|
+
const env = await this.getCurrentEnvironment()
|
|
483
|
+
|
|
484
|
+
if (!row) {
|
|
485
|
+
return await this.createPayPalConnections({
|
|
486
|
+
environment: env,
|
|
487
|
+
status: "pending_credentials",
|
|
488
|
+
auth_code: input.authCode,
|
|
489
|
+
shared_id: input.sharedId,
|
|
490
|
+
metadata: {},
|
|
491
|
+
})
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return await this.updatePayPalConnections({
|
|
495
|
+
id: row.id,
|
|
496
|
+
status: "pending_credentials",
|
|
497
|
+
auth_code: input.authCode,
|
|
498
|
+
shared_id: input.sharedId,
|
|
499
|
+
})
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Exchange authCode/sharedId for seller API credentials (server-side) and save in DB.
|
|
504
|
+
*
|
|
505
|
+
* This calls an optional exchange service you control:
|
|
506
|
+
* PAYPAL_EXCHANGE_SERVICE_URL or PAYPAL_PARTNER_EXCHANGE_URL
|
|
507
|
+
*
|
|
508
|
+
* Expected JSON response:
|
|
509
|
+
* { clientId: string, clientSecret: string, merchantId?: string }
|
|
510
|
+
*/
|
|
511
|
+
async exchangeAndSaveSellerCredentials(input: {
|
|
512
|
+
authCode: string
|
|
513
|
+
sharedId: string
|
|
514
|
+
env?: "sandbox" | "live"
|
|
515
|
+
}) {
|
|
516
|
+
// 1) Persist callback (sharedId/authCode) first
|
|
517
|
+
await this.saveOnboardCallback({ authCode: input.authCode, sharedId: input.sharedId })
|
|
518
|
+
|
|
519
|
+
// 2) Exchange authCode + sharedId (+ seller nonce) to get SELLER access token
|
|
520
|
+
const env = (input.env || (await this.getCurrentEnvironment())) as Environment
|
|
521
|
+
const baseUrl = env === "live" ? "https://api-m.paypal.com" : "https://api-m.sandbox.paypal.com"
|
|
522
|
+
|
|
523
|
+
// IMPORTANT: code_verifier MUST match the seller_nonce used when creating the onboarding link.
|
|
524
|
+
// In WooCommerce you use a fixed nonce() and it works, so we do the same here (no DB storage).
|
|
525
|
+
const { onboarding } = await this.ensureSettingsDefaults()
|
|
526
|
+
const sellerNonce = (onboarding.seller_nonce || "").trim()
|
|
527
|
+
if (!sellerNonce) {
|
|
528
|
+
throw new Error("PayPal seller nonce is not configured. Set PAYPAL_SELLER_NONCE.")
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const tokenBody = new URLSearchParams()
|
|
532
|
+
tokenBody.set("grant_type", "authorization_code")
|
|
533
|
+
tokenBody.set("code", input.authCode)
|
|
534
|
+
tokenBody.set("code_verifier", sellerNonce)
|
|
535
|
+
|
|
536
|
+
const basic = Buffer.from(`${input.sharedId}:`).toString("base64")
|
|
537
|
+
|
|
538
|
+
const tokenRes = await fetch(`${baseUrl}/v1/oauth2/token`, {
|
|
539
|
+
method: "POST",
|
|
540
|
+
headers: {
|
|
541
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
542
|
+
Authorization: `Basic ${basic}`,
|
|
543
|
+
},
|
|
544
|
+
body: tokenBody,
|
|
545
|
+
})
|
|
546
|
+
|
|
547
|
+
const tokenText = await tokenRes.text().catch(() => "")
|
|
548
|
+
let tokenJson: any = {}
|
|
549
|
+
try {
|
|
550
|
+
tokenJson = tokenText ? JSON.parse(tokenText) : {}
|
|
551
|
+
} catch {}
|
|
552
|
+
|
|
553
|
+
if (!tokenRes.ok) {
|
|
554
|
+
throw new Error(
|
|
555
|
+
`PayPal authorization_code token exchange failed (${tokenRes.status}): ${tokenText || JSON.stringify(tokenJson)}`
|
|
556
|
+
)
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const sellerAccessToken = String(tokenJson.access_token || "")
|
|
560
|
+
if (!sellerAccessToken) {
|
|
561
|
+
throw new Error("PayPal token exchange succeeded but access_token is missing.")
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// 3) Use SELLER access token to fetch seller REST API credentials
|
|
565
|
+
const partnerMerchantId = await this.getPartnerMerchantId(env)
|
|
566
|
+
if (!partnerMerchantId) {
|
|
567
|
+
throw new Error("Missing PayPal partner merchant id configuration.")
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const credRes = await fetch(
|
|
571
|
+
`${baseUrl}/v1/customer/partners/${encodeURIComponent(partnerMerchantId)}/merchant-integrations/credentials/`,
|
|
572
|
+
{
|
|
573
|
+
method: "GET",
|
|
574
|
+
headers: {
|
|
575
|
+
"Content-Type": "application/json",
|
|
576
|
+
Authorization: `Bearer ${sellerAccessToken}`,
|
|
577
|
+
...(onboarding.bn_code ? { "PayPal-Partner-Attribution-Id": onboarding.bn_code } : {}),
|
|
578
|
+
},
|
|
579
|
+
}
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
const credText = await credRes.text().catch(() => "")
|
|
583
|
+
let credJson: any = {}
|
|
584
|
+
try {
|
|
585
|
+
credJson = credText ? JSON.parse(credText) : {}
|
|
586
|
+
} catch {}
|
|
587
|
+
|
|
588
|
+
if (!credRes.ok) {
|
|
589
|
+
throw new Error(
|
|
590
|
+
`PayPal credentials fetch failed (${credRes.status}): ${credText || JSON.stringify(credJson)}`
|
|
591
|
+
)
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const clientId = String(credJson.client_id || "")
|
|
595
|
+
const clientSecret = String(credJson.client_secret || "")
|
|
596
|
+
const payerId = String(credJson.payer_id || "")
|
|
597
|
+
|
|
598
|
+
if (!clientId || !clientSecret) {
|
|
599
|
+
throw new Error(
|
|
600
|
+
`PayPal credentials response missing client_id/client_secret. Keys: ${Object.keys(credJson || {}).join(", ")}`
|
|
601
|
+
)
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// 4) Save seller credentials (marks status = connected)
|
|
605
|
+
await this.saveSellerCredentials({ clientId, clientSecret })
|
|
606
|
+
|
|
607
|
+
// 5) Store payer_id as merchant_id in metadata (optional)
|
|
608
|
+
if (payerId) {
|
|
609
|
+
const row = await this.getCurrentRow()
|
|
610
|
+
if (row) {
|
|
611
|
+
const meta = (row.metadata || {}) as any
|
|
612
|
+
const creds = { ...(meta.credentials || {}) }
|
|
613
|
+
creds[env] = { ...(creds[env] || {}), merchant_id: payerId }
|
|
614
|
+
await this.updatePayPalConnections({
|
|
615
|
+
id: row.id,
|
|
616
|
+
metadata: { ...meta, credentials: creds, merchant_id: payerId },
|
|
617
|
+
})
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
async saveSellerCredentials(input: { clientId: string; clientSecret: string }) {
|
|
624
|
+
const row = await this.getCurrentRow()
|
|
625
|
+
const env = await this.getCurrentEnvironment()
|
|
626
|
+
|
|
627
|
+
const encryptedSecret = this.maybeEncryptSecret(input.clientSecret)
|
|
628
|
+
const nextCreds = {
|
|
629
|
+
client_id: input.clientId,
|
|
630
|
+
client_secret: encryptedSecret,
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
if (!row) {
|
|
634
|
+
const created = await this.createPayPalConnections({
|
|
635
|
+
environment: env,
|
|
636
|
+
status: "connected",
|
|
637
|
+
seller_client_id: input.clientId,
|
|
638
|
+
seller_client_secret: encryptedSecret,
|
|
639
|
+
app_access_token: null,
|
|
640
|
+
app_access_token_expires_at: null,
|
|
641
|
+
metadata: {
|
|
642
|
+
credentials: {
|
|
643
|
+
[env]: nextCreds,
|
|
644
|
+
},
|
|
645
|
+
active_environment: env,
|
|
646
|
+
},
|
|
647
|
+
})
|
|
648
|
+
await this.recordAuditEvent("credentials_saved", {
|
|
649
|
+
environment: env,
|
|
650
|
+
client_id: input.clientId,
|
|
651
|
+
})
|
|
652
|
+
await this.ensureWebhookRegistration()
|
|
653
|
+
return created
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
const meta = (row.metadata || {}) as any
|
|
657
|
+
const creds = { ...(meta.credentials || {}) }
|
|
658
|
+
creds[env] = {
|
|
659
|
+
...(creds[env] || {}),
|
|
660
|
+
...nextCreds,
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
const updated = await this.updatePayPalConnections({
|
|
664
|
+
id: row.id,
|
|
665
|
+
status: "connected",
|
|
666
|
+
seller_client_id: input.clientId,
|
|
667
|
+
seller_client_secret: encryptedSecret,
|
|
668
|
+
app_access_token: null,
|
|
669
|
+
app_access_token_expires_at: null,
|
|
670
|
+
metadata: {
|
|
671
|
+
...(row.metadata || {}),
|
|
672
|
+
credentials: creds,
|
|
673
|
+
active_environment: env,
|
|
674
|
+
},
|
|
675
|
+
})
|
|
676
|
+
await this.recordAuditEvent("credentials_saved", {
|
|
677
|
+
environment: env,
|
|
678
|
+
client_id: input.clientId,
|
|
679
|
+
})
|
|
680
|
+
await this.ensureWebhookRegistration()
|
|
681
|
+
return updated
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
private async resolveWebhookUrl() {
|
|
685
|
+
const { onboarding } = await this.ensureSettingsDefaults()
|
|
686
|
+
const base = String(onboarding.backend_url || "").replace(/\/$/, "")
|
|
687
|
+
if (!base) {
|
|
688
|
+
throw new Error("PayPal backend URL is not configured.")
|
|
689
|
+
}
|
|
690
|
+
return `${base}/store/paypal/webhook`
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
private isLocalWebhookUrl(url: string) {
|
|
694
|
+
try {
|
|
695
|
+
const parsed = new URL(url)
|
|
696
|
+
return ["localhost", "127.0.0.1", "::1"].includes(parsed.hostname)
|
|
697
|
+
} catch {
|
|
698
|
+
return false
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
private async ensureWebhookRegistration() {
|
|
703
|
+
const env = await this.getCurrentEnvironment()
|
|
704
|
+
const { apiDetails } = await this.ensureSettingsDefaults()
|
|
705
|
+
const webhookIds = { ...(apiDetails.webhook_ids || {}) } as Record<string, string>
|
|
706
|
+
|
|
707
|
+
if (webhookIds[env]) {
|
|
708
|
+
return webhookIds[env]
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
const accessToken = await this.getAppAccessToken()
|
|
712
|
+
const baseUrl = env === "live" ? "https://api-m.paypal.com" : "https://api-m.sandbox.paypal.com"
|
|
713
|
+
const webhookUrl = await this.resolveWebhookUrl()
|
|
714
|
+
|
|
715
|
+
if (this.isLocalWebhookUrl(webhookUrl)) {
|
|
716
|
+
await this.recordAuditEvent("webhook_skipped_localhost", {
|
|
717
|
+
environment: env,
|
|
718
|
+
webhook_url: webhookUrl,
|
|
719
|
+
})
|
|
720
|
+
return webhookIds[env] || ""
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const listResp = await fetch(`${baseUrl}/v1/notifications/webhooks`, {
|
|
724
|
+
headers: {
|
|
725
|
+
Authorization: `Bearer ${accessToken}`,
|
|
726
|
+
"Content-Type": "application/json",
|
|
727
|
+
},
|
|
728
|
+
})
|
|
729
|
+
|
|
730
|
+
const listJson = await listResp.json().catch(() => ({}))
|
|
731
|
+
if (!listResp.ok) {
|
|
732
|
+
throw new Error(`PayPal webhook list failed (${listResp.status}): ${JSON.stringify(listJson)}`)
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
const existing = Array.isArray(listJson?.webhooks)
|
|
736
|
+
? listJson.webhooks.find((hook: any) => hook?.url === webhookUrl)
|
|
737
|
+
: null
|
|
738
|
+
|
|
739
|
+
let webhookId = existing?.id ? String(existing.id) : ""
|
|
740
|
+
|
|
741
|
+
if (!webhookId) {
|
|
742
|
+
const createResp = await fetch(`${baseUrl}/v1/notifications/webhooks`, {
|
|
743
|
+
method: "POST",
|
|
744
|
+
headers: {
|
|
745
|
+
Authorization: `Bearer ${accessToken}`,
|
|
746
|
+
"Content-Type": "application/json",
|
|
747
|
+
},
|
|
748
|
+
body: JSON.stringify({
|
|
749
|
+
url: webhookUrl,
|
|
750
|
+
event_types: [
|
|
751
|
+
{ name: "CHECKOUT.ORDER.APPROVED" },
|
|
752
|
+
{ name: "CHECKOUT.ORDER.CANCELLED" },
|
|
753
|
+
{ name: "PAYMENT.CAPTURE.COMPLETED" },
|
|
754
|
+
{ name: "PAYMENT.CAPTURE.DENIED" },
|
|
755
|
+
{ name: "PAYMENT.CAPTURE.REFUNDED" },
|
|
756
|
+
{ name: "PAYMENT.CAPTURE.REVERSED" },
|
|
757
|
+
{ name: "PAYMENT.AUTHORIZATION.CREATED" },
|
|
758
|
+
{ name: "PAYMENT.AUTHORIZATION.VOIDED" },
|
|
759
|
+
{ name: "PAYMENT.AUTHORIZATION.DENIED" },
|
|
760
|
+
{ name: "PAYMENT.REFUND.COMPLETED" },
|
|
761
|
+
{ name: "PAYMENT.REFUND.DENIED" },
|
|
762
|
+
{ name: "CUSTOMER.DISPUTE.CREATED" },
|
|
763
|
+
{ name: "CUSTOMER.DISPUTE.UPDATED" },
|
|
764
|
+
{ name: "CUSTOMER.DISPUTE.RESOLVED" },
|
|
765
|
+
],
|
|
766
|
+
}),
|
|
767
|
+
})
|
|
768
|
+
|
|
769
|
+
const createJson = await createResp.json().catch(() => ({}))
|
|
770
|
+
if (!createResp.ok) {
|
|
771
|
+
throw new Error(
|
|
772
|
+
`PayPal webhook create failed (${createResp.status}): ${JSON.stringify(createJson)}`
|
|
773
|
+
)
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
webhookId = String(createJson?.id || "")
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
if (!webhookId) {
|
|
780
|
+
throw new Error("PayPal webhook registration did not return an id")
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
const nextWebhookIds = { ...webhookIds, [env]: webhookId }
|
|
784
|
+
await this.saveSettings({
|
|
785
|
+
api_details: {
|
|
786
|
+
...apiDetails,
|
|
787
|
+
webhook_ids: nextWebhookIds,
|
|
788
|
+
},
|
|
789
|
+
})
|
|
790
|
+
|
|
791
|
+
await this.recordAuditEvent("webhook_registered", {
|
|
792
|
+
environment: env,
|
|
793
|
+
webhook_id: webhookId,
|
|
794
|
+
webhook_url: webhookUrl,
|
|
795
|
+
})
|
|
796
|
+
|
|
797
|
+
return webhookId
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
private maskValue(value?: string | null, visibleChars = 4) {
|
|
801
|
+
if (!value) return null
|
|
802
|
+
const trimmed = String(value)
|
|
803
|
+
if (trimmed.length <= visibleChars) {
|
|
804
|
+
return "•".repeat(trimmed.length)
|
|
805
|
+
}
|
|
806
|
+
return `${"•".repeat(Math.max(0, trimmed.length - visibleChars))}${trimmed.slice(
|
|
807
|
+
-visibleChars
|
|
808
|
+
)}`
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
async rotateCredentialEncryptionKey() {
|
|
812
|
+
const currentKey = this.getEncryptionKey()
|
|
813
|
+
if (!currentKey) {
|
|
814
|
+
throw new Error("PAYPAL_CREDENTIALS_ENCRYPTION_KEY must be set to rotate credentials.")
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
const row = await this.getCurrentRow()
|
|
818
|
+
if (!row) {
|
|
819
|
+
return { rotated: 0 }
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
const meta = (row.metadata || {}) as any
|
|
823
|
+
const credentials = { ...(meta.credentials || {}) }
|
|
824
|
+
let rotated = 0
|
|
825
|
+
|
|
826
|
+
for (const [env, envCreds] of Object.entries(credentials)) {
|
|
827
|
+
if (!envCreds || typeof envCreds !== "object") continue
|
|
828
|
+
const clientSecret = (envCreds as any).client_secret
|
|
829
|
+
if (!clientSecret) continue
|
|
830
|
+
|
|
831
|
+
const decrypted = this.maybeDecryptSecret(clientSecret)
|
|
832
|
+
const reEncrypted = this.maybeEncryptSecret(decrypted)
|
|
833
|
+
if (reEncrypted !== clientSecret) {
|
|
834
|
+
credentials[env] = {
|
|
835
|
+
...(envCreds as any),
|
|
836
|
+
client_secret: reEncrypted,
|
|
837
|
+
}
|
|
838
|
+
rotated += 1
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
if (rotated === 0) {
|
|
843
|
+
return { rotated: 0 }
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
await this.updatePayPalConnections({
|
|
847
|
+
id: row.id,
|
|
848
|
+
metadata: {
|
|
849
|
+
...(row.metadata || {}),
|
|
850
|
+
credentials,
|
|
851
|
+
},
|
|
852
|
+
seller_client_secret: credentials?.[row.environment as Environment]?.client_secret || null,
|
|
853
|
+
})
|
|
854
|
+
|
|
855
|
+
const updated = await this.getCurrentRow()
|
|
856
|
+
if (updated) {
|
|
857
|
+
await this.syncRowFieldsFromMetadata(updated, (updated.environment as Environment) || "sandbox")
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
await this.recordAuditEvent("credentials_rotated", {
|
|
861
|
+
environments: Object.keys(credentials),
|
|
862
|
+
})
|
|
863
|
+
|
|
864
|
+
return { rotated }
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
async getStatus(envOverride?: Environment) {
|
|
868
|
+
const row = await this.getCurrentRow()
|
|
869
|
+
const env = envOverride ?? (await this.getCurrentEnvironment())
|
|
870
|
+
|
|
871
|
+
if (!row) {
|
|
872
|
+
return { environment: env, status: "disconnected" as Status, seller_client_id_present: false }
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
const c = this.getEnvCreds(row, env)
|
|
876
|
+
const hasCreds = !!(c.clientId && c.clientSecret)
|
|
877
|
+
const merchantId = this.getMerchantId(row, env)
|
|
878
|
+
let sellerEmail: string | null = null
|
|
879
|
+
|
|
880
|
+
if (hasCreds && merchantId) {
|
|
881
|
+
try {
|
|
882
|
+
const merchantInfo = await this.fetchMerchantIntegrationDetails(env, merchantId)
|
|
883
|
+
sellerEmail = String(
|
|
884
|
+
merchantInfo?.primary_email ||
|
|
885
|
+
merchantInfo?.merchant_email ||
|
|
886
|
+
merchantInfo?.email ||
|
|
887
|
+
merchantInfo?.primaryEmail ||
|
|
888
|
+
""
|
|
889
|
+
)
|
|
890
|
+
if (!sellerEmail) {
|
|
891
|
+
sellerEmail = null
|
|
892
|
+
}
|
|
893
|
+
} catch (error) {
|
|
894
|
+
console.warn("[paypal_onboarding] failed to fetch merchant integration details", error)
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
return {
|
|
899
|
+
environment: env,
|
|
900
|
+
status: (hasCreds ? "connected" : "disconnected") as Status,
|
|
901
|
+
shared_id: row.shared_id ?? null,
|
|
902
|
+
auth_code: row.auth_code ? "***stored***" : null,
|
|
903
|
+
seller_client_id_present: hasCreds,
|
|
904
|
+
seller_client_id_masked: this.maskValue(c.clientId),
|
|
905
|
+
seller_client_secret_masked: c.clientSecret ? "••••••••" : null,
|
|
906
|
+
seller_email: sellerEmail,
|
|
907
|
+
updated_at: (row.updated_at as any)?.toISOString?.() ?? null,
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
async disconnect() {
|
|
912
|
+
const row = await this.getCurrentRow()
|
|
913
|
+
if (!row) return
|
|
914
|
+
const env = await this.getCurrentEnvironment()
|
|
915
|
+
|
|
916
|
+
const meta = (row.metadata || {}) as any
|
|
917
|
+
const creds = { ...(meta.credentials || {}) }
|
|
918
|
+
// Remove only the active environment credentials
|
|
919
|
+
delete creds[env]
|
|
920
|
+
|
|
921
|
+
const hasAnyCreds = Object.values(creds).some((v: any) => {
|
|
922
|
+
return v && typeof v === "object" && (v as any).client_id && (v as any).client_secret
|
|
923
|
+
})
|
|
924
|
+
|
|
925
|
+
await this.updatePayPalConnections({
|
|
926
|
+
id: row.id,
|
|
927
|
+
status: hasAnyCreds ? "connected" : "disconnected",
|
|
928
|
+
shared_id: null,
|
|
929
|
+
auth_code: null,
|
|
930
|
+
seller_client_id: null,
|
|
931
|
+
seller_client_secret: null,
|
|
932
|
+
app_access_token: null,
|
|
933
|
+
app_access_token_expires_at: null,
|
|
934
|
+
metadata: {
|
|
935
|
+
...(row.metadata || {}),
|
|
936
|
+
credentials: creds,
|
|
937
|
+
active_environment: env,
|
|
938
|
+
},
|
|
939
|
+
})
|
|
940
|
+
const updated = await this.getCurrentRow()
|
|
941
|
+
if (updated) {
|
|
942
|
+
await this.syncRowFieldsFromMetadata(updated, env)
|
|
943
|
+
}
|
|
944
|
+
await this.recordAuditEvent("disconnected", { environment: env })
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
async getAppAccessToken(): Promise<string> {
|
|
948
|
+
const row = await this.getCurrentRow()
|
|
949
|
+
const env = await this.getCurrentEnvironment()
|
|
950
|
+
const creds = await this.getActiveCredentials()
|
|
951
|
+
|
|
952
|
+
if (!row) {
|
|
953
|
+
throw new Error("PayPal connection row not found. Please complete onboarding.")
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
const expiresAt = row.app_access_token_expires_at ? new Date(row.app_access_token_expires_at as any) : null
|
|
957
|
+
if (row.app_access_token && expiresAt) {
|
|
958
|
+
const msLeft = expiresAt.getTime() - Date.now()
|
|
959
|
+
if (msLeft > 2 * 60 * 1000) return row.app_access_token
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
const baseUrl = env === "live" ? "https://api-m.paypal.com" : "https://api-m.sandbox.paypal.com"
|
|
963
|
+
const basic = Buffer.from(`${creds.client_id}:${creds.client_secret}`).toString("base64")
|
|
964
|
+
|
|
965
|
+
const body = new URLSearchParams()
|
|
966
|
+
body.set("grant_type", "client_credentials")
|
|
967
|
+
|
|
968
|
+
const res = await fetch(`${baseUrl}/v1/oauth2/token`, {
|
|
969
|
+
method: "POST",
|
|
970
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded", Authorization: `Basic ${basic}` },
|
|
971
|
+
body,
|
|
972
|
+
})
|
|
973
|
+
|
|
974
|
+
const json = await res.json().catch(() => ({}))
|
|
975
|
+
if (!res.ok) throw new Error(`PayPal client_credentials failed (${res.status}): ${JSON.stringify(json)}`)
|
|
976
|
+
|
|
977
|
+
const accessToken = String(json.access_token)
|
|
978
|
+
const expiresIn = Number(json.expires_in || 3600)
|
|
979
|
+
const newExpiresAt = new Date(Date.now() + expiresIn * 1000)
|
|
980
|
+
|
|
981
|
+
await this.updatePayPalConnections({
|
|
982
|
+
id: row.id,
|
|
983
|
+
app_access_token: accessToken,
|
|
984
|
+
app_access_token_expires_at: newExpiresAt as any,
|
|
985
|
+
})
|
|
986
|
+
|
|
987
|
+
return accessToken
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
/**
|
|
991
|
+
* Generate a client token for PayPal JS SDK (required for CardFields/PaymentFields).
|
|
992
|
+
* This token is short-lived and safe to send to the browser.
|
|
993
|
+
*
|
|
994
|
+
* PayPal endpoint: POST /v1/identity/generate-token
|
|
995
|
+
*/
|
|
996
|
+
async generateClientToken(opts?: { locale?: string }): Promise<string> {
|
|
997
|
+
const env = await this.getCurrentEnvironment()
|
|
998
|
+
const baseUrl = env === "live" ? "https://api-m.paypal.com" : "https://api-m.sandbox.paypal.com"
|
|
999
|
+
|
|
1000
|
+
const accessToken = await this.getAppAccessToken()
|
|
1001
|
+
|
|
1002
|
+
const res = await fetch(`${baseUrl}/v1/identity/generate-token`, {
|
|
1003
|
+
method: "POST",
|
|
1004
|
+
headers: {
|
|
1005
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1006
|
+
"Content-Type": "application/json",
|
|
1007
|
+
Accept: "application/json",
|
|
1008
|
+
...(opts?.locale ? { "Accept-Language": opts.locale } : {}),
|
|
1009
|
+
...(this.cfg.bnCode ? { "PayPal-Partner-Attribution-Id": this.cfg.bnCode } : {}),
|
|
1010
|
+
},
|
|
1011
|
+
})
|
|
1012
|
+
|
|
1013
|
+
const json = await res.json().catch(() => ({}))
|
|
1014
|
+
if (!res.ok) {
|
|
1015
|
+
throw new Error(`PayPal generate-token failed (${res.status}): ${JSON.stringify(json)}`)
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
const token = String((json as any)?.client_token || "")
|
|
1019
|
+
if (!token) {
|
|
1020
|
+
throw new Error("PayPal client_token is missing in generate-token response")
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
return token
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
/**
|
|
1027
|
+
* GLOBAL PayPal settings (single row)
|
|
1028
|
+
*/
|
|
1029
|
+
async getSettings() {
|
|
1030
|
+
const rows = await this.listPayPalSettings({})
|
|
1031
|
+
const row = rows?.[0]
|
|
1032
|
+
return { data: (row?.data || {}) as Record<string, any> }
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
async saveSettings(patch: Record<string, any>) {
|
|
1036
|
+
const rows = await this.listPayPalSettings({})
|
|
1037
|
+
const row = rows?.[0]
|
|
1038
|
+
const current = (row?.data || {}) as Record<string, any>
|
|
1039
|
+
|
|
1040
|
+
const next = { ...current, ...patch }
|
|
1041
|
+
|
|
1042
|
+
if (!row) {
|
|
1043
|
+
const created = await this.createPayPalSettings({ data: next })
|
|
1044
|
+
return { data: (created.data || {}) as Record<string, any> }
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
await this.updatePayPalSettings({ id: row.id, data: next })
|
|
1048
|
+
return { data: next }
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
/**
|
|
1052
|
+
* Active credentials based on selected environment in the single connection row.
|
|
1053
|
+
*/
|
|
1054
|
+
async getActiveCredentials() {
|
|
1055
|
+
const row = await this.getCurrentRow()
|
|
1056
|
+
const env = await this.getCurrentEnvironment()
|
|
1057
|
+
|
|
1058
|
+
if (!row) {
|
|
1059
|
+
throw new Error("PayPal connection row not found. Please complete onboarding.")
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
const c = this.getEnvCreds(row, env)
|
|
1063
|
+
const clientSecret = this.maybeDecryptSecret(c.clientSecret)
|
|
1064
|
+
|
|
1065
|
+
if (!c.clientId || !clientSecret) {
|
|
1066
|
+
throw new Error(
|
|
1067
|
+
`PayPal credentials missing for environment "${env}". Please save credentials.`
|
|
1068
|
+
)
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
return {
|
|
1072
|
+
environment: env,
|
|
1073
|
+
client_id: c.clientId,
|
|
1074
|
+
client_secret: clientSecret,
|
|
1075
|
+
merchant_id: c.merchantId,
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
async getOrderDetails(orderId: string) {
|
|
1080
|
+
if (!orderId) {
|
|
1081
|
+
throw new Error("PayPal orderId is required")
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
const creds = await this.getActiveCredentials()
|
|
1085
|
+
const base =
|
|
1086
|
+
creds.environment === "live"
|
|
1087
|
+
? "https://api-m.paypal.com"
|
|
1088
|
+
: "https://api-m.sandbox.paypal.com"
|
|
1089
|
+
const auth = Buffer.from(`${creds.client_id}:${creds.client_secret}`).toString("base64")
|
|
1090
|
+
|
|
1091
|
+
const tokenResp = await fetch(`${base}/v1/oauth2/token`, {
|
|
1092
|
+
method: "POST",
|
|
1093
|
+
headers: {
|
|
1094
|
+
Authorization: `Basic ${auth}`,
|
|
1095
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1096
|
+
},
|
|
1097
|
+
body: "grant_type=client_credentials",
|
|
1098
|
+
})
|
|
1099
|
+
|
|
1100
|
+
const tokenText = await tokenResp.text()
|
|
1101
|
+
if (!tokenResp.ok) {
|
|
1102
|
+
throw new Error(`PayPal token error (${tokenResp.status}): ${tokenText}`)
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
const tokenJson = JSON.parse(tokenText)
|
|
1106
|
+
const accessToken = String(tokenJson.access_token)
|
|
1107
|
+
|
|
1108
|
+
const resp = await fetch(`${base}/v2/checkout/orders/${orderId}`, {
|
|
1109
|
+
method: "GET",
|
|
1110
|
+
headers: {
|
|
1111
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1112
|
+
"Content-Type": "application/json",
|
|
1113
|
+
},
|
|
1114
|
+
})
|
|
1115
|
+
|
|
1116
|
+
const text = await resp.text()
|
|
1117
|
+
if (!resp.ok) {
|
|
1118
|
+
throw new Error(`PayPal get order error (${resp.status}): ${text}`)
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
return JSON.parse(text)
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
async createWebhookEventRecord(input: {
|
|
1125
|
+
event_id: string
|
|
1126
|
+
event_type: string
|
|
1127
|
+
resource_id?: string | null
|
|
1128
|
+
payload?: Record<string, unknown>
|
|
1129
|
+
event_version?: string | null
|
|
1130
|
+
transmission_id?: string | null
|
|
1131
|
+
transmission_time?: Date | null
|
|
1132
|
+
status?: string
|
|
1133
|
+
attempt_count?: number
|
|
1134
|
+
}) {
|
|
1135
|
+
try {
|
|
1136
|
+
const created = await this.createPayPalWebhookEvents({
|
|
1137
|
+
event_id: input.event_id,
|
|
1138
|
+
event_type: input.event_type,
|
|
1139
|
+
resource_id: input.resource_id ?? null,
|
|
1140
|
+
payload: input.payload ?? {},
|
|
1141
|
+
event_version: input.event_version ?? null,
|
|
1142
|
+
transmission_id: input.transmission_id ?? null,
|
|
1143
|
+
transmission_time: input.transmission_time ?? null,
|
|
1144
|
+
status: input.status ?? "pending",
|
|
1145
|
+
attempt_count: input.attempt_count ?? 0,
|
|
1146
|
+
next_retry_at: null,
|
|
1147
|
+
processed_at: null,
|
|
1148
|
+
last_error: null,
|
|
1149
|
+
})
|
|
1150
|
+
return { created: true, event: created }
|
|
1151
|
+
} catch (error: any) {
|
|
1152
|
+
const message = String(error?.message || "")
|
|
1153
|
+
if (message.includes("paypal_webhook_event_event_id_unique") || message.includes("unique")) {
|
|
1154
|
+
const existing = await this.listPayPalWebhookEvents({ event_id: input.event_id })
|
|
1155
|
+
return { created: false, event: existing?.[0] ?? null }
|
|
1156
|
+
}
|
|
1157
|
+
throw error
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
async updateWebhookEventRecord(input: {
|
|
1162
|
+
id: string
|
|
1163
|
+
status?: string
|
|
1164
|
+
attempt_count?: number
|
|
1165
|
+
next_retry_at?: Date | null
|
|
1166
|
+
processed_at?: Date | null
|
|
1167
|
+
last_error?: string | null
|
|
1168
|
+
resource_id?: string | null
|
|
1169
|
+
}) {
|
|
1170
|
+
return await this.updatePayPalWebhookEvents({
|
|
1171
|
+
id: input.id,
|
|
1172
|
+
status: input.status,
|
|
1173
|
+
attempt_count: input.attempt_count,
|
|
1174
|
+
next_retry_at: input.next_retry_at ?? null,
|
|
1175
|
+
processed_at: input.processed_at ?? null,
|
|
1176
|
+
last_error: input.last_error ?? null,
|
|
1177
|
+
resource_id: input.resource_id ?? null,
|
|
1178
|
+
})
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
async upsertDispute(input: {
|
|
1182
|
+
dispute_id: string
|
|
1183
|
+
status?: string | null
|
|
1184
|
+
reason?: string | null
|
|
1185
|
+
stage?: string | null
|
|
1186
|
+
amount?: string | null
|
|
1187
|
+
currency_code?: string | null
|
|
1188
|
+
transaction_id?: string | null
|
|
1189
|
+
seller_transaction_id?: string | null
|
|
1190
|
+
order_id?: string | null
|
|
1191
|
+
cart_id?: string | null
|
|
1192
|
+
payload?: Record<string, unknown>
|
|
1193
|
+
}) {
|
|
1194
|
+
if (!input.dispute_id) {
|
|
1195
|
+
throw new Error("dispute_id is required")
|
|
1196
|
+
}
|
|
1197
|
+
const existing = await this.listPayPalDisputes({ dispute_id: input.dispute_id })
|
|
1198
|
+
const row = existing?.[0]
|
|
1199
|
+
|
|
1200
|
+
if (!row) {
|
|
1201
|
+
return await this.createPayPalDisputes({
|
|
1202
|
+
dispute_id: input.dispute_id,
|
|
1203
|
+
status: input.status ?? null,
|
|
1204
|
+
reason: input.reason ?? null,
|
|
1205
|
+
stage: input.stage ?? null,
|
|
1206
|
+
amount: input.amount ?? null,
|
|
1207
|
+
currency_code: input.currency_code ?? null,
|
|
1208
|
+
transaction_id: input.transaction_id ?? null,
|
|
1209
|
+
seller_transaction_id: input.seller_transaction_id ?? null,
|
|
1210
|
+
order_id: input.order_id ?? null,
|
|
1211
|
+
cart_id: input.cart_id ?? null,
|
|
1212
|
+
payload: input.payload ?? {},
|
|
1213
|
+
})
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
return await this.updatePayPalDisputes({
|
|
1217
|
+
id: row.id,
|
|
1218
|
+
status: input.status ?? row.status ?? null,
|
|
1219
|
+
reason: input.reason ?? row.reason ?? null,
|
|
1220
|
+
stage: input.stage ?? row.stage ?? null,
|
|
1221
|
+
amount: input.amount ?? row.amount ?? null,
|
|
1222
|
+
currency_code: input.currency_code ?? row.currency_code ?? null,
|
|
1223
|
+
transaction_id: input.transaction_id ?? row.transaction_id ?? null,
|
|
1224
|
+
seller_transaction_id: input.seller_transaction_id ?? row.seller_transaction_id ?? null,
|
|
1225
|
+
order_id: input.order_id ?? row.order_id ?? null,
|
|
1226
|
+
cart_id: input.cart_id ?? row.cart_id ?? null,
|
|
1227
|
+
payload: {
|
|
1228
|
+
...(row.payload || {}),
|
|
1229
|
+
...(input.payload || {}),
|
|
1230
|
+
},
|
|
1231
|
+
})
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
async recordAuditEvent(eventType: string, metadata?: Record<string, unknown>) {
|
|
1235
|
+
const loggingEnabled = await this.getLoggingPreference().catch(() => true)
|
|
1236
|
+
if (!loggingEnabled) {
|
|
1237
|
+
return null
|
|
1238
|
+
}
|
|
1239
|
+
try {
|
|
1240
|
+
return await this.createPayPalAuditLogs({
|
|
1241
|
+
event_type: eventType,
|
|
1242
|
+
metadata: metadata ?? {},
|
|
1243
|
+
})
|
|
1244
|
+
} catch (error) {
|
|
1245
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
1246
|
+
if (message.includes("paypal_audit_log") && message.includes("does not exist")) {
|
|
1247
|
+
console.warn("[paypal_onboarding] audit log table missing; skipping audit event")
|
|
1248
|
+
return null
|
|
1249
|
+
}
|
|
1250
|
+
throw error
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
async getAuditLogs(limit = 50) {
|
|
1255
|
+
const entries = await this.listPayPalAuditLogs({})
|
|
1256
|
+
const sorted = [...(entries || [])].sort((a: any, b: any) => {
|
|
1257
|
+
const aTime = a?.created_at ? new Date(a.created_at).getTime() : 0
|
|
1258
|
+
const bTime = b?.created_at ? new Date(b.created_at).getTime() : 0
|
|
1259
|
+
return bTime - aTime
|
|
1260
|
+
})
|
|
1261
|
+
return sorted.slice(0, limit).map((entry: any) => ({
|
|
1262
|
+
id: entry.id,
|
|
1263
|
+
event_type: entry.event_type,
|
|
1264
|
+
metadata: this.sanitizeAuditMetadata(entry.metadata || {}),
|
|
1265
|
+
created_at: entry.created_at || null,
|
|
1266
|
+
}))
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
private sanitizeAuditMetadata(metadata: Record<string, unknown>) {
|
|
1270
|
+
const next: Record<string, unknown> = { ...metadata }
|
|
1271
|
+
if (typeof next.client_id === "string") {
|
|
1272
|
+
next.client_id = this.maskValue(next.client_id)
|
|
1273
|
+
}
|
|
1274
|
+
if (typeof next.client_secret === "string") {
|
|
1275
|
+
next.client_secret = "••••••••"
|
|
1276
|
+
}
|
|
1277
|
+
return next
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
async recordMetric(name: string, metadata?: Record<string, unknown>) {
|
|
1281
|
+
const loggingEnabled = await this.getLoggingPreference().catch(() => true)
|
|
1282
|
+
if (!loggingEnabled) {
|
|
1283
|
+
return null
|
|
1284
|
+
}
|
|
1285
|
+
const existing = await this.listPayPalMetrics({ name })
|
|
1286
|
+
const row = existing?.[0]
|
|
1287
|
+
const current = (row?.data || {}) as Record<string, any>
|
|
1288
|
+
const next = {
|
|
1289
|
+
...current,
|
|
1290
|
+
...(metadata || {}),
|
|
1291
|
+
count: Number(current.count || 0) + 1,
|
|
1292
|
+
last_recorded_at: new Date().toISOString(),
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
if (!row) {
|
|
1296
|
+
return await this.createPayPalMetrics({
|
|
1297
|
+
name,
|
|
1298
|
+
data: next,
|
|
1299
|
+
})
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
return await this.updatePayPalMetrics({
|
|
1303
|
+
id: row.id,
|
|
1304
|
+
name,
|
|
1305
|
+
data: next,
|
|
1306
|
+
})
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
async recordPaymentLog(eventType: string, metadata?: Record<string, unknown>) {
|
|
1310
|
+
const loggingEnabled = await this.getLoggingPreference().catch(() => true)
|
|
1311
|
+
if (!loggingEnabled) {
|
|
1312
|
+
return null
|
|
1313
|
+
}
|
|
1314
|
+
const payload = {
|
|
1315
|
+
event_type: eventType,
|
|
1316
|
+
metadata: metadata ?? {},
|
|
1317
|
+
created_at: new Date().toISOString(),
|
|
1318
|
+
}
|
|
1319
|
+
console.info("[PayPal] payment_event", payload)
|
|
1320
|
+
return await this.recordAuditEvent(`payment_${eventType}`, metadata)
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
async updateReconciliationStatus(input: {
|
|
1324
|
+
status: "success" | "failed"
|
|
1325
|
+
sessions_checked?: number
|
|
1326
|
+
sessions_updated?: number
|
|
1327
|
+
drift_count?: number
|
|
1328
|
+
error_message?: string | null
|
|
1329
|
+
last_drift_at?: string | null
|
|
1330
|
+
last_drift_order_id?: string | null
|
|
1331
|
+
}) {
|
|
1332
|
+
const existing = await this.listPayPalMetrics({ name: "reconcile_status" })
|
|
1333
|
+
const row = existing?.[0]
|
|
1334
|
+
const current = (row?.data || {}) as Record<string, any>
|
|
1335
|
+
const now = new Date().toISOString()
|
|
1336
|
+
const next = {
|
|
1337
|
+
...current,
|
|
1338
|
+
runs: Number(current.runs || 0) + 1,
|
|
1339
|
+
last_run_at: now,
|
|
1340
|
+
last_run_status: input.status,
|
|
1341
|
+
last_success_at: input.status === "success" ? now : current.last_success_at,
|
|
1342
|
+
last_failure_at: input.status === "failed" ? now : current.last_failure_at,
|
|
1343
|
+
sessions_checked: Number(input.sessions_checked ?? current.sessions_checked ?? 0),
|
|
1344
|
+
sessions_updated: Number(input.sessions_updated ?? current.sessions_updated ?? 0),
|
|
1345
|
+
drift_count: Number(input.drift_count ?? current.drift_count ?? 0),
|
|
1346
|
+
last_drift_at: input.last_drift_at ?? current.last_drift_at ?? null,
|
|
1347
|
+
last_drift_order_id: input.last_drift_order_id ?? current.last_drift_order_id ?? null,
|
|
1348
|
+
last_error: input.error_message ?? current.last_error ?? null,
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
if (!row) {
|
|
1352
|
+
return await this.createPayPalMetrics({
|
|
1353
|
+
name: "reconcile_status",
|
|
1354
|
+
data: next,
|
|
1355
|
+
})
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
return await this.updatePayPalMetrics({
|
|
1359
|
+
id: row.id,
|
|
1360
|
+
name: "reconcile_status",
|
|
1361
|
+
data: next,
|
|
1362
|
+
})
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
async getReconciliationStatus() {
|
|
1366
|
+
const rows = await this.listPayPalMetrics({ name: "reconcile_status" })
|
|
1367
|
+
const row = rows?.[0]
|
|
1368
|
+
return {
|
|
1369
|
+
status: (row?.data as Record<string, any>) || {},
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
async sendAlert(input: {
|
|
1374
|
+
type: string
|
|
1375
|
+
message: string
|
|
1376
|
+
metadata?: Record<string, unknown>
|
|
1377
|
+
}) {
|
|
1378
|
+
const urls = this.getAlertWebhookUrls()
|
|
1379
|
+
if (urls.length === 0) {
|
|
1380
|
+
return
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
const payload = {
|
|
1384
|
+
type: input.type,
|
|
1385
|
+
message: input.message,
|
|
1386
|
+
metadata: input.metadata ?? {},
|
|
1387
|
+
source: "paypal",
|
|
1388
|
+
timestamp: new Date().toISOString(),
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
await Promise.all(
|
|
1392
|
+
urls.map(async (url) => {
|
|
1393
|
+
try {
|
|
1394
|
+
const resp = await fetch(url, {
|
|
1395
|
+
method: "POST",
|
|
1396
|
+
headers: {
|
|
1397
|
+
"Content-Type": "application/json",
|
|
1398
|
+
},
|
|
1399
|
+
body: JSON.stringify(payload),
|
|
1400
|
+
})
|
|
1401
|
+
if (!resp.ok) {
|
|
1402
|
+
const text = await resp.text().catch(() => "")
|
|
1403
|
+
await this.recordAuditEvent("alert_failed", {
|
|
1404
|
+
url,
|
|
1405
|
+
status: resp.status,
|
|
1406
|
+
response: text,
|
|
1407
|
+
})
|
|
1408
|
+
} else {
|
|
1409
|
+
await this.recordAuditEvent("alert_sent", { url, type: input.type })
|
|
1410
|
+
}
|
|
1411
|
+
} catch (error: any) {
|
|
1412
|
+
await this.recordAuditEvent("alert_failed", {
|
|
1413
|
+
url,
|
|
1414
|
+
message: error?.message,
|
|
1415
|
+
})
|
|
1416
|
+
}
|
|
1417
|
+
})
|
|
1418
|
+
)
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
export default PayPalModuleService
|