@easypayment/medusa-paypal 0.6.4 → 0.6.6

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.
@@ -103,10 +103,6 @@ class PayPalModuleService extends MedusaService({
103
103
  return env === "live" ? onboarding.partner_merchant_id_live : onboarding.partner_merchant_id_sandbox
104
104
  }
105
105
 
106
- /**
107
- * We keep a single row in DB and store the currently selected environment there.
108
- * If no row exists yet, default to live (production).
109
- */
110
106
  private async getCurrentRow(): Promise<any | null> {
111
107
  const rows = await this.listPayPalConnections({})
112
108
  return rows?.[0] ?? null
@@ -185,10 +181,6 @@ class PayPalModuleService extends MedusaService({
185
181
  }
186
182
  }
187
183
 
188
- // Fallback: traverse nested values so shapes like
189
- // { email_address: { value: "seller@example.com" } }
190
- // or { email: { address: "seller@example.com" } }
191
- // are still detected.
192
184
  queue.push(...Object.values(obj))
193
185
  }
194
186
  }
@@ -406,18 +398,12 @@ class PayPalModuleService extends MedusaService({
406
398
  })
407
399
  }
408
400
 
409
- /**
410
- * Set environment based on admin UI selection (WooCommerce-style).
411
- * Switching environment clears stored credentials and requires re-onboarding.
412
- */
413
-
414
401
  async setEnvironment(env: Environment) {
415
402
  const nextEnv: Environment = env === "sandbox" ? "sandbox" : "live"
416
403
  const row = await this.getCurrentRow()
417
404
  const previousEnv = (row?.environment as Environment) || "live"
418
405
 
419
406
  if (!row) {
420
- // Create a row with no credentials yet for either environment
421
407
  const created = await this.createPayPalConnections({
422
408
  environment: nextEnv,
423
409
  status: "disconnected",
@@ -438,7 +424,6 @@ class PayPalModuleService extends MedusaService({
438
424
  return created
439
425
  }
440
426
 
441
- // Just switch the active environment (do NOT wipe other env credentials)
442
427
  await this.updatePayPalConnections({
443
428
  id: row.id,
444
429
  environment: nextEnv,
@@ -450,7 +435,6 @@ class PayPalModuleService extends MedusaService({
450
435
  },
451
436
  })
452
437
 
453
- // Sync top-level fields/status for the active env so existing code keeps working
454
438
  const updated = await this.getCurrentRow()
455
439
  if (updated) {
456
440
  await this.syncRowFieldsFromMetadata(updated, nextEnv)
@@ -463,36 +447,18 @@ class PayPalModuleService extends MedusaService({
463
447
  return await this.getCurrentRow()
464
448
  }
465
449
 
466
- /**
467
- * ✅ WooCommerce-style signup link generation (your service returns PayPal partner-referrals JSON)
468
- *
469
- * - POST to your PHP service (WPG_ONBOARDING_URL)
470
- * - Content-Type: application/x-www-form-urlencoded
471
- * - fields: email, sandbox, return_url, return_url_description, products[], partner_merchant_id
472
- *
473
- * Response formats supported:
474
- * 1) PayPal partner-referrals JSON: { links: [ { rel: "action_url", href: "..." }, ... ] }
475
- * 2) Custom JSON: { onboarding_url: "..." }
476
- * 3) Plain URL string
477
- */
478
450
  async createOnboardingLink(input?: { email?: string; products?: string[] }) {
479
451
  const { onboarding } = await this.ensureSettingsDefaults()
480
452
  const return_url = `${String(onboarding.backend_url || "").replace(/\/$/, "")}/admin/paypal/onboard-complete`
481
453
  const env = await this.getCurrentEnvironment()
482
454
  const partner_merchant_id = await this.getPartnerMerchantId(env)
483
455
 
484
- // Match WooCommerce behavior: prefer the current admin user email when available.
485
- // If it's missing, continue without it (some services can infer or ignore the field).
486
456
  const email = (input?.email || "").trim()
487
457
 
488
458
  if (!partner_merchant_id) {
489
459
  throw new Error("Missing PAYPAL_PARTNER_MERCHANT_ID_* env for current environment")
490
460
  }
491
461
 
492
- // NOTE:
493
- // We intentionally avoid DB access here because Medusa v2 has a known issue where
494
- // MedusaService-generated DB methods can throw 'fork' errors when called outside a transaction context.
495
- // We store onboarding state later when the JS callback returns authCode/sharedId.
496
462
  const form = new URLSearchParams()
497
463
  if (email) {
498
464
  form.set("email", email)
@@ -504,10 +470,6 @@ class PayPalModuleService extends MedusaService({
504
470
 
505
471
  const products = input?.products?.length ? input.products : ["PPCP"]
506
472
 
507
- // WooCommerce/wp_remote_request encodes PHP arrays like products[0]=PPCP.
508
- // To maximize compatibility with your existing PHP bridge, we send BOTH:
509
- // - products[0], products[1], ...
510
- // - products[] (common PHP convention)
511
473
  products.forEach((p, i) => {
512
474
  form.append(`products[${i}]`, p)
513
475
  form.append("products[]", p)
@@ -526,7 +488,6 @@ class PayPalModuleService extends MedusaService({
526
488
 
527
489
  const trimmed = text.trim()
528
490
 
529
- // plain URL
530
491
  if (trimmed.startsWith("http")) {
531
492
  return { onboarding_url: trimmed, return_url }
532
493
  }
@@ -538,22 +499,13 @@ class PayPalModuleService extends MedusaService({
538
499
  throw new Error(`Invalid onboarding link response (not JSON / URL): ${trimmed.slice(0, 200)}`)
539
500
  }
540
501
 
541
- /**
542
- * ✅ WooCommerce-style wrapper support
543
- *
544
- * Your PHP service (same as WooCommerce) often returns a wrapper like:
545
- * { result, http_code, headers, body }
546
- * where `body` is the actual PayPal partner-referrals JSON string.
547
- */
548
502
  if (json?.body) {
549
503
  const inner = typeof json.body === "string" ? json.body.trim() : json.body
550
504
 
551
- // body is a plain URL
552
505
  if (typeof inner === "string" && inner.startsWith("http")) {
553
506
  return { onboarding_url: inner, return_url }
554
507
  }
555
508
 
556
- // body is JSON string/object
557
509
  try {
558
510
  json = typeof inner === "string" ? JSON.parse(inner) : inner
559
511
  } catch {
@@ -565,7 +517,6 @@ class PayPalModuleService extends MedusaService({
565
517
  }
566
518
  }
567
519
 
568
- // Typical error shape: { name, message, debug_id, details: [...], links: [...] }
569
520
  if (json?.name && json?.message && (json?.debug_id || json?.details || json?.links)) {
570
521
  const debug = json.debug_id ? ` debug_id=${json.debug_id}` : ""
571
522
  const details = Array.isArray(json.details)
@@ -584,12 +535,10 @@ class PayPalModuleService extends MedusaService({
584
535
  throw new Error(`PayPal onboarding error: ${json.name}: ${json.message}.${debug}${details ? ` Details: ${details}` : ""}`)
585
536
  }
586
537
 
587
- // custom json
588
538
  if (json?.onboarding_url && String(json.onboarding_url).startsWith("http")) {
589
539
  return { onboarding_url: String(json.onboarding_url), return_url }
590
540
  }
591
541
 
592
- // PayPal partner-referrals format: links[] rel=action_url
593
542
  const links = Array.isArray(json?.links) ? json.links : null
594
543
  if (links) {
595
544
  const action = links.find(
@@ -611,7 +560,6 @@ class PayPalModuleService extends MedusaService({
611
560
  const env = await this.getCurrentEnvironment()
612
561
 
613
562
  if (row) {
614
- // MedusaService-generated update methods expect an object that includes the entity id
615
563
  await this.updatePayPalConnections({ id: row.id, status: "pending" })
616
564
  return
617
565
  }
@@ -645,29 +593,16 @@ class PayPalModuleService extends MedusaService({
645
593
  })
646
594
  }
647
595
 
648
- /**
649
- * Exchange authCode/sharedId for seller API credentials (server-side) and save in DB.
650
- *
651
- * This calls an optional exchange service you control:
652
- * PAYPAL_EXCHANGE_SERVICE_URL or PAYPAL_PARTNER_EXCHANGE_URL
653
- *
654
- * Expected JSON response:
655
- * { clientId: string, clientSecret: string, merchantId?: string }
656
- */
657
596
  async exchangeAndSaveSellerCredentials(input: {
658
597
  authCode: string
659
598
  sharedId: string
660
599
  env?: "sandbox" | "live"
661
600
  }) {
662
- // 1) Persist callback (sharedId/authCode) first
663
601
  await this.saveOnboardCallback({ authCode: input.authCode, sharedId: input.sharedId })
664
602
 
665
- // 2) Exchange authCode + sharedId (+ seller nonce) to get SELLER access token
666
603
  const env = (input.env || (await this.getCurrentEnvironment())) as Environment
667
604
  const baseUrl = env === "live" ? "https://api-m.paypal.com" : "https://api-m.sandbox.paypal.com"
668
605
 
669
- // IMPORTANT: code_verifier MUST match the seller_nonce used when creating the onboarding link.
670
- // In WooCommerce you use a fixed nonce() and it works, so we do the same here (no DB storage).
671
606
  const { onboarding } = await this.ensureSettingsDefaults()
672
607
  const sellerNonce = (onboarding.seller_nonce || "").trim()
673
608
  if (!sellerNonce) {
@@ -709,7 +644,6 @@ class PayPalModuleService extends MedusaService({
709
644
  throw new Error("PayPal token exchange succeeded but access_token is missing.")
710
645
  }
711
646
 
712
- // 3) Use SELLER access token to fetch seller REST API credentials
713
647
  const partnerMerchantId = await this.getPartnerMerchantId(env)
714
648
  if (!partnerMerchantId) {
715
649
  throw new Error("Missing PayPal partner merchant id configuration.")
@@ -755,7 +689,6 @@ class PayPalModuleService extends MedusaService({
755
689
  null
756
690
 
757
691
  if (!sellerEmail) {
758
- // Use all available merchant/payer ID candidates from the token and credential responses
759
692
  const merchantCandidates = [
760
693
  String(credJson.payer_id || "").trim(),
761
694
  String(credJson.merchant_id || "").trim(),
@@ -777,8 +710,6 @@ class PayPalModuleService extends MedusaService({
777
710
  }
778
711
  }
779
712
 
780
- // 4) Save seller credentials (marks status = connected)
781
- // We save FIRST so that getAppAccessToken() works for the post-save email lookup below
782
713
  await this.saveSellerCredentials({
783
714
  clientId,
784
715
  clientSecret,
@@ -786,9 +717,6 @@ class PayPalModuleService extends MedusaService({
786
717
  sellerEmail,
787
718
  })
788
719
 
789
- // 5) Post-save email lookup via merchant_id.
790
- // Some partner accounts do not have permission to query by tracking_id,
791
- // so we only rely on merchant integration detail lookup.
792
720
  if (!sellerEmail && sellerMerchantId) {
793
721
  try {
794
722
  const appAccessToken = await this.getAppAccessToken()
@@ -801,10 +729,8 @@ class PayPalModuleService extends MedusaService({
801
729
  console.warn("[PayPal] Post-save merchant_id email lookup failed:", e?.message || e)
802
730
  }
803
731
  }
804
-
805
732
  }
806
733
 
807
-
808
734
  async saveSellerCredentials(input: {
809
735
  clientId: string
810
736
  clientSecret: string
@@ -1114,7 +1040,6 @@ class PayPalModuleService extends MedusaService({
1114
1040
 
1115
1041
  const meta = (row.metadata || {}) as any
1116
1042
  const creds = { ...(meta.credentials || {}) }
1117
- // Remove only the active environment credentials
1118
1043
  delete creds[env]
1119
1044
 
1120
1045
  const hasAnyCreds = Object.values(creds).some((v: any) => {
@@ -1188,12 +1113,6 @@ class PayPalModuleService extends MedusaService({
1188
1113
  return accessToken
1189
1114
  }
1190
1115
 
1191
- /**
1192
- * Generate a client token for PayPal JS SDK (required for CardFields/PaymentFields).
1193
- * This token is short-lived and safe to send to the browser.
1194
- *
1195
- * PayPal endpoint: POST /v1/identity/generate-token
1196
- */
1197
1116
  async generateClientToken(opts?: { locale?: string }): Promise<string> {
1198
1117
  const env = await this.getCurrentEnvironment()
1199
1118
  const baseUrl = env === "live" ? "https://api-m.paypal.com" : "https://api-m.sandbox.paypal.com"
@@ -1224,20 +1143,12 @@ class PayPalModuleService extends MedusaService({
1224
1143
  return token
1225
1144
  }
1226
1145
 
1227
- /**
1228
- * GLOBAL PayPal settings (single row)
1229
- */
1230
1146
  async getSettings() {
1231
1147
  const rows = await this.listPayPalSettings({})
1232
1148
  const row = rows?.[0]
1233
1149
  return { data: (row?.data || {}) as Record<string, any> }
1234
1150
  }
1235
1151
 
1236
- /**
1237
- * Deep-merge patch into current settings.
1238
- * Nested objects (additional_settings, api_details, etc.) are merged,
1239
- * not replaced.
1240
- */
1241
1152
  private deepMerge(
1242
1153
  target: Record<string, any>,
1243
1154
  source: Record<string, any>
@@ -1278,9 +1189,6 @@ class PayPalModuleService extends MedusaService({
1278
1189
  return { data: next }
1279
1190
  }
1280
1191
 
1281
- /**
1282
- * Active credentials based on selected environment in the single connection row.
1283
- */
1284
1192
  async getActiveCredentials() {
1285
1193
  const row = await this.getCurrentRow()
1286
1194
  const env = await this.getCurrentEnvironment()
@@ -1411,7 +1319,6 @@ class PayPalModuleService extends MedusaService({
1411
1319
  return null
1412
1320
  }
1413
1321
 
1414
-
1415
1322
  async recordMetric(name: string, metadata?: Record<string, unknown>) {
1416
1323
  const existing = await this.listPayPalMetrics({ name })
1417
1324
  const row = existing?.[0]
@@ -1438,11 +1345,6 @@ class PayPalModuleService extends MedusaService({
1438
1345
  }
1439
1346
 
1440
1347
  async recordPaymentLog(eventType: string, metadata?: Record<string, unknown>) {
1441
- const payload = {
1442
- event_type: eventType,
1443
- metadata: metadata ?? {},
1444
- created_at: new Date().toISOString(),
1445
- }
1446
1348
  return await this.recordAuditEvent(`payment_${eventType}`, metadata)
1447
1349
  }
1448
1350