@easypayment/medusa-paypal 0.6.5 → 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.
@@ -319,7 +319,7 @@ function AdvancedCardPaymentsTab() {
319
319
  )
320
320
  ] }) });
321
321
  }
322
- function PayPalApplePayPage() {
322
+ function PayPalGooglePayPage() {
323
323
  return /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Navigate, { to: "/settings/paypal/connection", replace: true });
324
324
  }
325
325
  const config = adminSdk.defineRouteConfig({
@@ -916,7 +916,7 @@ function PayPalConnectionPage() {
916
916
  ` })
917
917
  ] });
918
918
  }
919
- function PayPalGooglePayPage() {
919
+ function PayPalApplePayPage() {
920
920
  return /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Navigate, { to: "/settings/paypal/connection", replace: true });
921
921
  }
922
922
  function PayPalPayLaterMessagingPage() {
@@ -1211,16 +1211,16 @@ const routeModule = {
1211
1211
  path: "/settings/paypal/advanced-card-payments"
1212
1212
  },
1213
1213
  {
1214
- Component: PayPalApplePayPage,
1215
- path: "/settings/paypal/apple-pay"
1214
+ Component: PayPalGooglePayPage,
1215
+ path: "/settings/paypal/google-pay"
1216
1216
  },
1217
1217
  {
1218
1218
  Component: PayPalConnectionPage,
1219
1219
  path: "/settings/paypal/connection"
1220
1220
  },
1221
1221
  {
1222
- Component: PayPalGooglePayPage,
1223
- path: "/settings/paypal/google-pay"
1222
+ Component: PayPalApplePayPage,
1223
+ path: "/settings/paypal/apple-pay"
1224
1224
  },
1225
1225
  {
1226
1226
  Component: PayPalPayLaterMessagingPage,
@@ -318,7 +318,7 @@ function AdvancedCardPaymentsTab() {
318
318
  )
319
319
  ] }) });
320
320
  }
321
- function PayPalApplePayPage() {
321
+ function PayPalGooglePayPage() {
322
322
  return /* @__PURE__ */ jsx(Navigate, { to: "/settings/paypal/connection", replace: true });
323
323
  }
324
324
  const config = defineRouteConfig({
@@ -915,7 +915,7 @@ function PayPalConnectionPage() {
915
915
  ` })
916
916
  ] });
917
917
  }
