@superlogic/spree-pay 0.4.14 → 0.5.1
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/README.md +82 -5
- package/build/{CryptoComTab-NMS7BP25.js → CryptoComTab-ZAWBJUCM.js} +11 -12
- package/build/{CryptoTab-4IC63YD5.js → CryptoTab-632DRL2D.js} +13 -14
- package/build/{chunk-UT65MGD2.js → chunk-4BM6VTMT.js} +4 -2
- package/build/{chunk-VZWGJKM3.js → chunk-AXNTOSBU.js} +1 -1
- package/build/{chunk-XFJYLJZ4.js → chunk-PTEQVWLJ.js} +143 -48
- package/build/index.cjs +625 -502
- package/build/index.css +5 -0
- package/build/index.d.cts +50 -5
- package/build/index.d.ts +50 -5
- package/build/index.js +82 -55
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -49,7 +49,6 @@ function CheckoutContent() {
|
|
|
49
49
|
const result = await process({
|
|
50
50
|
hash: order.hash,
|
|
51
51
|
capture: true, // Optional: auto-capture payment
|
|
52
|
-
metadata: { orderId: order.id }, // Optional: additional data
|
|
53
52
|
});
|
|
54
53
|
|
|
55
54
|
console.log('Payment result:', result);
|
|
@@ -59,6 +58,7 @@ function CheckoutContent() {
|
|
|
59
58
|
|
|
60
59
|
setStatus('success');
|
|
61
60
|
} catch (err) {
|
|
61
|
+
// `process` always rejects with a PaymentError — see "Error Handling" below
|
|
62
62
|
console.error('Payment failed:', err);
|
|
63
63
|
setStatus('error');
|
|
64
64
|
}
|
|
@@ -177,11 +177,10 @@ async function process(params: ProcessFnParams): Promise<PaymentMethodResult>;
|
|
|
177
177
|
type ProcessFnParams = {
|
|
178
178
|
hash: string; // Order hash from your backend
|
|
179
179
|
capture?: boolean; // Optional: auto-capture payment (default: false)
|
|
180
|
-
metadata?: Record<string, unknown>; // Optional: additional metadata to pass through
|
|
181
180
|
};
|
|
182
181
|
|
|
183
182
|
type PaymentMethodResult = {
|
|
184
|
-
status:
|
|
183
|
+
status: PaymentStatus; // Payment status (AUTHORIZED, CAPTURED, FAILED, etc.)
|
|
185
184
|
paymentId: string; // Unique payment identifier
|
|
186
185
|
txHash: string | null; // Transaction hash (for crypto payments)
|
|
187
186
|
txId: string | null; // Transaction ID
|
|
@@ -200,7 +199,7 @@ enum PaymentType {
|
|
|
200
199
|
POINTS = 'POINTS',
|
|
201
200
|
}
|
|
202
201
|
|
|
203
|
-
enum
|
|
202
|
+
enum PaymentStatus {
|
|
204
203
|
AUTHORIZED = 'AUTHORIZED', // Payment authorized (success)
|
|
205
204
|
CAPTURED = 'CAPTURED', // Payment captured (success)
|
|
206
205
|
FAILED = 'FAILED', // Payment failed
|
|
@@ -211,11 +210,76 @@ enum SlapiPaymentStatus {
|
|
|
211
210
|
}
|
|
212
211
|
```
|
|
213
212
|
|
|
213
|
+
### Error Handling
|
|
214
|
+
|
|
215
|
+
`process()` always rejects with a `PaymentError`. **Branch on `code`** (the failure category) to show your own UI copy. The original error — including any raw backend detail — is kept on `cause` **for logging only; never render it to end users.**
|
|
216
|
+
|
|
217
|
+
| `code` | When | Suggested UX |
|
|
218
|
+
| -------------------------------- | ---------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
|
|
219
|
+
| `PaymentErrorCode.CARD_ERROR` | A card payment was rejected by the backend (a 4xx response, or a terminal `FAILED` status on a card flow). | "We couldn't process your card — check it / try another." |
|
|
220
|
+
| `PaymentErrorCode.PAYMENT_ERROR` | Anything else: network/server error, timeout, cancelled flow, or a crypto/points failure. | Generic "payment failed, try again". |
|
|
221
|
+
|
|
222
|
+
> **Safety:** `message` is a fixed, generic label (e.g. `"Card payment failed"`) — it never contains raw backend text, so it's safe to log or show. To distinguish a real decline (e.g. "insufficient funds") from another 4xx, the backend must return a machine-readable error code; the widget intentionally does **not** surface the backend's free-text message to end users.
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
import { PaymentError, PaymentErrorCode, SpreePayHttpError, isPaymentError } from '@superlogic/spree-pay';
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
const result = await process({ hash, capture: true });
|
|
229
|
+
// result.status is AUTHORIZED or CAPTURED
|
|
230
|
+
} catch (err) {
|
|
231
|
+
if (isPaymentError(err)) {
|
|
232
|
+
// Choose user-facing copy from the category — do NOT show err.cause to users.
|
|
233
|
+
if (err.code === PaymentErrorCode.CARD_ERROR) {
|
|
234
|
+
// card-payment problem — prompt the user to check the card or try another
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
err.code; // PaymentErrorCode — the category to branch on
|
|
238
|
+
err.status; // PaymentStatus — e.g. FAILED
|
|
239
|
+
err.backendCode; // string | undefined — a stable backend code when available (e.g. 'INSUFFICIENT_FUNDS')
|
|
240
|
+
|
|
241
|
+
// For logging/debugging only — may carry sensitive backend detail:
|
|
242
|
+
if (err.cause instanceof SpreePayHttpError) {
|
|
243
|
+
err.cause.status; // numeric HTTP status (e.g. 400, 500)
|
|
244
|
+
err.cause.body; // raw response body
|
|
245
|
+
err.cause.url; // request URL
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
enum PaymentErrorCode {
|
|
253
|
+
CARD_ERROR = 'CARD_ERROR', // a card payment was rejected by the backend
|
|
254
|
+
PAYMENT_ERROR = 'PAYMENT_ERROR', // network/server/timeout/cancelled/crypto/points failure
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Thrown by the widget; detect it with `isPaymentError` and read its fields.
|
|
258
|
+
// Not meant to be constructed by consumers.
|
|
259
|
+
class PaymentError extends Error {
|
|
260
|
+
message: string; // fixed, generic label — never raw backend text; safe to log/show
|
|
261
|
+
code: PaymentErrorCode; // failure category to branch on (defaults to PAYMENT_ERROR)
|
|
262
|
+
status: PaymentStatus; // payment status at the point of failure
|
|
263
|
+
backendCode?: string; // stable machine code from the backend when available (e.g. 'INSUFFICIENT_FUNDS') — for finer branching
|
|
264
|
+
cause?: unknown; // the original thrown value (standard Error.cause) — log only, may hold sensitive detail
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Type guard for consumers — the supported way to detect a PaymentError
|
|
268
|
+
function isPaymentError(e: unknown): e is PaymentError;
|
|
269
|
+
|
|
270
|
+
// Thrown by the fetcher on a non-ok HTTP response; reachable via PaymentError.cause
|
|
271
|
+
class SpreePayHttpError extends Error {
|
|
272
|
+
status: number; // HTTP status code
|
|
273
|
+
body: string; // raw response body
|
|
274
|
+
url: string; // request URL
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
214
278
|
### useCapture3DS Hook
|
|
215
279
|
|
|
216
280
|
```typescript
|
|
217
281
|
// Use in your 3DS redirect page to capture payment intent and close the modal
|
|
218
|
-
useCapture3DS(
|
|
282
|
+
useCapture3DS(params: { paymentIntent?: string | null });
|
|
219
283
|
```
|
|
220
284
|
|
|
221
285
|
### Logger API
|
|
@@ -248,6 +312,19 @@ enum LogLevel {
|
|
|
248
312
|
}
|
|
249
313
|
```
|
|
250
314
|
|
|
315
|
+
Supporting types are exported so you can name them in your own code:
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
import type { Environment, LogContext, LoggerConfig } from '@superlogic/spree-pay';
|
|
319
|
+
|
|
320
|
+
type Environment = 'dev' | 'stg' | 'prod'; // also the `environment` field of the provider's ENV
|
|
321
|
+
type LoggerConfig = { minLevel: LogLevel; prefix: string; environment?: Environment };
|
|
322
|
+
type LogContext = Record<string, unknown>; // the optional context arg on logger.debug/info/warn/error
|
|
323
|
+
|
|
324
|
+
// configureLogger accepts a partial config:
|
|
325
|
+
function configureLogger(config: Partial<LoggerConfig> & { environment?: Environment }): void;
|
|
326
|
+
```
|
|
327
|
+
|
|
251
328
|
## Theme Customization
|
|
252
329
|
|
|
253
330
|
SpreePay uses CSS variables for theming (see all 58+ variables in `packages/spree-pay/src/styles/globals.css:22-82`). You can override these using CSS or CSS-in-JS.
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Iframe3ds
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-AXNTOSBU.js";
|
|
4
4
|
import {
|
|
5
5
|
InfoBanner,
|
|
6
6
|
Legal,
|
|
7
|
-
PaymentError,
|
|
8
7
|
SlapiPaymentService,
|
|
9
8
|
logger,
|
|
9
|
+
settlePaymentResult,
|
|
10
|
+
toPaymentError,
|
|
11
|
+
useIsLoggedIn,
|
|
10
12
|
useSpreePayConfig,
|
|
11
13
|
useSpreePayEnv,
|
|
12
14
|
useSpreePayRegister,
|
|
13
15
|
useSpreePaymentMethod,
|
|
14
16
|
useStaticConfig
|
|
15
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-PTEQVWLJ.js";
|
|
16
18
|
|
|
17
19
|
// src/components/CryptoComTab/CryptoComTab.tsx
|
|
18
20
|
import { useCallback, useEffect } from "react";
|
|
@@ -27,11 +29,10 @@ var useCryptoComPayment = () => {
|
|
|
27
29
|
if (selectedPaymentMethod.type !== "CDC" /* CDC */) {
|
|
28
30
|
throw new Error("Unsupported payment method");
|
|
29
31
|
}
|
|
30
|
-
const { hash
|
|
32
|
+
const { hash } = params;
|
|
31
33
|
cryptoComLogger.info("Starting Crypto.com Pay payment", { hash });
|
|
32
34
|
const { data: paymentResData } = await SlapiPaymentService.createPayment({
|
|
33
35
|
hash,
|
|
34
|
-
metadata,
|
|
35
36
|
type: "CDC" /* CDC */,
|
|
36
37
|
cdc: {
|
|
37
38
|
returnUrl: `${typeof window !== "undefined" ? window.location.origin : ""}${redirect3dsURI}?payment_intent=success`,
|
|
@@ -77,13 +78,14 @@ var useCryptoComPayment = () => {
|
|
|
77
78
|
// src/components/CryptoComTab/Checkout.tsx
|
|
78
79
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
79
80
|
var Checkout = () => {
|
|
81
|
+
const isLoggedIn = useIsLoggedIn();
|
|
80
82
|
const { appProps } = useStaticConfig();
|
|
81
83
|
const { isInternalProcessing } = useSpreePaymentMethod();
|
|
82
84
|
return /* @__PURE__ */ jsx(
|
|
83
85
|
"button",
|
|
84
86
|
{
|
|
85
87
|
onClick: appProps.onProcess,
|
|
86
|
-
disabled: !!appProps.isProcessing || isInternalProcessing,
|
|
88
|
+
disabled: !isLoggedIn || !!appProps.isProcessing || isInternalProcessing,
|
|
87
89
|
className: "flex flex-col items-center rounded-md bg-(--crypto-pay-bg) p-2 text-(--brand-primary) hover:bg-(--crypto-pay-bg-hover) disabled:cursor-not-allowed disabled:bg-(--crypto-pay-bg-hover) disabled:text-(--disabled)",
|
|
88
90
|
children: /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-7 w-[76px]", fill: "none", viewBox: "0 0 76 28", children: [
|
|
89
91
|
/* @__PURE__ */ jsx(
|
|
@@ -146,12 +148,9 @@ var CryptoComTab = () => {
|
|
|
146
148
|
async (data) => {
|
|
147
149
|
try {
|
|
148
150
|
const res = await cryptoComPayment(data);
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
return Promise.reject(new PaymentError("Crypto payment failed", res.status));
|
|
153
|
-
} catch (_) {
|
|
154
|
-
return Promise.reject(new PaymentError("Payment failed", "FAILED" /* FAILED */));
|
|
151
|
+
return settlePaymentResult(res, "Crypto payment failed");
|
|
152
|
+
} catch (e) {
|
|
153
|
+
return Promise.reject(toPaymentError(e, "Crypto payment failed"));
|
|
155
154
|
}
|
|
156
155
|
},
|
|
157
156
|
[cryptoComPayment]
|
|
@@ -2,21 +2,23 @@ import {
|
|
|
2
2
|
CheckoutButton,
|
|
3
3
|
PointsSwitch,
|
|
4
4
|
cn as cn2
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-4BM6VTMT.js";
|
|
6
6
|
import {
|
|
7
7
|
Dialog,
|
|
8
8
|
DialogContent,
|
|
9
9
|
DialogDescription,
|
|
10
10
|
DialogTitle,
|
|
11
11
|
InfoBanner,
|
|
12
|
-
PaymentError,
|
|
13
12
|
SlapiPaymentService,
|
|
14
13
|
cn,
|
|
15
14
|
logger,
|
|
15
|
+
settlePaymentResult,
|
|
16
|
+
toPaymentError,
|
|
17
|
+
useIsLoggedIn,
|
|
16
18
|
useSpreePayConfig,
|
|
17
19
|
useSpreePayRegister,
|
|
18
20
|
useSpreePaymentMethod
|
|
19
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-PTEQVWLJ.js";
|
|
20
22
|
|
|
21
23
|
// src/components/CryptoTab/Crypto/CryptoWrapper.tsx
|
|
22
24
|
import { useMemo as useMemo2 } from "react";
|
|
@@ -153,7 +155,7 @@ var useCryptoPayment = () => {
|
|
|
153
155
|
});
|
|
154
156
|
throw error;
|
|
155
157
|
}
|
|
156
|
-
const { capture, hash
|
|
158
|
+
const { capture, hash } = params;
|
|
157
159
|
const TOKEN = selectedPaymentMethod.method.symbol;
|
|
158
160
|
cryptoPaymentLogger.info("Starting crypto payment", {
|
|
159
161
|
hash,
|
|
@@ -213,7 +215,6 @@ var useCryptoPayment = () => {
|
|
|
213
215
|
const paymentRes = await SlapiPaymentService.createPayment({
|
|
214
216
|
hash,
|
|
215
217
|
capture,
|
|
216
|
-
metadata,
|
|
217
218
|
type: "CRYPTO" /* CRYPTO */,
|
|
218
219
|
crypto: {
|
|
219
220
|
token: TOKEN,
|
|
@@ -763,12 +764,9 @@ var Crypto = () => {
|
|
|
763
764
|
async (data) => {
|
|
764
765
|
try {
|
|
765
766
|
const res = await cryptoPayment(data);
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
return Promise.reject(new PaymentError("Crypto payment failed", res.status));
|
|
770
|
-
} catch (_) {
|
|
771
|
-
return Promise.reject(new PaymentError("Payment failed", "FAILED" /* FAILED */));
|
|
767
|
+
return settlePaymentResult(res, "Crypto payment failed");
|
|
768
|
+
} catch (e) {
|
|
769
|
+
return Promise.reject(toPaymentError(e, "Crypto payment failed"));
|
|
772
770
|
}
|
|
773
771
|
},
|
|
774
772
|
[cryptoPayment]
|
|
@@ -812,23 +810,24 @@ function getCachedWagmiConfig(projectId, appName) {
|
|
|
812
810
|
return cfg;
|
|
813
811
|
}
|
|
814
812
|
var CryptoWrapper = () => {
|
|
813
|
+
const isLoggedIn = useIsLoggedIn();
|
|
815
814
|
const { spreePayConfig, configIsLoading } = useSpreePayConfig();
|
|
816
815
|
const wagmiConfig = useMemo2(() => {
|
|
817
816
|
if (!spreePayConfig) return null;
|
|
818
817
|
return getCachedWagmiConfig(spreePayConfig.rainbowProjectId, spreePayConfig.rainbowAppName);
|
|
819
818
|
}, [spreePayConfig]);
|
|
820
|
-
if (configIsLoading || !wagmiConfig) return null;
|
|
819
|
+
if (!isLoggedIn || configIsLoading || !wagmiConfig) return null;
|
|
821
820
|
return /* @__PURE__ */ jsx12(WagmiProvider, { config: wagmiConfig, children: /* @__PURE__ */ jsx12(QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ jsx12(RainbowKitProvider, { theme: lightTheme({ borderRadius: "large" }), children: /* @__PURE__ */ jsx12(NiceModal3.Provider, { children: /* @__PURE__ */ jsx12(Crypto, {}) }) }) }) });
|
|
822
821
|
};
|
|
823
822
|
|
|
824
823
|
// src/components/CryptoTab/CryptoTab.tsx
|
|
825
824
|
import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
826
|
-
var CryptoTab = (
|
|
825
|
+
var CryptoTab = () => {
|
|
827
826
|
const { spreePayConfig } = useSpreePayConfig();
|
|
828
827
|
return /* @__PURE__ */ jsxs8("div", { children: [
|
|
829
828
|
/* @__PURE__ */ jsx13("div", { className: "border-b border-(--border-component-specific-card) px-5 py-5 md:px-7 md:py-6", children: /* @__PURE__ */ jsx13(CryptoWrapper, {}) }),
|
|
830
829
|
!spreePayConfig?.crypto.hidePoints && /* @__PURE__ */ jsx13("div", { className: "border-b border-(--border-component-specific-card) px-5 py-5 md:px-7 md:py-6", children: /* @__PURE__ */ jsx13(PointsSwitch, { disabled: true, message: spreePayConfig?.crypto.pointsInfoMessage }) }),
|
|
831
|
-
/* @__PURE__ */ jsx13(CheckoutButton, {
|
|
830
|
+
/* @__PURE__ */ jsx13(CheckoutButton, {})
|
|
832
831
|
] });
|
|
833
832
|
};
|
|
834
833
|
export {
|
|
@@ -2,11 +2,12 @@ import {
|
|
|
2
2
|
InfoBanner,
|
|
3
3
|
Legal,
|
|
4
4
|
cn,
|
|
5
|
+
useIsLoggedIn,
|
|
5
6
|
useSpreePayConfig,
|
|
6
7
|
useSpreePayEnv,
|
|
7
8
|
useSpreePaymentMethod,
|
|
8
9
|
useStaticConfig
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-PTEQVWLJ.js";
|
|
10
11
|
|
|
11
12
|
// src/components/common/PointsSwitch.tsx
|
|
12
13
|
import { useId } from "react";
|
|
@@ -205,7 +206,8 @@ var getTransactionFee = (amount = 0, transactionFeePercentage) => {
|
|
|
205
206
|
|
|
206
207
|
// src/components/CheckoutButton.tsx
|
|
207
208
|
import { Fragment, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
208
|
-
var CheckoutButton = (
|
|
209
|
+
var CheckoutButton = () => {
|
|
210
|
+
const isLoggedIn = useIsLoggedIn();
|
|
209
211
|
const { appProps, staticConfig } = useStaticConfig();
|
|
210
212
|
const {
|
|
211
213
|
amount,
|
|
@@ -8,9 +8,85 @@ var PaymentType = /* @__PURE__ */ ((PaymentType2) => {
|
|
|
8
8
|
PaymentType2["POINTS"] = "POINTS";
|
|
9
9
|
return PaymentType2;
|
|
10
10
|
})(PaymentType || {});
|
|
11
|
+
var PaymentStatus = /* @__PURE__ */ ((PaymentStatus2) => {
|
|
12
|
+
PaymentStatus2["NOT_INITIALIZED"] = "NOT_INITIALIZED";
|
|
13
|
+
PaymentStatus2["PENDING"] = "PENDING";
|
|
14
|
+
PaymentStatus2["AUTHORIZED"] = "AUTHORIZED";
|
|
15
|
+
PaymentStatus2["FAILED"] = "FAILED";
|
|
16
|
+
PaymentStatus2["CAPTURED"] = "CAPTURED";
|
|
17
|
+
PaymentStatus2["VOIDED"] = "VOIDED";
|
|
18
|
+
PaymentStatus2["CAPTURE_FAILED"] = "CAPTURE_FAILED";
|
|
19
|
+
return PaymentStatus2;
|
|
20
|
+
})(PaymentStatus || {});
|
|
21
|
+
|
|
22
|
+
// src/types/errors.ts
|
|
23
|
+
var SpreePayHttpError = class extends Error {
|
|
24
|
+
constructor(status, body, url) {
|
|
25
|
+
const snippet = body.length > 200 ? `${body.slice(0, 200)}\u2026` : body;
|
|
26
|
+
super(snippet || `Request failed: ${status}`);
|
|
27
|
+
this.status = status;
|
|
28
|
+
this.body = body;
|
|
29
|
+
this.url = url;
|
|
30
|
+
this.name = "SpreePayHttpError";
|
|
31
|
+
}
|
|
32
|
+
status;
|
|
33
|
+
body;
|
|
34
|
+
url;
|
|
35
|
+
};
|
|
36
|
+
var PaymentErrorCode = /* @__PURE__ */ ((PaymentErrorCode2) => {
|
|
37
|
+
PaymentErrorCode2["CARD_ERROR"] = "CARD_ERROR";
|
|
38
|
+
PaymentErrorCode2["PAYMENT_ERROR"] = "PAYMENT_ERROR";
|
|
39
|
+
return PaymentErrorCode2;
|
|
40
|
+
})(PaymentErrorCode || {});
|
|
41
|
+
var PaymentError = class extends Error {
|
|
42
|
+
status;
|
|
43
|
+
code;
|
|
44
|
+
/**
|
|
45
|
+
* Optional stable code from the backend (e.g. `INSUFFICIENT_FUNDS`) for branching
|
|
46
|
+
* on specific cases without changing the `code` enum. Safe to expose (an
|
|
47
|
+
* identifier, not free-text); absent until the backend supplies one.
|
|
48
|
+
*/
|
|
49
|
+
backendCode;
|
|
50
|
+
constructor(message, status, options) {
|
|
51
|
+
super(message, { cause: options?.cause });
|
|
52
|
+
this.name = "PaymentError";
|
|
53
|
+
this.status = status;
|
|
54
|
+
this.code = options?.code ?? "PAYMENT_ERROR" /* PAYMENT_ERROR */;
|
|
55
|
+
this.backendCode = options?.backendCode;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
var isPaymentError = (e) => e instanceof PaymentError;
|
|
59
|
+
var isDeclineStatus = (status) => status === "FAILED" /* FAILED */ || status === "CAPTURE_FAILED" /* CAPTURE_FAILED */;
|
|
60
|
+
var isSuccessStatus = (status) => status === "AUTHORIZED" /* AUTHORIZED */ || status === "CAPTURED" /* CAPTURED */;
|
|
61
|
+
var isProcessorRejection = (e) => e instanceof SpreePayHttpError && e.status >= 400 && e.status < 500 && e.status !== 401 && e.status !== 403 && e.status !== 404;
|
|
62
|
+
var backendCodeFromBody = (e) => {
|
|
63
|
+
try {
|
|
64
|
+
const parsed = JSON.parse(e.body);
|
|
65
|
+
const code = parsed?.code ?? parsed?.errorCode;
|
|
66
|
+
if (typeof code === "string" && code.trim()) return code;
|
|
67
|
+
} catch {
|
|
68
|
+
}
|
|
69
|
+
return void 0;
|
|
70
|
+
};
|
|
71
|
+
var toPaymentError = (e, fallbackMessage = "Payment failed", declineCode = "PAYMENT_ERROR" /* PAYMENT_ERROR */) => {
|
|
72
|
+
if (e instanceof PaymentError) return e;
|
|
73
|
+
return new PaymentError(fallbackMessage, "FAILED" /* FAILED */, {
|
|
74
|
+
code: isProcessorRejection(e) ? declineCode : "PAYMENT_ERROR" /* PAYMENT_ERROR */,
|
|
75
|
+
backendCode: e instanceof SpreePayHttpError ? backendCodeFromBody(e) : void 0,
|
|
76
|
+
cause: e
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
var settlePaymentResult = (res, fallbackMessage, failureCode = "PAYMENT_ERROR" /* PAYMENT_ERROR */) => {
|
|
80
|
+
if (isSuccessStatus(res.status)) {
|
|
81
|
+
return res;
|
|
82
|
+
}
|
|
83
|
+
throw new PaymentError(fallbackMessage, res.status, {
|
|
84
|
+
code: isDeclineStatus(res.status) ? failureCode : "PAYMENT_ERROR" /* PAYMENT_ERROR */
|
|
85
|
+
});
|
|
86
|
+
};
|
|
11
87
|
|
|
12
88
|
// package.json
|
|
13
|
-
var version = "0.
|
|
89
|
+
var version = "0.5.1";
|
|
14
90
|
|
|
15
91
|
// src/utils/logger.ts
|
|
16
92
|
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
@@ -128,20 +204,6 @@ var configureLogger = (config2) => {
|
|
|
128
204
|
|
|
129
205
|
// src/context/SpreePayActionsContext.tsx
|
|
130
206
|
import { createContext, useCallback, useContext, useRef, useState } from "react";
|
|
131
|
-
|
|
132
|
-
// src/types/errors.ts
|
|
133
|
-
var PaymentError = class extends Error {
|
|
134
|
-
status;
|
|
135
|
-
details;
|
|
136
|
-
constructor(message, status, details) {
|
|
137
|
-
super(message);
|
|
138
|
-
this.name = "PaymentError";
|
|
139
|
-
this.status = status;
|
|
140
|
-
this.details = details;
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
// src/context/SpreePayActionsContext.tsx
|
|
145
207
|
import { jsx } from "react/jsx-runtime";
|
|
146
208
|
var processLogger = logger.child("process");
|
|
147
209
|
var SpreePayActionsContext = createContext(void 0);
|
|
@@ -168,12 +230,11 @@ var SpreePayProvider = ({ children, env }) => {
|
|
|
168
230
|
hash: data.hash,
|
|
169
231
|
paymentType: selectedPaymentMethod.type
|
|
170
232
|
});
|
|
171
|
-
throw new
|
|
233
|
+
throw new Error("Payment already in progress");
|
|
172
234
|
}
|
|
173
235
|
processLogger.info("Payment process started", {
|
|
174
236
|
hash: data.hash,
|
|
175
237
|
capture: data.capture,
|
|
176
|
-
hasMetadata: Boolean(data.metadata),
|
|
177
238
|
paymentType: selectedPaymentMethod.type
|
|
178
239
|
});
|
|
179
240
|
inFlightRef.current = true;
|
|
@@ -193,8 +254,7 @@ var SpreePayProvider = ({ children, env }) => {
|
|
|
193
254
|
paymentType: selectedPaymentMethod.type,
|
|
194
255
|
errorMessage: e instanceof Error ? e.message : String(e)
|
|
195
256
|
});
|
|
196
|
-
|
|
197
|
-
throw new PaymentError("Payment failed", "FAILED" /* FAILED */, e);
|
|
257
|
+
throw toPaymentError(e);
|
|
198
258
|
} finally {
|
|
199
259
|
inFlightRef.current = false;
|
|
200
260
|
setInternalProcessing(false);
|
|
@@ -389,6 +449,27 @@ var buildUrl = (key) => {
|
|
|
389
449
|
const qs = usp.toString();
|
|
390
450
|
return cfg.baseUrl + url + (qs ? `?${qs}` : "");
|
|
391
451
|
};
|
|
452
|
+
var parseResponse = async (res) => {
|
|
453
|
+
if (!res.ok) {
|
|
454
|
+
const text = await res.text().catch(() => "");
|
|
455
|
+
throw new SpreePayHttpError(res.status, text, res.url);
|
|
456
|
+
}
|
|
457
|
+
const contentType = res.headers.get("content-type") || "";
|
|
458
|
+
if (!contentType.includes("application/json")) {
|
|
459
|
+
return await res.text();
|
|
460
|
+
}
|
|
461
|
+
return await res.json();
|
|
462
|
+
};
|
|
463
|
+
var publicGet = async (baseUrl, tenantId, path) => {
|
|
464
|
+
const res = await fetch(`${baseUrl}${path}`, {
|
|
465
|
+
headers: {
|
|
466
|
+
Accept: "application/json",
|
|
467
|
+
["X-Tenant-ID"]: tenantId
|
|
468
|
+
},
|
|
469
|
+
cache: "no-store"
|
|
470
|
+
});
|
|
471
|
+
return parseResponse(res);
|
|
472
|
+
};
|
|
392
473
|
var request = async (method, url, body) => {
|
|
393
474
|
if (!cfg.accessToken) throw new Error("Missing SLAPI accessToken. Call registerApi(...) first.");
|
|
394
475
|
if (!cfg.tenantId) throw new Error("Missing SLAPI tenantId. Call registerApi(...) first.");
|
|
@@ -414,15 +495,7 @@ var request = async (method, url, body) => {
|
|
|
414
495
|
body: payload,
|
|
415
496
|
cache: "no-store"
|
|
416
497
|
});
|
|
417
|
-
|
|
418
|
-
const text = await res.text().catch(() => "");
|
|
419
|
-
throw new Error(text || `Request failed: ${res.status}`);
|
|
420
|
-
}
|
|
421
|
-
const contentType = res.headers.get("content-type") || "";
|
|
422
|
-
if (!contentType.includes("application/json")) {
|
|
423
|
-
return await res.text();
|
|
424
|
-
}
|
|
425
|
-
return await res.json();
|
|
498
|
+
return parseResponse(res);
|
|
426
499
|
};
|
|
427
500
|
var slapiApi = {
|
|
428
501
|
get: async () => {
|
|
@@ -474,14 +547,26 @@ var registerApi = (config2) => {
|
|
|
474
547
|
import useSWR from "swr";
|
|
475
548
|
var URL = "/v1/tenants/configs/spree-pay";
|
|
476
549
|
var useSpreePayConfig = () => {
|
|
477
|
-
const { origin } = useSpreePayEnv();
|
|
478
|
-
const {
|
|
550
|
+
const { origin, tenantId } = useSpreePayEnv();
|
|
551
|
+
const { staticConfig } = useStaticConfig();
|
|
552
|
+
const key = origin ? `${URL}?origin=${origin}` : URL;
|
|
553
|
+
const { data, isLoading } = useSWR(
|
|
554
|
+
key,
|
|
555
|
+
(path) => publicGet(staticConfig.slapiUrl, tenantId, path)
|
|
556
|
+
);
|
|
479
557
|
return { spreePayConfig: data ?? null, configIsLoading: isLoading };
|
|
480
558
|
};
|
|
481
559
|
|
|
560
|
+
// src/context/LoginStatusContext.tsx
|
|
561
|
+
import { createContext as createContext3, useContext as useContext3 } from "react";
|
|
562
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
563
|
+
var LoginStatusContext = createContext3(false);
|
|
564
|
+
var LoginStatusProvider = ({ isLoggedIn, children }) => /* @__PURE__ */ jsx3(LoginStatusContext.Provider, { value: isLoggedIn, children });
|
|
565
|
+
var useIsLoggedIn = () => useContext3(LoginStatusContext);
|
|
566
|
+
|
|
482
567
|
// src/components/common/InfoBanner.tsx
|
|
483
568
|
import xss from "xss";
|
|
484
|
-
import { jsx as
|
|
569
|
+
import { jsx as jsx4, jsxs } from "react/jsx-runtime";
|
|
485
570
|
var InfoBanner = (props) => {
|
|
486
571
|
const { type = "info", message } = props;
|
|
487
572
|
if (!message) return null;
|
|
@@ -493,14 +578,14 @@ var InfoBanner = (props) => {
|
|
|
493
578
|
"bg-(--s-warning)/10": type === "warning"
|
|
494
579
|
}),
|
|
495
580
|
children: [
|
|
496
|
-
type === "info" && /* @__PURE__ */
|
|
581
|
+
type === "info" && /* @__PURE__ */ jsx4(
|
|
497
582
|
"svg",
|
|
498
583
|
{
|
|
499
584
|
xmlns: "http://www.w3.org/2000/svg",
|
|
500
585
|
fill: "none",
|
|
501
586
|
viewBox: "0 0 20 20",
|
|
502
587
|
className: "size-5 shrink-0 text-(--positive)",
|
|
503
|
-
children: /* @__PURE__ */
|
|
588
|
+
children: /* @__PURE__ */ jsx4(
|
|
504
589
|
"path",
|
|
505
590
|
{
|
|
506
591
|
fill: "currentColor",
|
|
@@ -509,14 +594,14 @@ var InfoBanner = (props) => {
|
|
|
509
594
|
)
|
|
510
595
|
}
|
|
511
596
|
),
|
|
512
|
-
type === "warning" && /* @__PURE__ */
|
|
597
|
+
type === "warning" && /* @__PURE__ */ jsx4(
|
|
513
598
|
"svg",
|
|
514
599
|
{
|
|
515
600
|
xmlns: "http://www.w3.org/2000/svg",
|
|
516
601
|
fill: "none",
|
|
517
602
|
viewBox: "0 0 20 20",
|
|
518
603
|
className: "size-5 shrink-0 text-(--warning)",
|
|
519
|
-
children: /* @__PURE__ */
|
|
604
|
+
children: /* @__PURE__ */ jsx4(
|
|
520
605
|
"path",
|
|
521
606
|
{
|
|
522
607
|
fill: "currentColor",
|
|
@@ -525,7 +610,7 @@ var InfoBanner = (props) => {
|
|
|
525
610
|
)
|
|
526
611
|
}
|
|
527
612
|
),
|
|
528
|
-
/* @__PURE__ */
|
|
613
|
+
/* @__PURE__ */ jsx4(
|
|
529
614
|
"p",
|
|
530
615
|
{
|
|
531
616
|
className: "break-anywhere text-[14px] leading-5 text-(--primary) [&_a]:underline",
|
|
@@ -538,13 +623,13 @@ var InfoBanner = (props) => {
|
|
|
538
623
|
};
|
|
539
624
|
|
|
540
625
|
// src/components/common/Legal.tsx
|
|
541
|
-
import { jsx as
|
|
626
|
+
import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
542
627
|
var Legal = () => {
|
|
543
628
|
const { spreePayConfig } = useSpreePayConfig();
|
|
544
629
|
return /* @__PURE__ */ jsxs2("p", { className: "text-xs leading-5 font-medium text-(--secondary)", children: [
|
|
545
630
|
"By clicking on the button below I acknowledge that I have read and accepted the",
|
|
546
631
|
" ",
|
|
547
|
-
/* @__PURE__ */
|
|
632
|
+
/* @__PURE__ */ jsx5(
|
|
548
633
|
"a",
|
|
549
634
|
{
|
|
550
635
|
className: "whitespace-nowrap underline",
|
|
@@ -703,17 +788,17 @@ var SlapiPaymentService = {
|
|
|
703
788
|
// src/ui/dialog.tsx
|
|
704
789
|
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
705
790
|
import { XIcon } from "lucide-react";
|
|
706
|
-
import { jsx as
|
|
791
|
+
import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
707
792
|
function Dialog({ ...props }) {
|
|
708
|
-
return /* @__PURE__ */
|
|
793
|
+
return /* @__PURE__ */ jsx6(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
|
|
709
794
|
}
|
|
710
795
|
function DialogPortal({ ...props }) {
|
|
711
796
|
const container = usePortalContainer();
|
|
712
797
|
const safeContainer = container && document.body.contains(container) ? container : void 0;
|
|
713
|
-
return /* @__PURE__ */
|
|
798
|
+
return /* @__PURE__ */ jsx6(DialogPrimitive.Portal, { container: safeContainer, "data-slot": "dialog-portal", ...props });
|
|
714
799
|
}
|
|
715
800
|
function DialogOverlay({ className, ...props }) {
|
|
716
|
-
return /* @__PURE__ */
|
|
801
|
+
return /* @__PURE__ */ jsx6(
|
|
717
802
|
DialogPrimitive.Overlay,
|
|
718
803
|
{
|
|
719
804
|
"data-slot": "dialog-overlay",
|
|
@@ -732,7 +817,7 @@ function DialogContent({
|
|
|
732
817
|
...props
|
|
733
818
|
}) {
|
|
734
819
|
return /* @__PURE__ */ jsxs3(DialogPortal, { "data-slot": "dialog-portal", children: [
|
|
735
|
-
/* @__PURE__ */
|
|
820
|
+
/* @__PURE__ */ jsx6(DialogOverlay, {}),
|
|
736
821
|
/* @__PURE__ */ jsxs3(
|
|
737
822
|
DialogPrimitive.Content,
|
|
738
823
|
{
|
|
@@ -750,8 +835,8 @@ function DialogContent({
|
|
|
750
835
|
"data-slot": "dialog-close",
|
|
751
836
|
className: "ring-offset-background focus:ring-ring absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-(--accent) data-[state=open]:text-(--secondary) [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
752
837
|
children: [
|
|
753
|
-
/* @__PURE__ */
|
|
754
|
-
/* @__PURE__ */
|
|
838
|
+
/* @__PURE__ */ jsx6(XIcon, {}),
|
|
839
|
+
/* @__PURE__ */ jsx6("span", { className: "sr-only", children: "Close" })
|
|
755
840
|
]
|
|
756
841
|
}
|
|
757
842
|
)
|
|
@@ -761,7 +846,7 @@ function DialogContent({
|
|
|
761
846
|
] });
|
|
762
847
|
}
|
|
763
848
|
function DialogTitle({ className, ...props }) {
|
|
764
|
-
return /* @__PURE__ */
|
|
849
|
+
return /* @__PURE__ */ jsx6(
|
|
765
850
|
DialogPrimitive.Title,
|
|
766
851
|
{
|
|
767
852
|
"data-slot": "dialog-title",
|
|
@@ -771,7 +856,7 @@ function DialogTitle({ className, ...props }) {
|
|
|
771
856
|
);
|
|
772
857
|
}
|
|
773
858
|
function DialogDescription({ className, ...props }) {
|
|
774
|
-
return /* @__PURE__ */
|
|
859
|
+
return /* @__PURE__ */ jsx6(
|
|
775
860
|
DialogPrimitive.Description,
|
|
776
861
|
{
|
|
777
862
|
"data-slot": "dialog-description",
|
|
@@ -782,9 +867,17 @@ function DialogDescription({ className, ...props }) {
|
|
|
782
867
|
}
|
|
783
868
|
|
|
784
869
|
export {
|
|
785
|
-
PaymentError,
|
|
786
870
|
isNewCard,
|
|
787
871
|
PaymentType,
|
|
872
|
+
PaymentStatus,
|
|
873
|
+
SpreePayHttpError,
|
|
874
|
+
PaymentErrorCode,
|
|
875
|
+
PaymentError,
|
|
876
|
+
isPaymentError,
|
|
877
|
+
isDeclineStatus,
|
|
878
|
+
isSuccessStatus,
|
|
879
|
+
toPaymentError,
|
|
880
|
+
settlePaymentResult,
|
|
788
881
|
LogLevel,
|
|
789
882
|
logger,
|
|
790
883
|
configureLogger,
|
|
@@ -804,6 +897,8 @@ export {
|
|
|
804
897
|
registerApi,
|
|
805
898
|
SlapiPaymentService,
|
|
806
899
|
useSpreePayConfig,
|
|
900
|
+
LoginStatusProvider,
|
|
901
|
+
useIsLoggedIn,
|
|
807
902
|
InfoBanner,
|
|
808
903
|
Legal
|
|
809
904
|
};
|