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