918
- function PayPalGooglePayPage() {
918
+ function PayPalApplePayPage() {
919
919
  return /* @__PURE__ */ jsx(Navigate, { to: "/settings/paypal/connection", replace: true });
920
920
  }
921
921
  function PayPalPayLaterMessagingPage() {
@@ -1210,16 +1210,16 @@ const routeModule = {
1210
1210
  path: "/settings/paypal/advanced-card-payments"
1211
1211
  },
1212
1212
  {
1213
- Component: PayPalApplePayPage,
1214
- path: "/settings/paypal/apple-pay"
1213
+ Component: PayPalGooglePayPage,
1214
+ path: "/settings/paypal/google-pay"
1215
1215
  },
1216
1216
  {
1217
1217
  Component: PayPalConnectionPage,
1218
1218
  path: "/settings/paypal/connection"
1219
1219
  },
1220
1220
  {
1221
- Component: PayPalGooglePayPage,
1222
- path: "/settings/paypal/google-pay"
1221
+ Component: PayPalApplePayPage,
1222
+ path: "/settings/paypal/apple-pay"
1223
1223
  },
1224
1224
  {
1225
1225
  Component: PayPalPayLaterMessagingPage,
@@ -23,10 +23,6 @@ declare class PayPalPaymentProvider extends AbstractPaymentProvider<Options> {
23
23
  private recordSuccess;
24
24
  private recordPaymentEvent;
25
25
  createAccountHolder(input: CreateAccountHolderInput): Promise<CreateAccountHolderOutput>;
26
- /**
27
- * Create a payment session when the customer selects PayPal.
28
- * Must return an object containing an `id` and `data`.
29
- */
30
26
  initiatePayment(input: InitiatePaymentInput): Promise<InitiatePaymentOutput>;
31
27
  updatePayment(input: UpdatePaymentInput): Promise<UpdatePaymentOutput>;
32
28
  authorizePayment(input: AuthorizePaymentInput): Promise<AuthorizePaymentOutput>;
@@ -36,10 +32,6 @@ declare class PayPalPaymentProvider extends AbstractPaymentProvider<Options> {
36
32
  refundPayment(input: RefundPaymentInput): Promise<RefundPaymentOutput>;
37
33
  cancelPayment(input: CancelPaymentInput): Promise<CancelPaymentOutput>;
38
34
  deletePayment(_input: DeletePaymentInput): Promise<DeletePaymentOutput>;
39
- /**
40
- * Required by AbstractPaymentProvider in Medusa v2.
41
- * This is used by /hooks/payment/{identifier}_{providerId}
42
- */
43
35
  getWebhookActionAndData(payload: ProviderWebhookPayload["payload"]): Promise<WebhookActionResult>;
44
36
  }
45
37
  export default PayPalPaymentProvider;
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../../../../src/modules/paypal/payment-provider/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAA;AAEnE,OAAO,KAAK,EACV,qBAAqB,EACrB,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,EACxB,yBAAyB,EACzB,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,EACrB,sBAAsB,EACtB,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,mBAAmB,EACnB,sBAAsB,EACtB,mBAAmB,EACpB,MAAM,2BAA2B,CAAA;AASlC,KAAK,OAAO,GAAG,EAAE,CAAA;AAWjB,cAAM,qBAAsB,SAAQ,uBAAuB,CAAC,OAAO,CAAC;IAClE,MAAM,CAAC,UAAU,SAAW;IAE5B,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;gBAExB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,OAAO;IAKzD,OAAO,CAAC,oBAAoB;IAWxB,eAAe;4BAWqD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;oBACvC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;YAmBjE,uBAAuB;YAQrB,oBAAoB;YAqDtB,eAAe;IAkB7B,OAAO,CAAC,iBAAiB;YAQX,oBAAoB;IAclC,OAAO,CAAC,gBAAgB;IAyBxB,OAAO,CAAC,sBAAsB;IAqB9B,OAAO,CAAC,cAAc;IAwBtB,OAAO,CAAC,cAAc;YA6BR,aAAa;YAeb,aAAa;YAab,kBAAkB;IAa1B,mBAAmB,CACvB,KAAK,EAAE,wBAAwB,GAC9B,OAAO,CAAC,yBAAyB,CAAC;IAarC;;;OAGG;IACG,eAAe,CACnB,KAAK,EAAE,oBAAoB,GAC1B,OAAO,CAAC,qBAAqB,CAAC;IA2C3B,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAsBtE,gBAAgB,CACpB,KAAK,EAAE,qBAAqB,GAC3B,OAAO,CAAC,sBAAsB,CAAC;IAmK5B,eAAe,CACnB,KAAK,EAAE,oBAAoB,GAC1B,OAAO,CAAC,qBAAqB,CAAC;IAyB3B,gBAAgB,CACpB,KAAK,EAAE,qBAAqB,GAC3B,OAAO,CAAC,sBAAsB,CAAC;IAwC5B,cAAc,CAClB,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,oBAAoB,CAAC;IAyK1B,aAAa,CACjB,KAAK,EAAE,kBAAkB,GACxB,OAAO,CAAC,mBAAmB,CAAC;IA2GzB,aAAa,CACjB,KAAK,EAAE,kBAAkB,GACxB,OAAO,CAAC,mBAAmB,CAAC;IAwHzB,aAAa,CACjB,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,mBAAmB,CAAC;IAI/B;;;OAGG;IACG,uBAAuB,CAC3B,OAAO,EAAE,sBAAsB,CAAC,SAAS,CAAC,GACzC,OAAO,CAAC,mBAAmB,CAAC;CAGhC;AAED,eAAe,qBAAqB,CAAA;AACpC,OAAO,EAAE,qBAAqB,EAAE,CAAA"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../../../../src/modules/paypal/payment-provider/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAA;AAEnE,OAAO,KAAK,EACV,qBAAqB,EACrB,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,EACxB,yBAAyB,EACzB,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,EACrB,sBAAsB,EACtB,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,mBAAmB,EACnB,sBAAsB,EACtB,mBAAmB,EACpB,MAAM,2BAA2B,CAAA;AASlC,KAAK,OAAO,GAAG,EAAE,CAAA;AAUjB,cAAM,qBAAsB,SAAQ,uBAAuB,CAAC,OAAO,CAAC;IAClE,MAAM,CAAC,UAAU,SAAW;IAE5B,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;gBAExB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,EAAE,OAAO;IAKzD,OAAO,CAAC,oBAAoB;IAWtB,eAAe;4BAW6C,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;oBACvC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;YAoB3D,uBAAuB;YAQvB,oBAAoB;YA0DpB,eAAe;IAkB7B,OAAO,CAAC,iBAAiB;YAWX,oBAAoB;IAclC,OAAO,CAAC,gBAAgB;IAUxB,OAAO,CAAC,sBAAsB;IAS9B,OAAO,CAAC,cAAc;IAiBtB,OAAO,CAAC,cAAc;YAWR,aAAa;YAYb,aAAa;YAUb,kBAAkB;IAU1B,mBAAmB,CACvB,KAAK,EAAE,wBAAwB,GAC9B,OAAO,CAAC,yBAAyB,CAAC;IAY/B,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAgC5E,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAoBtE,gBAAgB,CACpB,KAAK,EAAE,qBAAqB,GAC3B,OAAO,CAAC,sBAAsB,CAAC;IAmN5B,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAyB5E,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAwC/E,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAyKzE,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IA2GtE,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAiHtE,aAAa,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAIvE,uBAAuB,CAC3B,OAAO,EAAE,sBAAsB,CAAC,SAAS,CAAC,GACzC,OAAO,CAAC,mBAAmB,CAAC;CAGhC;AAED,eAAe,qBAAqB,CAAA;AACpC,OAAO,EAAE,qBAAqB,EAAE,CAAA"}
@@ -11,7 +11,6 @@ function generateSessionId() {
11
11
  return (0, crypto_1.randomUUID)();
12
12
  }
13
13
  catch {
14
- // Fallback for environments where randomUUID isn't available
15
14
  return `pp_${Date.now()}_${Math.random().toString(16).slice(2)}`;
16
15
  }
17
16
  }
@@ -72,10 +71,11 @@ class PayPalPaymentProvider extends utils_1.AbstractPaymentProvider {
72
71
  async getPayPalAccessToken() {
73
72
  const paypal = this.resolvePayPalService();
74
73
  if (!paypal) {
75
- // Fallback: load credentials directly from paypal_connection table
76
74
  const { Pool: _FbPool } = require("pg");
77
75
  const _fbPool = new _FbPool({ connectionString: process.env.DATABASE_URL });
78
- const _fbResult = await _fbPool.query("SELECT metadata, environment, seller_client_id, seller_client_secret FROM paypal_connection WHERE status='connected' ORDER BY created_at DESC LIMIT 1").finally(() => _fbPool.end());
76
+ const _fbResult = await _fbPool
77
+ .query("SELECT metadata, environment, seller_client_id, seller_client_secret FROM paypal_connection WHERE status='connected' ORDER BY created_at DESC LIMIT 1")
78
+ .finally(() => _fbPool.end());
79
79
  const _fbRow = _fbResult.rows[0];
80
80
  if (!_fbRow)
81
81
  throw new Error("No active PayPal connection found in DB");
@@ -87,7 +87,10 @@ class PayPalPaymentProvider extends utils_1.AbstractPaymentProvider {
87
87
  const _fbAuth = Buffer.from(`${_fbId}:${_fbSec}`).toString("base64");
88
88
  const _fbResp = await fetch(`${_fbBase}/v1/oauth2/token`, {
89
89
  method: "POST",
90
- headers: { Authorization: `Basic ${_fbAuth}`, "Content-Type": "application/x-www-form-urlencoded" },
90
+ headers: {
91
+ Authorization: `Basic ${_fbAuth}`,
92
+ "Content-Type": "application/x-www-form-urlencoded",
93
+ },
91
94
  body: "grant_type=client_credentials",
92
95
  });
93
96
  const _fbText = await _fbResp.text();
@@ -151,37 +154,28 @@ class PayPalPaymentProvider extends utils_1.AbstractPaymentProvider {
151
154
  }
152
155
  mapCaptureStatus(status) {
153
156
  const normalized = String(status || "").toUpperCase();
154
- if (!normalized) {
157
+ if (!normalized)
155
158
  return null;
156
- }
157
- if (normalized === "COMPLETED") {
159
+ if (normalized === "COMPLETED")
158
160
  return "captured";
159
- }
160
- if (normalized === "PENDING") {
161
+ if (normalized === "PENDING")
161
162
  return "pending";
162
- }
163
- if (["DENIED", "DECLINED", "FAILED"].includes(normalized)) {
163
+ if (["DENIED", "DECLINED", "FAILED"].includes(normalized))
164
164
  return "error";
165
- }
166
- if (["REFUNDED", "PARTIALLY_REFUNDED", "REVERSED"].includes(normalized)) {
165
+ if (["REFUNDED", "PARTIALLY_REFUNDED", "REVERSED"].includes(normalized))
167
166
  return "canceled";
168
- }
169
167
  return null;
170
168
  }
171
169
  mapAuthorizationStatus(status) {
172
170
  const normalized = String(status || "").toUpperCase();
173
- if (!normalized) {
171
+ if (!normalized)
174
172
  return null;
175
- }
176
- if (["CREATED", "APPROVED", "PENDING"].includes(normalized)) {
173
+ if (["CREATED", "APPROVED", "PENDING"].includes(normalized))
177
174
  return "authorized";
178
- }
179
- if (["VOIDED", "EXPIRED"].includes(normalized)) {
175
+ if (["VOIDED", "EXPIRED"].includes(normalized))
180
176
  return "canceled";
181
- }
182
- if (["DENIED", "DECLINED", "FAILED"].includes(normalized)) {
177
+ if (["DENIED", "DECLINED", "FAILED"].includes(normalized))
183
178
  return "error";
184
- }
185
179
  return null;
186
180
  }
187
181
  serializeError(error) {
@@ -193,76 +187,61 @@ class PayPalPaymentProvider extends utils_1.AbstractPaymentProvider {
193
187
  message: error.message,
194
188
  stack: error.stack,
195
189
  cause: cause instanceof Error
196
- ? {
197
- name: cause.name,
198
- message: cause.message,
199
- stack: cause.stack,
200
- }
190
+ ? { name: cause.name, message: cause.message, stack: cause.stack }
201
191
  : cause,
202
192
  };
203
193
  }
204
- return {
205
- message: String(error),
206
- };
194
+ return { message: String(error) };
207
195
  }
208
196
  mapOrderStatus(status) {
209
197
  const normalized = String(status || "").toUpperCase();
210
- if (!normalized) {
198
+ if (!normalized)
211
199
  return "pending";
212
- }
213
- if (normalized === "COMPLETED") {
200
+ if (normalized === "COMPLETED")
214
201
  return "captured";
215
- }
216
- if (normalized === "APPROVED") {
202
+ if (normalized === "APPROVED")
217
203
  return "authorized";
218
- }
219
- if (["VOIDED", "CANCELLED"].includes(normalized)) {
204
+ if (["VOIDED", "CANCELLED"].includes(normalized))
220
205
  return "canceled";
221
- }
222
- if (["CREATED", "SAVED", "PAYER_ACTION_REQUIRED"].includes(normalized)) {
206
+ if (["CREATED", "SAVED", "PAYER_ACTION_REQUIRED"].includes(normalized))
223
207
  return "pending";
224
- }
225
- if (["FAILED", "EXPIRED"].includes(normalized)) {
208
+ if (["FAILED", "EXPIRED"].includes(normalized))
226
209
  return "error";
227
- }
228
210
  return "pending";
229
211
  }
230
212
  async recordFailure(eventType, metadata) {
231
213
  const paypal = this.resolvePayPalService();
232
- if (!paypal) {
214
+ if (!paypal)
233
215
  return;
234
- }
235
216
  try {
236
217
  await paypal.recordPaymentLog(eventType, metadata);
237
218
  await paypal.recordAuditEvent(eventType, metadata);
238
219
  await paypal.recordMetric(eventType);
239
220
  }
240
221
  catch {
241
- // ignore audit logging failures
222
+ // ignore
242
223
  }
243
224
  }
244
225
  async recordSuccess(metricName) {
245
226
  const paypal = this.resolvePayPalService();
246
- if (!paypal) {
227
+ if (!paypal)
247
228
  return;
248
- }
249
229
  try {
250
230
  await paypal.recordMetric(metricName);
251
231
  }
252
232
  catch {
253
- // ignore metrics failures
233
+ // ignore
254
234
  }
255
235
  }
256
236
  async recordPaymentEvent(eventType, metadata) {
257
237
  const paypal = this.resolvePayPalService();
258
- if (!paypal) {
238
+ if (!paypal)
259
239
  return;
260
- }
261
240
  try {
262
241
  await paypal.recordPaymentLog(eventType, metadata);
263
242
  }
264
243
  catch {
265
- // ignore payment logging failures
244
+ // ignore
266
245
  }
267
246
  }
268
247
  async createAccountHolder(input) {
@@ -276,10 +255,6 @@ class PayPalPaymentProvider extends utils_1.AbstractPaymentProvider {
276
255
  },
277
256
  };
278
257
  }
279
- /**
280
- * Create a payment session when the customer selects PayPal.
281
- * Must return an object containing an `id` and `data`.
282
- */
283
258
  async initiatePayment(input) {
284
259
  const providerId = input.data?.provider_id;
285
260
  try {
@@ -300,15 +275,6 @@ class PayPalPaymentProvider extends utils_1.AbstractPaymentProvider {
300
275
  };
301
276
  }
302
277
  catch (error) {
303
- console.error("[PayPal] provider initiate failed", {
304
- provider_id: providerId,
305
- payment_collection_id: input.data
306
- ?.payment_collection_id,
307
- cart_id: input.data?.cart_id,
308
- amount: input.amount,
309
- currency_code: input.currency_code,
310
- error: this.serializeError(error),
311
- });
312
278
  await this.recordFailure("initiate_failed", {
313
279
  error: this.serializeError(error),
314
280
  currency_code: input.currency_code,
@@ -339,83 +305,128 @@ class PayPalPaymentProvider extends utils_1.AbstractPaymentProvider {
339
305
  async authorizePayment(input) {
340
306
  const { data, amount, currencyCode } = await this.normalizePaymentData(input);
341
307
  const existingPayPal = (data.paypal || {});
308
+ const { additionalSettings } = await this.resolveSettings();
309
+ const paymentActionRaw = typeof additionalSettings.paymentAction === "string"
310
+ ? additionalSettings.paymentAction
311
+ : "capture";
312
+ const returnStatus = paymentActionRaw === "authorize" ? "authorized" : "captured";
313
+ const timestampKey = paymentActionRaw === "authorize" ? "authorized_at" : "captured_at";
314
+ // ── CASE 1: Already processed (has capture_id, authorization_id, or timestamp) ──
342
315
  if (existingPayPal.capture_id ||
343
316
  existingPayPal.authorization_id ||
344
317
  data.authorized_at ||
345
318
  data.captured_at) {
346
- const { additionalSettings } = await this.resolveSettings();
347
- const paymentAction = typeof additionalSettings.paymentAction === "string"
348
- ? additionalSettings.paymentAction
349
- : "capture";
350
- const returnStatus = paymentAction === "authorize" ? "authorized" : "captured";
319
+ console.info("[PayPal] authorizePayment: session already processed, returning", returnStatus);
351
320
  return {
352
321
  status: returnStatus,
353
322
  data: {
354
323
  ...(input.data || {}),
355
- ...(paymentAction === "authorize"
356
- ? { authorized_at: new Date().toISOString() }
357
- : { captured_at: new Date().toISOString() }),
324
+ [timestampKey]: data[timestampKey] || new Date().toISOString(),
358
325
  },
359
326
  };
360
327
  }
328
+ // ── CASE 2: Has order_id — fetch current status from PayPal ──────────────
329
+ // This is the KEY fix: capture-order already processed the payment but
330
+ // Medusa's cart/complete workflow calls authorizePayment again with the
331
+ // original session data (before capture-order wrote authorization_id).
332
+ // We fetch the live PayPal order to get the real status.
333
+ const orderId = String(existingPayPal.order_id || data.order_id || "");
334
+ if (orderId) {
335
+ try {
336
+ console.info("[PayPal] authorizePayment: fetching live order status for", orderId);
337
+ const order = await this.getOrderDetails(orderId);
338
+ const capture = order?.purchase_units?.[0]?.payments?.captures?.[0];
339
+ const authorization = order?.purchase_units?.[0]?.payments?.authorizations?.[0];
340
+ // Order was already captured or authorized by capture-order route
341
+ if (capture?.id || authorization?.id) {
342
+ console.info("[PayPal] authorizePayment: order already processed by PayPal, returning", returnStatus);
343
+ return {
344
+ status: returnStatus,
345
+ data: {
346
+ ...(input.data || {}),
347
+ paypal: {
348
+ ...existingPayPal,
349
+ order_id: orderId,
350
+ order,
351
+ authorization_id: authorization?.id || existingPayPal.authorization_id,
352
+ capture_id: capture?.id || existingPayPal.capture_id,
353
+ },
354
+ [timestampKey]: new Date().toISOString(),
355
+ },
356
+ };
357
+ }
358
+ // Order exists and is APPROVED — customer completed PayPal flow
359
+ // but capture-order hasn't run yet (edge case). Mark as authorized.
360
+ if (["APPROVED", "CREATED", "SAVED"].includes(String(order?.status || "").toUpperCase())) {
361
+ console.info("[PayPal] authorizePayment: order approved, marking authorized");
362
+ return {
363
+ status: "authorized",
364
+ data: {
365
+ ...(input.data || {}),
366
+ paypal: {
367
+ ...existingPayPal,
368
+ order_id: orderId,
369
+ order,
370
+ },
371
+ authorized_at: new Date().toISOString(),
372
+ },
373
+ };
374
+ }
375
+ }
376
+ catch (e) {
377
+ // Non-fatal — log and fall through to creating a new order below
378
+ console.warn("[PayPal] authorizePayment: order lookup failed:", e?.message);
379
+ }
380
+ }
381
+ // ── CASE 3: No order_id — create a new PayPal order ──────────────────────
382
+ // This handles the rare case where Medusa calls authorizePayment before
383
+ // the frontend has created a PayPal order (e.g. admin-created orders).
361
384
  const requestId = this.getIdempotencyKey(input, "authorize");
362
385
  let debugId = null;
363
- const { additionalSettings } = await this.resolveSettings();
364
- const paymentActionRaw = typeof additionalSettings.paymentAction === "string"
365
- ? additionalSettings.paymentAction
366
- : "capture";
367
386
  const orderIntent = paymentActionRaw === "authorize" ? "AUTHORIZE" : "CAPTURE";
368
387
  try {
369
388
  const { accessToken, base } = await this.getPayPalAccessToken();
370
- const existingPayPal = (data.paypal || {});
371
- let orderId = String(existingPayPal.order_id || data.order_id || "");
372
- let order = null;
373
- let authorization = null;
374
- if (!orderId) {
375
- const value = (0, amounts_1.formatAmountForPayPal)(amount, currencyCode || "EUR");
376
- const orderPayload = {
377
- intent: orderIntent,
378
- purchase_units: [
379
- {
380
- reference_id: data.cart_id || data.payment_collection_id || undefined,
381
- custom_id: data.session_id || data.cart_id || data.payment_collection_id || undefined,
382
- amount: {
383
- currency_code: currencyCode || "EUR",
384
- value,
385
- },
389
+ const value = (0, amounts_1.formatAmountForPayPal)(amount, currencyCode || "EUR");
390
+ const orderPayload = {
391
+ intent: orderIntent,
392
+ purchase_units: [
393
+ {
394
+ reference_id: data.cart_id || data.payment_collection_id || undefined,
395
+ custom_id: data.session_id || data.cart_id || data.payment_collection_id || undefined,
396
+ amount: {
397
+ currency_code: currencyCode || "EUR",
398
+ value,
386
399
  },
387
- ],
388
- custom_id: data.session_id || data.cart_id || data.payment_collection_id || undefined,
389
- };
390
- const ppResp = await fetch(`${base}/v2/checkout/orders`, {
391
- method: "POST",
392
- headers: {
393
- Authorization: `Bearer ${accessToken}`,
394
- "Content-Type": "application/json",
395
- "PayPal-Request-Id": requestId,
396
400
  },
397
- body: JSON.stringify(orderPayload),
398
- });
399
- const ppText = await ppResp.text();
400
- debugId = ppResp.headers.get("paypal-debug-id");
401
- if (!ppResp.ok) {
402
- throw new Error(`PayPal create order error (${ppResp.status}): ${ppText}${debugId ? ` debug_id=${debugId}` : ""}`);
403
- }
404
- order = JSON.parse(ppText);
405
- orderId = String(order.id || "");
406
- }
407
- else {
408
- order = (await this.getOrderDetails(orderId));
401
+ ],
402
+ custom_id: data.session_id || data.cart_id || data.payment_collection_id || undefined,
403
+ };
404
+ const ppResp = await fetch(`${base}/v2/checkout/orders`, {
405
+ method: "POST",
406
+ headers: {
407
+ Authorization: `Bearer ${accessToken}`,
408
+ "Content-Type": "application/json",
409
+ "PayPal-Request-Id": requestId,
410
+ },
411
+ body: JSON.stringify(orderPayload),
412
+ });
413
+ const ppText = await ppResp.text();
414
+ debugId = ppResp.headers.get("paypal-debug-id");
415
+ if (!ppResp.ok) {
416
+ throw new Error(`PayPal create order error (${ppResp.status}): ${ppText}${debugId ? ` debug_id=${debugId}` : ""}`);
409
417
  }
410
- if (!order || !orderId) {
418
+ const order = JSON.parse(ppText);
419
+ const newOrderId = String(order.id || "");
420
+ if (!order || !newOrderId) {
411
421
  throw new Error("Unable to resolve PayPal order details for authorization.");
412
422
  }
413
423
  const existingAuthorization = order?.purchase_units?.[0]?.payments?.authorizations?.[0] || null;
424
+ let authorization = null;
414
425
  if (existingAuthorization) {
415
426
  authorization = order;
416
427
  }
417
428
  else {
418
- const authorizeResp = await fetch(`${base}/v2/checkout/orders/${orderId}/authorize`, {
429
+ const authorizeResp = await fetch(`${base}/v2/checkout/orders/${newOrderId}/authorize`, {
419
430
  method: "POST",
420
431
  headers: {
421
432
  Authorization: `Bearer ${accessToken}`,
@@ -434,7 +445,7 @@ class PayPalPaymentProvider extends utils_1.AbstractPaymentProvider {
434
445
  existingAuthorization?.id;
435
446
  await this.recordSuccess("authorize_success");
436
447
  await this.recordPaymentEvent("authorize", {
437
- order_id: orderId,
448
+ order_id: newOrderId,
438
449
  authorization_id: authorizationId,
439
450
  amount,
440
451
  currency_code: currencyCode,
@@ -446,7 +457,7 @@ class PayPalPaymentProvider extends utils_1.AbstractPaymentProvider {
446
457
  ...(input.data || {}),
447
458
  paypal: {
448
459
  ...(input.data || {}).paypal,
449
- order_id: orderId,
460
+ order_id: newOrderId,
450
461
  order: order || authorization,
451
462
  authorization_id: authorizationId,
452
463
  authorizations: authorization?.purchase_units?.[0]?.payments?.authorizations || [],
@@ -623,9 +634,7 @@ class PayPalPaymentProvider extends utils_1.AbstractPaymentProvider {
623
634
  }
624
635
  const capture = JSON.parse(ppText);
625
636
  const captureId = capture?.id || capture?.purchase_units?.[0]?.payments?.captures?.[0]?.id;
626
- const existingCaptures = Array.isArray(paypalData.captures)
627
- ? paypalData.captures
628
- : [];
637
+ const existingCaptures = Array.isArray(paypalData.captures) ? paypalData.captures : [];
629
638
  const captureEntry = {
630
639
  id: captureId,
631
640
  status: capture?.status,
@@ -788,7 +797,6 @@ class PayPalPaymentProvider extends utils_1.AbstractPaymentProvider {
788
797
  await this.recordPaymentEvent("void", {
789
798
  order_id: orderId,
790
799
  authorization_id: authorizationId,
791
- request_id: requestId,
792
800
  });
793
801
  }
794
802
  else if (captureId) {
@@ -820,12 +828,6 @@ class PayPalPaymentProvider extends utils_1.AbstractPaymentProvider {
820
828
  paypalData.refund_status = refund?.status;
821
829
  paypalData.refunds = [...existingRefunds, refundEntry];
822
830
  await this.recordSuccess("cancel_refund_success");
823
- await this.recordPaymentEvent("cancel_refund", {
824
- order_id: orderId,
825
- capture_id: captureId,
826
- refund_id: refund?.id,
827
- request_id: requestId,
828
- });
829
831
  }
830
832
  return {
831
833
  data: {
@@ -856,10 +858,6 @@ class PayPalPaymentProvider extends utils_1.AbstractPaymentProvider {
856
858
  async deletePayment(_input) {
857
859
  return { data: {} };
858
860
  }
859
- /**
860
- * Required by AbstractPaymentProvider in Medusa v2.
861
- * This is used by /hooks/payment/{identifier}_{providerId}
862
- */
863
861
  async getWebhookActionAndData(payload) {
864
862
  return (0, webhook_utils_1.getPayPalWebhookActionAndData)(payload);
865
863
  }