@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 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: SlapiPaymentStatus; // Payment status (AUTHORIZED, CAPTURED, FAILED, etc.)
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 SlapiPaymentStatus {
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(searchParams: Record<string, string | null>);
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-VZWGJKM3.js";
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-XFJYLJZ4.js";
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, metadata } = params;
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
- if (["AUTHORIZED" /* AUTHORIZED */, "CAPTURED" /* CAPTURED */].includes(res.status)) {
150
- return Promise.resolve(res);
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-UT65MGD2.js";
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-XFJYLJZ4.js";
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, metadata } = params;
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
- if (["AUTHORIZED" /* AUTHORIZED */, "CAPTURED" /* CAPTURED */].includes(res.status)) {
767
- return Promise.resolve(res);
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 = ({ isLoggedIn }) => {
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, { isLoggedIn })
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-XFJYLJZ4.js";
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 = ({ isLoggedIn }) => {
209
+ var CheckoutButton = () => {
210
+ const isLoggedIn = useIsLoggedIn();
209
211
  const { appProps, staticConfig } = useStaticConfig();
210
212
  const {
211
213
  amount,
@@ -3,7 +3,7 @@ import {
3
3
  DialogContent,
4
4
  DialogDescription,
5
5
  DialogTitle
6
- } from "./chunk-XFJYLJZ4.js";
6
+ } from "./chunk-PTEQVWLJ.js";
7
7
 
8
8
  // src/modals/Iframe3ds.tsx
9
9
  import { useEffect } from "react";
@@ -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.4.14";
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 PaymentError("Payment already in progress", "FAILED" /* FAILED */);
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
- if (e instanceof Error) throw e;
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
- if (!res.ok) {
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 { data, isLoading } = useSWR(origin ? `${URL}?origin=${origin}` : URL);
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 jsx3, jsxs } from "react/jsx-runtime";
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__ */ jsx3(
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__ */ jsx3(
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__ */ jsx3(
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__ */ jsx3(
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__ */ jsx3(
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 jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
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__ */ jsx4(
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 jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
791
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
707
792
  function Dialog({ ...props }) {
708
- return /* @__PURE__ */ jsx5(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
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__ */ jsx5(DialogPrimitive.Portal, { container: safeContainer, "data-slot": "dialog-portal", ...props });
798
+ return /* @__PURE__ */ jsx6(DialogPrimitive.Portal, { container: safeContainer, "data-slot": "dialog-portal", ...props });
714
799
  }
715
800
  function DialogOverlay({ className, ...props }) {
716
- return /* @__PURE__ */ jsx5(
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__ */ jsx5(DialogOverlay, {}),
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__ */ jsx5(XIcon, {}),
754
- /* @__PURE__ */ jsx5("span", { className: "sr-only", children: "Close" })
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__ */ jsx5(
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__ */ jsx5(
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
  };