@easypayment/medusa-paypal-ui 1.2.0 → 1.2.3
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/CHANGELOG.md +7 -0
- package/dist/index.cjs +197 -88
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +197 -88
- package/dist/index.mjs.map +1 -1
- package/dist/order.cjs.map +1 -1
- package/dist/order.mjs.map +1 -1
- package/package.json +1 -1
- package/src/adapters/MedusaNextPayPalAdapter.tsx +196 -195
- package/src/client/http.ts +214 -204
- package/src/client/paypal.ts +91 -88
- package/src/client/types.ts +40 -40
- package/src/components/PayPalAdvancedCard.tsx +646 -611
- package/src/components/PayPalCurrencyNotice.tsx +24 -19
- package/src/components/PayPalPaymentSection.tsx +5 -1
- package/src/components/PayPalProvider.tsx +44 -38
- package/src/components/PayPalSmartButtons.tsx +489 -445
- package/src/hooks/usePayPalConfig.ts +100 -96
- package/src/hooks/usePayPalPaymentMethods.ts +146 -145
- package/src/index.ts +19 -16
- package/src/order.ts +140 -134
- package/src/utils/next-errors.ts +21 -21
- package/src/utils/processing-overlay.ts +96 -96
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@easypayment/medusa-paypal-ui` are documented here.
|
|
4
4
|
|
|
5
|
+
## 1.2.3 — 2026-09-21
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- Code formatting only: applied the repo's Prettier style (no semicolons,
|
|
9
|
+
double quotes, 80-column width) across `src/` and added `.prettierrc` so the
|
|
10
|
+
package shares the plugin's formatting config. No behavior changes.
|
|
11
|
+
|
|
5
12
|
## 1.2.0 — 2026-09-02
|
|
6
13
|
|
|
7
14
|
### Fixed
|
package/dist/index.cjs
CHANGED
|
@@ -125,7 +125,9 @@ function createHttpClient(opts) {
|
|
|
125
125
|
text = await res.text().catch(() => "");
|
|
126
126
|
} catch (err) {
|
|
127
127
|
if (err instanceof Error && err.name === "AbortError" && !(callerSignal && callerSignal.aborted)) {
|
|
128
|
-
throw new Error(
|
|
128
|
+
throw new Error(
|
|
129
|
+
`[PayPal] Request to ${path} timed out after ${timeoutMs / 1e3}s`
|
|
130
|
+
);
|
|
129
131
|
}
|
|
130
132
|
throw err;
|
|
131
133
|
} finally {
|
|
@@ -152,7 +154,10 @@ function createHttpClient(opts) {
|
|
|
152
154
|
);
|
|
153
155
|
}
|
|
154
156
|
if (!text) {
|
|
155
|
-
throw new HttpError(
|
|
157
|
+
throw new HttpError(
|
|
158
|
+
`[PayPal] Empty response body from ${path} (${res.status})`,
|
|
159
|
+
res.status
|
|
160
|
+
);
|
|
156
161
|
}
|
|
157
162
|
const contentType = res.headers.get("content-type") || "";
|
|
158
163
|
if (!contentType.includes("application/json")) {
|
|
@@ -179,7 +184,9 @@ function createHttpClient(opts) {
|
|
|
179
184
|
if (i >= retries || !transient || aborted) {
|
|
180
185
|
throw err;
|
|
181
186
|
}
|
|
182
|
-
await wait(
|
|
187
|
+
await wait(
|
|
188
|
+
RETRY_BASE_DELAY_MS * Math.pow(2, i) + Math.random() * RETRY_BASE_DELAY_MS
|
|
189
|
+
);
|
|
183
190
|
}
|
|
184
191
|
}
|
|
185
192
|
throw lastError;
|
|
@@ -228,7 +235,10 @@ function createPayPalStoreApi(opts) {
|
|
|
228
235
|
// per-order-id fallback is the correct idempotency scope.
|
|
229
236
|
"Idempotency-Key": generateIdempotencyKey()
|
|
230
237
|
},
|
|
231
|
-
body: JSON.stringify({
|
|
238
|
+
body: JSON.stringify({
|
|
239
|
+
cart_id: cartId,
|
|
240
|
+
is_card_payment: isCardPayment
|
|
241
|
+
})
|
|
232
242
|
});
|
|
233
243
|
},
|
|
234
244
|
captureOrder(cartId, orderId) {
|
|
@@ -274,7 +284,9 @@ function usePayPalConfig({
|
|
|
274
284
|
const hit = _cache.get(key);
|
|
275
285
|
const seedConfig = hit && Date.now() - hit.at < CACHE_TTL ? hit.config : null;
|
|
276
286
|
const [config, setConfig] = (0, import_react.useState)(seedConfig);
|
|
277
|
-
const [loading, setLoading] = (0, import_react.useState)(
|
|
287
|
+
const [loading, setLoading] = (0, import_react.useState)(
|
|
288
|
+
seedConfig === null && enabled
|
|
289
|
+
);
|
|
278
290
|
const [error, setError] = (0, import_react.useState)(null);
|
|
279
291
|
const fetchIdRef = (0, import_react.useRef)(0);
|
|
280
292
|
(0, import_react.useEffect)(() => {
|
|
@@ -305,7 +317,9 @@ function usePayPalConfig({
|
|
|
305
317
|
} catch (e) {
|
|
306
318
|
if (e instanceof Error && e.name === "AbortError") return;
|
|
307
319
|
if (!mounted || id !== fetchIdRef.current) return;
|
|
308
|
-
setError(
|
|
320
|
+
setError(
|
|
321
|
+
e instanceof Error ? e.message : "Failed to load PayPal config"
|
|
322
|
+
);
|
|
309
323
|
} finally {
|
|
310
324
|
if (mounted && id === fetchIdRef.current) setLoading(false);
|
|
311
325
|
}
|
|
@@ -335,7 +349,13 @@ function PayPalProvider(props) {
|
|
|
335
349
|
"disable-funding": disableFunding,
|
|
336
350
|
"data-partner-attribution-id": BN_CODE
|
|
337
351
|
};
|
|
338
|
-
}, [
|
|
352
|
+
}, [
|
|
353
|
+
config.client_id,
|
|
354
|
+
config.currency,
|
|
355
|
+
config.client_token,
|
|
356
|
+
intent,
|
|
357
|
+
disableFunding
|
|
358
|
+
]);
|
|
339
359
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
340
360
|
import_react_paypal_js.PayPalScriptProvider,
|
|
341
361
|
{
|
|
@@ -350,10 +370,17 @@ function PayPalProvider(props) {
|
|
|
350
370
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
351
371
|
function PayPalCurrencyNotice({ config }) {
|
|
352
372
|
if (config.currency_supported) return null;
|
|
353
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
373
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
374
|
+
"div",
|
|
375
|
+
{
|
|
376
|
+
role: "alert",
|
|
377
|
+
style: { padding: 12, border: "1px solid #ddd", borderRadius: 10 },
|
|
378
|
+
children: [
|
|
379
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontWeight: 600, marginBottom: 6 }, children: "PayPal currency issue" }),
|
|
380
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("ul", { style: { margin: 0, paddingLeft: 18 }, children: (config.currency_errors || []).map((e) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("li", { children: e }, e)) })
|
|
381
|
+
]
|
|
382
|
+
}
|
|
383
|
+
);
|
|
357
384
|
}
|
|
358
385
|
|
|
359
386
|
// src/components/PayPalSmartButtons.tsx
|
|
@@ -529,7 +556,11 @@ function PayPalSmartButtons(props) {
|
|
|
529
556
|
showProcessingOverlay();
|
|
530
557
|
setError(null);
|
|
531
558
|
try {
|
|
532
|
-
const completeResult = await markPaymentComplete(
|
|
559
|
+
const completeResult = await markPaymentComplete(
|
|
560
|
+
baseUrl,
|
|
561
|
+
cartId,
|
|
562
|
+
publishableApiKey
|
|
563
|
+
);
|
|
533
564
|
clearCartCaptured(cartId);
|
|
534
565
|
await onPaid?.({ ...captured, ...completeResult });
|
|
535
566
|
setCompletionPending(false);
|
|
@@ -612,12 +643,34 @@ function PayPalSmartButtons(props) {
|
|
|
612
643
|
)
|
|
613
644
|
] }),
|
|
614
645
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { textAlign: "center" }, children: [
|
|
615
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
646
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
647
|
+
"div",
|
|
648
|
+
{
|
|
649
|
+
style: {
|
|
650
|
+
fontSize: 18,
|
|
651
|
+
fontWeight: 600,
|
|
652
|
+
color: "#111827",
|
|
653
|
+
letterSpacing: "-0.01em"
|
|
654
|
+
},
|
|
655
|
+
children: "Processing your payment"
|
|
656
|
+
}
|
|
657
|
+
),
|
|
658
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
659
|
+
"div",
|
|
660
|
+
{
|
|
661
|
+
style: {
|
|
662
|
+
fontSize: 14,
|
|
663
|
+
color: "#6b7280",
|
|
664
|
+
marginTop: 6,
|
|
665
|
+
lineHeight: 1.5
|
|
666
|
+
},
|
|
667
|
+
children: [
|
|
668
|
+
"This may take a few moments. Please do not close",
|
|
669
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("br", {}),
|
|
670
|
+
"or refresh this page."
|
|
671
|
+
]
|
|
672
|
+
}
|
|
673
|
+
)
|
|
621
674
|
] }),
|
|
622
675
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
623
676
|
"div",
|
|
@@ -764,68 +817,84 @@ function PayPalSmartButtons(props) {
|
|
|
764
817
|
]
|
|
765
818
|
}
|
|
766
819
|
),
|
|
767
|
-
isResolved && !completionPending && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
768
|
-
|
|
820
|
+
isResolved && !completionPending && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
821
|
+
"div",
|
|
769
822
|
{
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
shape: config.button_shape,
|
|
776
|
-
label: config.button_label,
|
|
777
|
-
height: config.button_height
|
|
778
|
-
},
|
|
779
|
-
createOrder: async () => {
|
|
780
|
-
if (processing) throw new Error("Payment already processing");
|
|
781
|
-
if (capturedRef.current) {
|
|
782
|
-
void finalizeCapturedPayment();
|
|
783
|
-
throw new Error("Your payment was already received \u2014 finalizing your order.");
|
|
784
|
-
}
|
|
785
|
-
setError(null);
|
|
786
|
-
setProcessing(true);
|
|
787
|
-
try {
|
|
788
|
-
const r = await api.createOrder(cartId);
|
|
789
|
-
return r.id;
|
|
790
|
-
} catch (e) {
|
|
791
|
-
setProcessing(false);
|
|
792
|
-
throw e;
|
|
793
|
-
}
|
|
823
|
+
style: buttonsReady ? void 0 : {
|
|
824
|
+
position: "absolute",
|
|
825
|
+
left: -9999,
|
|
826
|
+
opacity: 0,
|
|
827
|
+
pointerEvents: "none"
|
|
794
828
|
},
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
829
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
830
|
+
import_react_paypal_js2.PayPalButtons,
|
|
831
|
+
{
|
|
832
|
+
forceReRender: [config.currency, config.intent, cartId],
|
|
833
|
+
onInit: handleInit,
|
|
834
|
+
style: {
|
|
835
|
+
layout: "vertical",
|
|
836
|
+
color: config.button_color,
|
|
837
|
+
shape: config.button_shape,
|
|
838
|
+
label: config.button_label,
|
|
839
|
+
height: config.button_height
|
|
840
|
+
},
|
|
841
|
+
createOrder: async () => {
|
|
842
|
+
if (processing) throw new Error("Payment already processing");
|
|
843
|
+
if (capturedRef.current) {
|
|
844
|
+
void finalizeCapturedPayment();
|
|
845
|
+
throw new Error(
|
|
846
|
+
"Your payment was already received \u2014 finalizing your order."
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
setError(null);
|
|
850
|
+
setProcessing(true);
|
|
851
|
+
try {
|
|
852
|
+
const r = await api.createOrder(cartId);
|
|
853
|
+
return r.id;
|
|
854
|
+
} catch (e) {
|
|
855
|
+
setProcessing(false);
|
|
856
|
+
throw e;
|
|
857
|
+
}
|
|
858
|
+
},
|
|
859
|
+
onApprove: async (data) => {
|
|
860
|
+
try {
|
|
861
|
+
setProcessing(true);
|
|
862
|
+
showProcessingOverlay();
|
|
863
|
+
setError(null);
|
|
864
|
+
const orderId = String(data?.orderID || "");
|
|
865
|
+
if (!orderId)
|
|
866
|
+
throw new Error(
|
|
867
|
+
"PayPal order ID is missing from approval response"
|
|
868
|
+
);
|
|
869
|
+
const result = await api.captureOrder(cartId, orderId);
|
|
870
|
+
capturedRef.current = result || {};
|
|
871
|
+
markCartCaptured(cartId);
|
|
872
|
+
} catch (e) {
|
|
873
|
+
if (isNextRouterError(e)) return;
|
|
874
|
+
hideProcessingOverlay();
|
|
875
|
+
setProcessing(false);
|
|
876
|
+
const msg = e instanceof Error ? e.message : "Payment capture failed";
|
|
877
|
+
setError(msg);
|
|
878
|
+
onError?.(msg);
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
await finalizeCapturedPayment();
|
|
882
|
+
},
|
|
883
|
+
onCancel: () => {
|
|
884
|
+
hideProcessingOverlay();
|
|
885
|
+
setProcessing(false);
|
|
886
|
+
},
|
|
887
|
+
onError: (err) => {
|
|
888
|
+
hideProcessingOverlay();
|
|
889
|
+
setProcessing(false);
|
|
890
|
+
const msg = err instanceof Error ? err.message : err?.message || "PayPal error";
|
|
891
|
+
setError(msg);
|
|
892
|
+
onError?.(msg);
|
|
893
|
+
}
|
|
813
894
|
}
|
|
814
|
-
|
|
815
|
-
},
|
|
816
|
-
onCancel: () => {
|
|
817
|
-
hideProcessingOverlay();
|
|
818
|
-
setProcessing(false);
|
|
819
|
-
},
|
|
820
|
-
onError: (err) => {
|
|
821
|
-
hideProcessingOverlay();
|
|
822
|
-
setProcessing(false);
|
|
823
|
-
const msg = err instanceof Error ? err.message : err?.message || "PayPal error";
|
|
824
|
-
setError(msg);
|
|
825
|
-
onError?.(msg);
|
|
826
|
-
}
|
|
895
|
+
)
|
|
827
896
|
}
|
|
828
|
-
)
|
|
897
|
+
),
|
|
829
898
|
completionPending && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
830
899
|
"button",
|
|
831
900
|
{
|
|
@@ -1025,7 +1094,11 @@ function PayPalAdvancedCard(props) {
|
|
|
1025
1094
|
showProcessingOverlay();
|
|
1026
1095
|
setError(null);
|
|
1027
1096
|
try {
|
|
1028
|
-
const completeResult = await markPaymentComplete(
|
|
1097
|
+
const completeResult = await markPaymentComplete(
|
|
1098
|
+
baseUrl,
|
|
1099
|
+
cartId,
|
|
1100
|
+
publishableApiKey
|
|
1101
|
+
);
|
|
1029
1102
|
clearCartCaptured(cartId);
|
|
1030
1103
|
await onPaid?.({ ...captured, ...completeResult });
|
|
1031
1104
|
setCompletionPending(false);
|
|
@@ -1185,12 +1258,34 @@ function PayPalAdvancedCard(props) {
|
|
|
1185
1258
|
)
|
|
1186
1259
|
] }),
|
|
1187
1260
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { textAlign: "center" }, children: [
|
|
1188
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1261
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1262
|
+
"div",
|
|
1263
|
+
{
|
|
1264
|
+
style: {
|
|
1265
|
+
fontSize: 18,
|
|
1266
|
+
fontWeight: 600,
|
|
1267
|
+
color: "#111827",
|
|
1268
|
+
letterSpacing: "-0.01em"
|
|
1269
|
+
},
|
|
1270
|
+
children: "Processing your payment"
|
|
1271
|
+
}
|
|
1272
|
+
),
|
|
1273
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1274
|
+
"div",
|
|
1275
|
+
{
|
|
1276
|
+
style: {
|
|
1277
|
+
fontSize: 14,
|
|
1278
|
+
color: "#6b7280",
|
|
1279
|
+
marginTop: 6,
|
|
1280
|
+
lineHeight: 1.5
|
|
1281
|
+
},
|
|
1282
|
+
children: [
|
|
1283
|
+
"This may take a few moments. Please do not close",
|
|
1284
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("br", {}),
|
|
1285
|
+
"or refresh this page."
|
|
1286
|
+
]
|
|
1287
|
+
}
|
|
1288
|
+
)
|
|
1194
1289
|
] }),
|
|
1195
1290
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1196
1291
|
"div",
|
|
@@ -1225,10 +1320,13 @@ function PayPalAdvancedCard(props) {
|
|
|
1225
1320
|
{
|
|
1226
1321
|
style: cardStyle,
|
|
1227
1322
|
createOrder: async () => {
|
|
1228
|
-
if (creatingOrderRef.current)
|
|
1323
|
+
if (creatingOrderRef.current)
|
|
1324
|
+
throw new Error("Payment already processing");
|
|
1229
1325
|
if (capturedRef.current) {
|
|
1230
1326
|
void finalizeCapturedPayment();
|
|
1231
|
-
throw new Error(
|
|
1327
|
+
throw new Error(
|
|
1328
|
+
"Your payment was already received \u2014 finalizing your order."
|
|
1329
|
+
);
|
|
1232
1330
|
}
|
|
1233
1331
|
creatingOrderRef.current = true;
|
|
1234
1332
|
try {
|
|
@@ -1245,7 +1343,10 @@ function PayPalAdvancedCard(props) {
|
|
|
1245
1343
|
setError(null);
|
|
1246
1344
|
showProcessingOverlay();
|
|
1247
1345
|
const orderId = String(data?.orderID || "");
|
|
1248
|
-
if (!orderId)
|
|
1346
|
+
if (!orderId)
|
|
1347
|
+
throw new Error(
|
|
1348
|
+
"PayPal order ID is missing from approval response"
|
|
1349
|
+
);
|
|
1249
1350
|
const result = await api.captureOrder(cartId, orderId);
|
|
1250
1351
|
capturedRef.current = result || {};
|
|
1251
1352
|
markCartCaptured(cartId);
|
|
@@ -1304,7 +1405,13 @@ function PayPalAdvancedCard(props) {
|
|
|
1304
1405
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { children: [
|
|
1305
1406
|
"Sandbox mode \u2014 use test card",
|
|
1306
1407
|
" ",
|
|
1307
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1408
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1409
|
+
"strong",
|
|
1410
|
+
{
|
|
1411
|
+
style: { fontFamily: "monospace", letterSpacing: "0.05em" },
|
|
1412
|
+
children: "4111 1111 1111 1111"
|
|
1413
|
+
}
|
|
1414
|
+
),
|
|
1308
1415
|
" ",
|
|
1309
1416
|
"with any future date and any CVV."
|
|
1310
1417
|
] })
|
|
@@ -1706,7 +1813,9 @@ function usePayPalPaymentMethods({
|
|
|
1706
1813
|
const key = cacheKey2(baseUrl, cartId);
|
|
1707
1814
|
const hit = _cache2.get(key);
|
|
1708
1815
|
const seed = hit && Date.now() - hit.at < CACHE_TTL2 ? hit.result : null;
|
|
1709
|
-
const [result, setResult] = (0, import_react6.useState)(
|
|
1816
|
+
const [result, setResult] = (0, import_react6.useState)(
|
|
1817
|
+
seed ?? { ...DEFAULT_RESULT, loading: enabled }
|
|
1818
|
+
);
|
|
1710
1819
|
const fetchIdRef = (0, import_react6.useRef)(0);
|
|
1711
1820
|
(0, import_react6.useEffect)(() => {
|
|
1712
1821
|
if (!enabled) {
|