@superlogic/spree-pay 0.3.4 → 0.4.0

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
@@ -105,9 +105,45 @@ type SpreePayProps = {
105
105
  disabledPoints?: boolean; // Disables points payment option if true.
106
106
  topUpLink?: string; // Optional link to top-up page for points balance.
107
107
  className?: string; // Optional wrapper class for layout/styling.
108
+
109
+ // Multi-currency display (all three fields should be supplied together)
110
+ currencyCode?: string; // ISO 4217 display currency (e.g. "AUD").
111
+ foreignCurrencyAmount?: number; // Pre-fee order total in the display currency (e.g. 1280.46). Used directly as Pay button base; transaction fee is added on top.
112
+ exchangeRate?: number; // 1 unit of currencyCode in USD (e.g. 0.65 for AUD→USD). Used to convert USD remainder when points are applied.
108
113
  };
109
114
  ```
110
115
 
116
+ #### Multi-currency display
117
+
118
+ When `currencyCode`, `foreignCurrencyAmount`, and `exchangeRate` are all provided the widget shows prices in the shopper-selected currency. Settlement and points calculations continue in USD.
119
+
120
+ ```tsx
121
+ <SpreePay
122
+ amount={832.3} // USD amount — used for points math and payment-intent creation
123
+ currencyCode="AUD"
124
+ foreignCurrencyAmount={1280.46} // pre-fee order total in AUD from your backend
125
+ exchangeRate={0.65} // 1 AUD = 0.65 USD
126
+ onProcess={handleProcess}
127
+ transactionFeePercentage={0.04}
128
+ className="w-full max-w-[540px]"
129
+ isProcessing={status === 'processing'}
130
+ />
131
+ ```
132
+
133
+ Display behaviour:
134
+
135
+ - **Pay button (no points)** — shows `foreignCurrencyAmount + fee` in the display currency
136
+ - **Pay button (split)** — shows the card portion converted from USD remainder + fee
137
+ - **Points slider** — card amount updates live in the display currency as the slider moves
138
+ - **Available points value** — shown in display currency alongside the points count
139
+
140
+ After the user commits the points slider, `selectedPaymentMethod` exposes:
141
+
142
+ - `amount` — USD card amount including fee (use for payment-intent creation)
143
+ - `foreignCurrencyAmount` — card amount in display currency, 2 d.p. (use for order display / receipts)
144
+
145
+ If any of the three display params are absent the widget falls back to USD-only display — no breaking change.
146
+
111
147
  ## API Reference
112
148
 
113
149
  ### SpreePayProvider Props
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  Iframe3ds
3
- } from "./chunk-ER5YKU2O.js";
3
+ } from "./chunk-BGLP7QYP.js";
4
4
  import {
5
5
  InfoBanner,
6
6
  Legal,
@@ -12,7 +12,7 @@ import {
12
12
  useSpreePayRegister,
13
13
  useSpreePaymentMethod,
14
14
  useStaticConfig
15
- } from "./chunk-5FKBVNC4.js";
15
+ } from "./chunk-JUAE4572.js";
16
16
 
17
17
  // src/components/CryptoComTab/CryptoComTab.tsx
18
18
  import { useCallback, useEffect } from "react";
@@ -2,7 +2,7 @@ import {
2
2
  CheckoutButton,
3
3
  PointsSwitch,
4
4
  cn as cn2
5
- } from "./chunk-GO3SK2EV.js";
5
+ } from "./chunk-STM24EZ3.js";
6
6
  import {
7
7
  Dialog,
8
8
  DialogContent,
@@ -16,7 +16,7 @@ import {
16
16
  useSpreePayConfig,
17
17
  useSpreePayRegister,
18
18
  useSpreePaymentMethod
19
- } from "./chunk-5FKBVNC4.js";
19
+ } from "./chunk-JUAE4572.js";
20
20
 
21
21
  // src/components/CryptoTab/Crypto/CryptoWrapper.tsx
22
22
  import { useMemo as useMemo2 } from "react";
@@ -2,7 +2,7 @@ import {
2
2
  Dialog,
3
3
  DialogContent,
4
4
  DialogTitle
5
- } from "./chunk-5FKBVNC4.js";
5
+ } from "./chunk-JUAE4572.js";
6
6
 
7
7
  // src/modals/Iframe3ds.tsx
8
8
  import { useEffect } from "react";
@@ -9,7 +9,7 @@ var PaymentType = /* @__PURE__ */ ((PaymentType2) => {
9
9
  })(PaymentType || {});
10
10
 
11
11
  // package.json
12
- var version = "0.3.4";
12
+ var version = "0.4.0";
13
13
 
14
14
  // src/utils/logger.ts
15
15
  var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
@@ -533,10 +533,19 @@ var InfoBanner = (props) => {
533
533
  import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
534
534
  var Legal = () => {
535
535
  const { spreePayConfig } = useSpreePayConfig();
536
- return /* @__PURE__ */ jsxs2("p", { className: "text-center text-xs leading-5 font-medium text-(--secondary)", children: [
536
+ return /* @__PURE__ */ jsxs2("p", { className: "text-xs leading-5 font-medium text-(--secondary)", children: [
537
537
  "By clicking on the button below I acknowledge that I have read and accepted the",
538
538
  " ",
539
- /* @__PURE__ */ jsx4("a", { className: "underline", href: spreePayConfig?.termsConditionsUrl, target: "_blank", rel: "noreferrer", children: "Terms and Conditions" }),
539
+ /* @__PURE__ */ jsx4(
540
+ "a",
541
+ {
542
+ className: "whitespace-nowrap underline",
543
+ href: spreePayConfig?.termsConditionsUrl,
544
+ target: "_blank",
545
+ rel: "noreferrer",
546
+ children: "Terms and Conditions"
547
+ }
548
+ ),
540
549
  "."
541
550
  ] });
542
551
  };
@@ -6,7 +6,7 @@ import {
6
6
  useSpreePayEnv,
7
7
  useSpreePaymentMethod,
8
8
  useStaticConfig
9
- } from "./chunk-5FKBVNC4.js";
9
+ } from "./chunk-JUAE4572.js";
10
10
 
11
11
  // src/components/common/PointsSwitch.tsx
12
12
  import { useId } from "react";
@@ -60,11 +60,11 @@ function Switch({ className, ...props }) {
60
60
  }
61
61
 
62
62
  // src/utils/format.ts
63
- var formatUSD = (amount, currency = "USD") => {
63
+ var formatCurrency = (amount, currency = "USD") => {
64
64
  const formattedAmount = new Intl.NumberFormat("en-US", {
65
65
  currency,
66
66
  style: "currency",
67
- maximumFractionDigits: 5,
67
+ maximumFractionDigits: 2,
68
68
  minimumFractionDigits: 2
69
69
  }).format(amount);
70
70
  return formattedAmount;
@@ -93,7 +93,10 @@ var PointsSwitch = (props) => {
93
93
  const { disabled = false, value, onChange, message } = props;
94
94
  const { spreePayConfig } = useSpreePayConfig();
95
95
  const { appProps } = useStaticConfig();
96
+ const { currencyCode, exchangeRate, topUpLink, foreignCurrencyAmount } = appProps;
96
97
  const { balance } = useSlapiBalance();
98
+ const hasForeignCurrency = !!(currencyCode && exchangeRate && foreignCurrencyAmount);
99
+ const formatPointsValue = (usd) => hasForeignCurrency ? formatCurrency(usd / exchangeRate, currencyCode) : formatCurrency(usd);
97
100
  const id = useId();
98
101
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-6", children: [
99
102
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
@@ -114,15 +117,15 @@ var PointsSwitch = (props) => {
114
117
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5", children: [
115
118
  balance?.availablePoints ? /* @__PURE__ */ jsxs("p", { className: "flex-1 text-right text-sm font-medium text-(--brand-primary)", children: [
116
119
  formatPoints(balance.availablePoints, spreePayConfig?.pointsTitle),
117
- !!spreePayConfig?.pointsConversionRatio && /* @__PURE__ */ jsx3("span", { className: "text-(--secondary)", children: ` \xB7 ${formatUSD(balance.availablePoints * spreePayConfig.pointsConversionRatio)}` })
120
+ !!spreePayConfig?.pointsConversionRatio && /* @__PURE__ */ jsx3("span", { className: "text-(--secondary)", children: ` \xB7 ${formatPointsValue(balance.availablePoints * spreePayConfig.pointsConversionRatio)}` })
118
121
  ] }) : null,
119
- Boolean(appProps.topUpLink) && /* @__PURE__ */ jsx3(
122
+ Boolean(topUpLink) && /* @__PURE__ */ jsx3(
120
123
  "a",
121
124
  {
122
125
  className: "cursor-pointer rounded-full bg-(--s-brand) px-2 py-1.5 text-xs font-medium text-(--inverse) hover:bg-(--s-brand-hover)",
123
126
  rel: "noreferrer",
124
127
  target: "_blank",
125
- href: appProps.topUpLink,
128
+ href: topUpLink,
126
129
  children: "Top Up"
127
130
  }
128
131
  )
@@ -204,28 +207,45 @@ var getTransactionFee = (amount = 0, transactionFeePercentage) => {
204
207
  import { Fragment, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
205
208
  var CheckoutButton = ({ isLoggedIn }) => {
206
209
  const { appProps, staticConfig } = useStaticConfig();
207
- const { amount, onProcess, isProcessing, transactionFeePercentage } = appProps;
210
+ const {
211
+ amount,
212
+ onProcess,
213
+ isProcessing,
214
+ transactionFeePercentage,
215
+ currencyCode,
216
+ foreignCurrencyAmount,
217
+ exchangeRate
218
+ } = appProps;
208
219
  const { spreePayConfig } = useSpreePayConfig();
209
220
  const { tenantId, keycloakClientId } = useSpreePayEnv();
210
221
  const { selectedPaymentMethod, isInternalProcessing } = useSpreePaymentMethod();
211
- const { splitAmount, type, method } = selectedPaymentMethod;
212
- const usdAmount = getSplitAmount(amount ?? 0, splitAmount ?? 0, spreePayConfig?.pointsConversionRatio);
222
+ const { pointsAmount, type, method } = selectedPaymentMethod;
223
+ const usdAmount = getSplitAmount(amount ?? 0, pointsAmount ?? 0, spreePayConfig?.pointsConversionRatio);
213
224
  const isDisabled = !amount || !method && usdAmount !== 0 || !!isProcessing || isInternalProcessing || !isLoggedIn;
214
225
  const isCC = type === "CREDIT_CARD" /* CREDIT_CARD */;
215
226
  const isCrypto = type === "CRYPTO" /* CRYPTO */;
227
+ const hasForeignCurrency = !!(currencyCode && foreignCurrencyAmount && exchangeRate);
228
+ const formatCashWithFee = (usdCash, foreignBase) => {
229
+ const fee = getTransactionFee(usdCash, transactionFeePercentage);
230
+ if (hasForeignCurrency) {
231
+ const base = foreignBase ?? usdCash / exchangeRate;
232
+ return formatCurrency(base + fee / exchangeRate, currencyCode);
233
+ }
234
+ return formatCurrency(usdCash + fee);
235
+ };
216
236
  const getCheckoutContent = () => {
217
237
  if (!!isProcessing || isInternalProcessing) {
218
238
  return /* @__PURE__ */ jsx5(Spinner, { className: "inline", size: "sm" });
219
239
  }
220
240
  if (isCC && amount) {
221
- if (splitAmount && usdAmount) {
222
- return `Pay ${formatUSD(usdAmount + getTransactionFee(usdAmount, transactionFeePercentage))} + ${formatPoints(splitAmount, spreePayConfig?.pointsTitle)}`;
241
+ if (pointsAmount && usdAmount) {
242
+ return `Pay ${formatCashWithFee(usdAmount)} + ${formatPoints(pointsAmount, spreePayConfig?.pointsTitle)}`;
223
243
  }
224
244
  if (usdAmount) {
225
- return `Pay ${formatUSD(usdAmount + getTransactionFee(usdAmount, transactionFeePercentage))}`;
245
+ return `Pay ${formatCashWithFee(usdAmount, hasForeignCurrency ? foreignCurrencyAmount : void 0)}`;
226
246
  }
227
- if (splitAmount) {
228
- return `Pay ${formatPoints(splitAmount, spreePayConfig?.pointsTitle)}`;
247
+ if (pointsAmount) {
248
+ return `Pay ${formatPoints(pointsAmount, spreePayConfig?.pointsTitle)}`;
229
249
  }
230
250
  return "Checkout";
231
251
  }
@@ -272,7 +292,7 @@ export {
272
292
  getTransactionFee,
273
293
  getSplitAmount,
274
294
  useSlapiBalance,
275
- formatUSD,
295
+ formatCurrency,
276
296
  formatPoints,
277
297
  Label,
278
298
  PointsSwitch,
package/build/index.cjs CHANGED
@@ -68,7 +68,7 @@ var init_payments = __esm({
68
68
  var version;
69
69
  var init_package = __esm({
70
70
  "package.json"() {
71
- version = "0.3.4";
71
+ version = "0.4.0";
72
72
  }
73
73
  });
74
74
 
@@ -962,15 +962,15 @@ var init_spinner = __esm({
962
962
  });
963
963
 
964
964
  // src/utils/format.ts
965
- var formatUSD, formatPoints, formatCoin;
965
+ var formatCurrency, formatPoints, formatCoin;
966
966
  var init_format = __esm({
967
967
  "src/utils/format.ts"() {
968
968
  "use strict";
969
- formatUSD = (amount, currency = "USD") => {
969
+ formatCurrency = (amount, currency = "USD") => {
970
970
  const formattedAmount = new Intl.NumberFormat("en-US", {
971
971
  currency,
972
972
  style: "currency",
973
- maximumFractionDigits: 5,
973
+ maximumFractionDigits: 2,
974
974
  minimumFractionDigits: 2
975
975
  }).format(amount);
976
976
  return formattedAmount;
@@ -1135,7 +1135,10 @@ var init_PointsSwitch = __esm({
1135
1135
  const { disabled = false, value, onChange, message } = props;
1136
1136
  const { spreePayConfig } = useSpreePayConfig();
1137
1137
  const { appProps } = useStaticConfig();
1138
+ const { currencyCode, exchangeRate, topUpLink, foreignCurrencyAmount } = appProps;
1138
1139
  const { balance } = useSlapiBalance();
1140
+ const hasForeignCurrency = !!(currencyCode && exchangeRate && foreignCurrencyAmount);
1141
+ const formatPointsValue = (usd) => hasForeignCurrency ? formatCurrency(usd / exchangeRate, currencyCode) : formatCurrency(usd);
1139
1142
  const id = (0, import_react5.useId)();
1140
1143
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col gap-6", children: [
1141
1144
  /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center justify-between gap-3", children: [
@@ -1156,15 +1159,15 @@ var init_PointsSwitch = __esm({
1156
1159
  /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center gap-2.5", children: [
1157
1160
  balance?.availablePoints ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("p", { className: "flex-1 text-right text-sm font-medium text-(--brand-primary)", children: [
1158
1161
  formatPoints(balance.availablePoints, spreePayConfig?.pointsTitle),
1159
- !!spreePayConfig?.pointsConversionRatio && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-(--secondary)", children: ` \xB7 ${formatUSD(balance.availablePoints * spreePayConfig.pointsConversionRatio)}` })
1162
+ !!spreePayConfig?.pointsConversionRatio && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "text-(--secondary)", children: ` \xB7 ${formatPointsValue(balance.availablePoints * spreePayConfig.pointsConversionRatio)}` })
1160
1163
  ] }) : null,
1161
- Boolean(appProps.topUpLink) && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1164
+ Boolean(topUpLink) && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1162
1165
  "a",
1163
1166
  {
1164
1167
  className: "cursor-pointer rounded-full bg-(--s-brand) px-2 py-1.5 text-xs font-medium text-(--inverse) hover:bg-(--s-brand-hover)",
1165
1168
  rel: "noreferrer",
1166
1169
  target: "_blank",
1167
- href: appProps.topUpLink,
1170
+ href: topUpLink,
1168
1171
  children: "Top Up"
1169
1172
  }
1170
1173
  )
@@ -1185,10 +1188,19 @@ var init_Legal = __esm({
1185
1188
  import_jsx_runtime10 = require("react/jsx-runtime");
1186
1189
  Legal = () => {
1187
1190
  const { spreePayConfig } = useSpreePayConfig();
1188
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("p", { className: "text-center text-xs leading-5 font-medium text-(--secondary)", children: [
1191
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("p", { className: "text-xs leading-5 font-medium text-(--secondary)", children: [
1189
1192
  "By clicking on the button below I acknowledge that I have read and accepted the",
1190
1193
  " ",
1191
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("a", { className: "underline", href: spreePayConfig?.termsConditionsUrl, target: "_blank", rel: "noreferrer", children: "Terms and Conditions" }),
1194
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1195
+ "a",
1196
+ {
1197
+ className: "whitespace-nowrap underline",
1198
+ href: spreePayConfig?.termsConditionsUrl,
1199
+ target: "_blank",
1200
+ rel: "noreferrer",
1201
+ children: "Terms and Conditions"
1202
+ }
1203
+ ),
1192
1204
  "."
1193
1205
  ] });
1194
1206
  };
@@ -1221,28 +1233,45 @@ var init_CheckoutButton = __esm({
1221
1233
  import_jsx_runtime11 = require("react/jsx-runtime");
1222
1234
  CheckoutButton = ({ isLoggedIn }) => {
1223
1235
  const { appProps, staticConfig } = useStaticConfig();
1224
- const { amount, onProcess, isProcessing, transactionFeePercentage } = appProps;
1236
+ const {
1237
+ amount,
1238
+ onProcess,
1239
+ isProcessing,
1240
+ transactionFeePercentage,
1241
+ currencyCode,
1242
+ foreignCurrencyAmount,
1243
+ exchangeRate
1244
+ } = appProps;
1225
1245
  const { spreePayConfig } = useSpreePayConfig();
1226
1246
  const { tenantId, keycloakClientId } = useSpreePayEnv();
1227
1247
  const { selectedPaymentMethod, isInternalProcessing } = useSpreePaymentMethod();
1228
- const { splitAmount, type, method } = selectedPaymentMethod;
1229
- const usdAmount = getSplitAmount(amount ?? 0, splitAmount ?? 0, spreePayConfig?.pointsConversionRatio);
1248
+ const { pointsAmount, type, method } = selectedPaymentMethod;
1249
+ const usdAmount = getSplitAmount(amount ?? 0, pointsAmount ?? 0, spreePayConfig?.pointsConversionRatio);
1230
1250
  const isDisabled = !amount || !method && usdAmount !== 0 || !!isProcessing || isInternalProcessing || !isLoggedIn;
1231
1251
  const isCC = type === "CREDIT_CARD" /* CREDIT_CARD */;
1232
1252
  const isCrypto = type === "CRYPTO" /* CRYPTO */;
1253
+ const hasForeignCurrency = !!(currencyCode && foreignCurrencyAmount && exchangeRate);
1254
+ const formatCashWithFee = (usdCash, foreignBase) => {
1255
+ const fee = getTransactionFee(usdCash, transactionFeePercentage);
1256
+ if (hasForeignCurrency) {
1257
+ const base2 = foreignBase ?? usdCash / exchangeRate;
1258
+ return formatCurrency(base2 + fee / exchangeRate, currencyCode);
1259
+ }
1260
+ return formatCurrency(usdCash + fee);
1261
+ };
1233
1262
  const getCheckoutContent = () => {
1234
1263
  if (!!isProcessing || isInternalProcessing) {
1235
1264
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Spinner, { className: "inline", size: "sm" });
1236
1265
  }
1237
1266
  if (isCC && amount) {
1238
- if (splitAmount && usdAmount) {
1239
- return `Pay ${formatUSD(usdAmount + getTransactionFee(usdAmount, transactionFeePercentage))} + ${formatPoints(splitAmount, spreePayConfig?.pointsTitle)}`;
1267
+ if (pointsAmount && usdAmount) {
1268
+ return `Pay ${formatCashWithFee(usdAmount)} + ${formatPoints(pointsAmount, spreePayConfig?.pointsTitle)}`;
1240
1269
  }
1241
1270
  if (usdAmount) {
1242
- return `Pay ${formatUSD(usdAmount + getTransactionFee(usdAmount, transactionFeePercentage))}`;
1271
+ return `Pay ${formatCashWithFee(usdAmount, hasForeignCurrency ? foreignCurrencyAmount : void 0)}`;
1243
1272
  }
1244
- if (splitAmount) {
1245
- return `Pay ${formatPoints(splitAmount, spreePayConfig?.pointsTitle)}`;
1273
+ if (pointsAmount) {
1274
+ return `Pay ${formatPoints(pointsAmount, spreePayConfig?.pointsTitle)}`;
1246
1275
  }
1247
1276
  return "Checkout";
1248
1277
  }
@@ -3026,8 +3055,10 @@ var useSplitCardPayments = (mode = "web2") => {
3026
3055
 
3027
3056
  // src/hooks/useCards.ts
3028
3057
  var import_swr2 = __toESM(require("swr"), 1);
3058
+ init_SpreePayActionsContext();
3029
3059
  var useCards = () => {
3030
- const { data, isLoading, mutate } = (0, import_swr2.default)(`/v1/payments/cards`);
3060
+ const { origin } = useSpreePayEnv();
3061
+ const { data, isLoading, mutate } = (0, import_swr2.default)(`/v1/payments/cards?origin=${origin}`);
3031
3062
  return {
3032
3063
  cards: data?.data.filter((c) => c.active) || [],
3033
3064
  cardsIsLoading: isLoading,
@@ -3086,7 +3117,7 @@ var CardListItem = ({ card, isSelected, onSelect }) => {
3086
3117
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
3087
3118
  "div",
3088
3119
  {
3089
- className: cn("flex h-full w-full items-center justify-between rounded-r-sm px-3", {
3120
+ className: cn("flex h-full w-full items-center justify-between rounded-r-sm pr-2 pl-3", {
3090
3121
  "border-(--primary)": isSelected
3091
3122
  }),
3092
3123
  children: [
@@ -3392,6 +3423,7 @@ init_common();
3392
3423
  var import_react11 = require("react");
3393
3424
  var import_airkit2 = require("@mocanetwork/airkit");
3394
3425
  init_SpreePayActionsContext();
3426
+ init_StaticConfigContext();
3395
3427
  init_useSlapiBalance();
3396
3428
  init_useSpreePayConfig();
3397
3429
  init_format();
@@ -4395,17 +4427,27 @@ var PointsSelector = (props) => {
4395
4427
  const { appProps } = useStaticConfig();
4396
4428
  const { spreePayConfig } = useSpreePayConfig();
4397
4429
  const { pointsConversionRatio } = spreePayConfig || {};
4430
+ const { currencyCode, foreignCurrencyAmount, exchangeRate, amount, transactionFeePercentage } = appProps;
4431
+ const hasForeignCurrency = !!(currencyCode && foreignCurrencyAmount && exchangeRate);
4398
4432
  const min = 0;
4399
- const maxByAmount = pointsConversionRatio && pointsConversionRatio > 0 ? (appProps.amount ?? 0) / pointsConversionRatio : 0;
4433
+ const maxByAmount = pointsConversionRatio && pointsConversionRatio > 0 ? (amount ?? 0) / pointsConversionRatio : 0;
4400
4434
  const max = Math.min(maxByAmount, balance?.availablePoints ?? 0);
4401
4435
  const step = 10;
4402
4436
  const [splitTokens, setSplitTokens] = (0, import_react10.useState)(0);
4403
- const usdAmount = getSplitAmount(appProps.amount ?? 0, splitTokens, pointsConversionRatio);
4437
+ const usdAmount = getSplitAmount(amount ?? 0, splitTokens, pointsConversionRatio);
4438
+ const pointsValue = String(Math.round(splitTokens));
4439
+ const usdWithFee = usdAmount + getTransactionFee(usdAmount, transactionFeePercentage);
4440
+ const cashDisplayValue = hasForeignCurrency ? formatCurrency(usdWithFee / exchangeRate, currencyCode) : formatCurrency(usdWithFee);
4404
4441
  const handleCommit = (value) => {
4405
- setSelectedPaymentMethod({ ...selectedPaymentMethod, splitAmount: value });
4442
+ const committedUsd = getSplitAmount(amount ?? 0, value, pointsConversionRatio);
4443
+ const committedUsdWithFee = committedUsd + getTransactionFee(committedUsd, transactionFeePercentage);
4444
+ setSelectedPaymentMethod({
4445
+ ...selectedPaymentMethod,
4446
+ pointsAmount: value,
4447
+ amount: parseFloat(committedUsdWithFee.toFixed(2)),
4448
+ foreignCurrencyAmount: hasForeignCurrency ? parseFloat((committedUsdWithFee / exchangeRate).toFixed(2)) : void 0
4449
+ });
4406
4450
  };
4407
- const pointsValue = String(Math.round(splitTokens));
4408
- const usdValue = formatUSD(usdAmount + getTransactionFee(usdAmount, appProps.transactionFeePercentage));
4409
4451
  return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
4410
4452
  "button",
4411
4453
  {
@@ -4465,10 +4507,10 @@ var PointsSelector = (props) => {
4465
4507
  Input,
4466
4508
  {
4467
4509
  readOnly: true,
4468
- name: "usd amount",
4469
- value: usdValue,
4510
+ name: "card amount",
4511
+ value: cashDisplayValue,
4470
4512
  className: "sm:text-md min-w-[50px] bg-(--s-default) px-1 text-center text-xs font-medium sm:min-w-[100px] sm:px-2",
4471
- style: { width: `${usdValue.length}ch` },
4513
+ style: { width: `${cashDisplayValue.length}ch` },
4472
4514
  onClick: (e) => e.stopPropagation()
4473
4515
  }
4474
4516
  ),
@@ -4487,10 +4529,14 @@ var SplitBlock = (props) => {
4487
4529
  const { onToggle, isSelected, onSelect } = props;
4488
4530
  const { balance, isBalanceLoading } = useSlapiBalance();
4489
4531
  const { spreePayConfig } = useSpreePayConfig();
4532
+ const { appProps } = useStaticConfig();
4533
+ const { currencyCode, exchangeRate, foreignCurrencyAmount } = appProps;
4490
4534
  const [address, setAddress] = (0, import_react11.useState)(null);
4491
4535
  const [walletReady, setWalletReady] = (0, import_react11.useState)(false);
4492
4536
  const { pointsConversionRatio, pointsTitle } = spreePayConfig || {};
4493
4537
  const { useWeb3Points, environment } = useSpreePayEnv();
4538
+ const hasForeignCurrency = !!(currencyCode && exchangeRate && foreignCurrencyAmount);
4539
+ const formatPointsValue = (usd) => hasForeignCurrency ? formatCurrency(usd / exchangeRate, currencyCode) : formatCurrency(usd);
4494
4540
  const prevPointsChainRef = (0, import_react11.useRef)(spreePayConfig?.pointsChain);
4495
4541
  const initWallet = (0, import_react11.useCallback)(
4496
4542
  async (pointsChain) => {
@@ -4535,7 +4581,7 @@ var SplitBlock = (props) => {
4535
4581
  " ",
4536
4582
  formatPoints(balance.availablePoints, pointsTitle),
4537
4583
  " ",
4538
- pointsConversionRatio && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "text-(--secondary)", children: formatUSD(balance.availablePoints * pointsConversionRatio) })
4584
+ pointsConversionRatio && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "text-(--secondary)", children: formatPointsValue(balance.availablePoints * pointsConversionRatio) })
4539
4585
  ] }) : null }),
4540
4586
  isBalanceLoading ? /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "h-4 w-6 animate-pulse bg-(--s-secondary)" }) : !balance?.availablePoints && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("p", { className: "text-sm font-medium text-(--brand-primary)", children: "No points available" }),
4541
4587
  address && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("div", { className: "text-sm font-medium text-(--brand-primary)", children: address.length > 8 ? `${address.slice(0, 4)}...${address.slice(-4)}` : address })
@@ -4554,7 +4600,7 @@ var Points = () => {
4554
4600
  setUsePoints(enabled);
4555
4601
  if (!enabled) {
4556
4602
  setSelectedPointsType(null);
4557
- setSelectedPaymentMethod({ ...selectedPaymentMethod, splitAmount: void 0 });
4603
+ setSelectedPaymentMethod({ ...selectedPaymentMethod, pointsAmount: void 0 });
4558
4604
  }
4559
4605
  };
4560
4606
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
@@ -4596,7 +4642,7 @@ var CreditCardTab = ({ isLoggedIn }) => {
4596
4642
  async (data) => {
4597
4643
  try {
4598
4644
  let res = null;
4599
- const pointsAmount = selectedPaymentMethod.splitAmount ?? 0;
4645
+ const pointsAmount = selectedPaymentMethod.pointsAmount ?? 0;
4600
4646
  const usdAmount = getSplitAmount(appProps.amount ?? 0, pointsAmount, spreePayConfig?.pointsConversionRatio);
4601
4647
  if (usdAmount && pointsAmount) {
4602
4648
  res = await splitPayment({ ...data, points: pointsAmount });
package/build/index.css CHANGED
@@ -767,6 +767,9 @@
767
767
  .sl-spreepay .pt-5 {
768
768
  padding-top: calc(var(--spacing) * 5);
769
769
  }
770
+ .sl-spreepay .pr-2 {
771
+ padding-right: calc(var(--spacing) * 2);
772
+ }
770
773
  .sl-spreepay .pb-2 {
771
774
  padding-bottom: calc(var(--spacing) * 2);
772
775
  }
@@ -782,6 +785,9 @@
782
785
  .sl-spreepay .pl-1\.5 {
783
786
  padding-left: calc(var(--spacing) * 1.5);
784
787
  }
788
+ .sl-spreepay .pl-3 {
789
+ padding-left: calc(var(--spacing) * 3);
790
+ }
785
791
  .sl-spreepay .text-center {
786
792
  text-align: center;
787
793
  }
package/build/index.d.cts CHANGED
@@ -1,16 +1,24 @@
1
- import { FC, ReactNode } from 'react';
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
2
3
 
3
- type SpreePayProps = {
4
- onProcess?: () => Promise<void>;
5
- amount?: number;
6
- className?: string;
7
- isProcessing?: boolean;
8
- transactionFeePercentage?: number;
9
- disabledPoints?: boolean;
10
- topUpLink?: string;
11
- };
4
+ type SpreePayProps = Partial<{
5
+ onProcess: () => Promise<void>;
6
+ /** amount in USD (e.g. $10.00 → 10 USD) */
7
+ amount: number;
8
+ className: string;
9
+ isProcessing: boolean;
10
+ transactionFeePercentage: number;
11
+ disabledPoints: boolean;
12
+ topUpLink: string;
13
+ /** ISO 4217 display currency (e.g. "AUD"). Requires foreignCurrencyAmount + exchangeRate. */
14
+ currencyCode: string;
15
+ /** Order total in the display currency. */
16
+ foreignCurrencyAmount: number;
17
+ /** 1 unit of currencyCode in USD (e.g. 1 AUD = 0.65 USD → 0.65). */
18
+ exchangeRate: number;
19
+ }>;
12
20
 
13
- declare const SpreePay: FC<SpreePayProps>;
21
+ declare const SpreePay: (props: SpreePayProps) => react_jsx_runtime.JSX.Element;
14
22
 
15
23
  declare const useCapture3DS: (searchParams: Record<string, string | null>) => void;
16
24
 
@@ -79,17 +87,23 @@ type NewCard = Pick<Card, 'expireMonth' | 'expireYear' | 'lastFourNumbers' | 'sc
79
87
  type CardPaymentMethod = {
80
88
  method: NewCard | Card | null;
81
89
  type: PaymentType.CREDIT_CARD;
82
- splitAmount?: number;
90
+ pointsAmount?: number;
91
+ amount?: number;
92
+ foreignCurrencyAmount?: number;
83
93
  };
84
94
  type CryptoPaymentMethod = {
85
95
  method: Coin | NativeCoin | null;
86
96
  type: PaymentType.CRYPTO;
87
- splitAmount?: number;
97
+ pointsAmount?: number;
98
+ amount?: number;
99
+ foreignCurrencyAmount?: number;
88
100
  };
89
101
  type CDCPaymentMethod = {
90
102
  method: Coin | NativeCoin | null;
91
103
  type: PaymentType.CDC;
92
- splitAmount?: number;
104
+ pointsAmount?: number;
105
+ amount?: number;
106
+ foreignCurrencyAmount?: number;
93
107
  };
94
108
  type SelectedPaymentMethod = CardPaymentMethod | CryptoPaymentMethod | CDCPaymentMethod;
95
109
  declare enum PaymentType {
@@ -126,7 +140,7 @@ type SpreePayProviderProps = {
126
140
  children: ReactNode;
127
141
  env: ENV;
128
142
  };
129
- declare const SpreePayProvider: FC<SpreePayProviderProps>;
143
+ declare const SpreePayProvider: ({ children, env }: SpreePayProviderProps) => react_jsx_runtime.JSX.Element;
130
144
  declare const useSpreePay: () => {
131
145
  process: ProcessFn;
132
146
  isProcessing: boolean;
package/build/index.d.ts CHANGED
@@ -1,16 +1,24 @@
1
- import { FC, ReactNode } from 'react';
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
2
3
 
3
- type SpreePayProps = {
4
- onProcess?: () => Promise<void>;
5
- amount?: number;
6
- className?: string;
7
- isProcessing?: boolean;
8
- transactionFeePercentage?: number;
9
- disabledPoints?: boolean;
10
- topUpLink?: string;
11
- };
4
+ type SpreePayProps = Partial<{
5
+ onProcess: () => Promise<void>;
6
+ /** amount in USD (e.g. $10.00 → 10 USD) */
7
+ amount: number;
8
+ className: string;
9
+ isProcessing: boolean;
10
+ transactionFeePercentage: number;
11
+ disabledPoints: boolean;
12
+ topUpLink: string;
13
+ /** ISO 4217 display currency (e.g. "AUD"). Requires foreignCurrencyAmount + exchangeRate. */
14
+ currencyCode: string;
15
+ /** Order total in the display currency. */
16
+ foreignCurrencyAmount: number;
17
+ /** 1 unit of currencyCode in USD (e.g. 1 AUD = 0.65 USD → 0.65). */
18
+ exchangeRate: number;
19
+ }>;
12
20
 
13
- declare const SpreePay: FC<SpreePayProps>;
21
+ declare const SpreePay: (props: SpreePayProps) => react_jsx_runtime.JSX.Element;
14
22
 
15
23
  declare const useCapture3DS: (searchParams: Record<string, string | null>) => void;
16
24
 
@@ -79,17 +87,23 @@ type NewCard = Pick<Card, 'expireMonth' | 'expireYear' | 'lastFourNumbers' | 'sc
79
87
  type CardPaymentMethod = {
80
88
  method: NewCard | Card | null;
81
89
  type: PaymentType.CREDIT_CARD;
82
- splitAmount?: number;
90
+ pointsAmount?: number;
91
+ amount?: number;
92
+ foreignCurrencyAmount?: number;
83
93
  };
84
94
  type CryptoPaymentMethod = {
85
95
  method: Coin | NativeCoin | null;
86
96
  type: PaymentType.CRYPTO;
87
- splitAmount?: number;
97
+ pointsAmount?: number;
98
+ amount?: number;
99
+ foreignCurrencyAmount?: number;
88
100
  };
89
101
  type CDCPaymentMethod = {
90
102
  method: Coin | NativeCoin | null;
91
103
  type: PaymentType.CDC;
92
- splitAmount?: number;
104
+ pointsAmount?: number;
105
+ amount?: number;
106
+ foreignCurrencyAmount?: number;
93
107
  };
94
108
  type SelectedPaymentMethod = CardPaymentMethod | CryptoPaymentMethod | CDCPaymentMethod;
95
109
  declare enum PaymentType {
@@ -126,7 +140,7 @@ type SpreePayProviderProps = {
126
140
  children: ReactNode;
127
141
  env: ENV;
128
142
  };
129
- declare const SpreePayProvider: FC<SpreePayProviderProps>;
143
+ declare const SpreePayProvider: ({ children, env }: SpreePayProviderProps) => react_jsx_runtime.JSX.Element;
130
144
  declare const useSpreePay: () => {
131
145
  process: ProcessFn;
132
146
  isProcessing: boolean;
package/build/index.js CHANGED
@@ -3,15 +3,15 @@ import {
3
3
  Label,
4
4
  PointsSwitch,
5
5
  cn as cn2,
6
+ formatCurrency,
6
7
  formatPoints,
7
- formatUSD,
8
8
  getSplitAmount,
9
9
  getTransactionFee,
10
10
  useSlapiBalance
11
- } from "./chunk-GO3SK2EV.js";
11
+ } from "./chunk-STM24EZ3.js";
12
12
  import {
13
13
  Iframe3ds
14
- } from "./chunk-ER5YKU2O.js";
14
+ } from "./chunk-BGLP7QYP.js";
15
15
  import {
16
16
  InfoBanner,
17
17
  LogLevel,
@@ -31,7 +31,7 @@ import {
31
31
  useSpreePayRegister,
32
32
  useSpreePaymentMethod,
33
33
  useStaticConfig
34
- } from "./chunk-5FKBVNC4.js";
34
+ } from "./chunk-JUAE4572.js";
35
35
 
36
36
  // src/SpreePay.tsx
37
37
  import { useCallback as useCallback7, useEffect as useEffect8, useMemo as useMemo7, useState as useState10 } from "react";
@@ -538,7 +538,8 @@ var useSplitCardPayments = (mode = "web2") => {
538
538
  // src/hooks/useCards.ts
539
539
  import useSWR from "swr";
540
540
  var useCards = () => {
541
- const { data, isLoading, mutate } = useSWR(`/v1/payments/cards`);
541
+ const { origin } = useSpreePayEnv();
542
+ const { data, isLoading, mutate } = useSWR(`/v1/payments/cards?origin=${origin}`);
542
543
  return {
543
544
  cards: data?.data.filter((c) => c.active) || [],
544
545
  cardsIsLoading: isLoading,
@@ -586,7 +587,7 @@ var CardListItem = ({ card, isSelected, onSelect }) => {
586
587
  /* @__PURE__ */ jsxs(
587
588
  "div",
588
589
  {
589
- className: cn("flex h-full w-full items-center justify-between rounded-r-sm px-3", {
590
+ className: cn("flex h-full w-full items-center justify-between rounded-r-sm pr-2 pl-3", {
590
591
  "border-(--primary)": isSelected
591
592
  }),
592
593
  children: [
@@ -1872,17 +1873,27 @@ var PointsSelector = (props) => {
1872
1873
  const { appProps } = useStaticConfig();
1873
1874
  const { spreePayConfig } = useSpreePayConfig();
1874
1875
  const { pointsConversionRatio } = spreePayConfig || {};
1876
+ const { currencyCode, foreignCurrencyAmount, exchangeRate, amount, transactionFeePercentage } = appProps;
1877
+ const hasForeignCurrency = !!(currencyCode && foreignCurrencyAmount && exchangeRate);
1875
1878
  const min = 0;
1876
- const maxByAmount = pointsConversionRatio && pointsConversionRatio > 0 ? (appProps.amount ?? 0) / pointsConversionRatio : 0;
1879
+ const maxByAmount = pointsConversionRatio && pointsConversionRatio > 0 ? (amount ?? 0) / pointsConversionRatio : 0;
1877
1880
  const max = Math.min(maxByAmount, balance?.availablePoints ?? 0);
1878
1881
  const step = 10;
1879
1882
  const [splitTokens, setSplitTokens] = useState6(0);
1880
- const usdAmount = getSplitAmount(appProps.amount ?? 0, splitTokens, pointsConversionRatio);
1883
+ const usdAmount = getSplitAmount(amount ?? 0, splitTokens, pointsConversionRatio);
1884
+ const pointsValue = String(Math.round(splitTokens));
1885
+ const usdWithFee = usdAmount + getTransactionFee(usdAmount, transactionFeePercentage);
1886
+ const cashDisplayValue = hasForeignCurrency ? formatCurrency(usdWithFee / exchangeRate, currencyCode) : formatCurrency(usdWithFee);
1881
1887
  const handleCommit = (value) => {
1882
- setSelectedPaymentMethod({ ...selectedPaymentMethod, splitAmount: value });
1888
+ const committedUsd = getSplitAmount(amount ?? 0, value, pointsConversionRatio);
1889
+ const committedUsdWithFee = committedUsd + getTransactionFee(committedUsd, transactionFeePercentage);
1890
+ setSelectedPaymentMethod({
1891
+ ...selectedPaymentMethod,
1892
+ pointsAmount: value,
1893
+ amount: parseFloat(committedUsdWithFee.toFixed(2)),
1894
+ foreignCurrencyAmount: hasForeignCurrency ? parseFloat((committedUsdWithFee / exchangeRate).toFixed(2)) : void 0
1895
+ });
1883
1896
  };
1884
- const pointsValue = String(Math.round(splitTokens));
1885
- const usdValue = formatUSD(usdAmount + getTransactionFee(usdAmount, appProps.transactionFeePercentage));
1886
1897
  return /* @__PURE__ */ jsxs6(
1887
1898
  "button",
1888
1899
  {
@@ -1942,10 +1953,10 @@ var PointsSelector = (props) => {
1942
1953
  Input,
1943
1954
  {
1944
1955
  readOnly: true,
1945
- name: "usd amount",
1946
- value: usdValue,
1956
+ name: "card amount",
1957
+ value: cashDisplayValue,
1947
1958
  className: "sm:text-md min-w-[50px] bg-(--s-default) px-1 text-center text-xs font-medium sm:min-w-[100px] sm:px-2",
1948
- style: { width: `${usdValue.length}ch` },
1959
+ style: { width: `${cashDisplayValue.length}ch` },
1949
1960
  onClick: (e) => e.stopPropagation()
1950
1961
  }
1951
1962
  ),
@@ -1964,10 +1975,14 @@ var SplitBlock = (props) => {
1964
1975
  const { onToggle, isSelected, onSelect } = props;
1965
1976
  const { balance, isBalanceLoading } = useSlapiBalance();
1966
1977
  const { spreePayConfig } = useSpreePayConfig();
1978
+ const { appProps } = useStaticConfig();
1979
+ const { currencyCode, exchangeRate, foreignCurrencyAmount } = appProps;
1967
1980
  const [address, setAddress] = useState7(null);
1968
1981
  const [walletReady, setWalletReady] = useState7(false);
1969
1982
  const { pointsConversionRatio, pointsTitle } = spreePayConfig || {};
1970
1983
  const { useWeb3Points, environment } = useSpreePayEnv();
1984
+ const hasForeignCurrency = !!(currencyCode && exchangeRate && foreignCurrencyAmount);
1985
+ const formatPointsValue = (usd) => hasForeignCurrency ? formatCurrency(usd / exchangeRate, currencyCode) : formatCurrency(usd);
1971
1986
  const prevPointsChainRef = useRef6(spreePayConfig?.pointsChain);
1972
1987
  const initWallet = useCallback4(
1973
1988
  async (pointsChain) => {
@@ -2012,7 +2027,7 @@ var SplitBlock = (props) => {
2012
2027
  " ",
2013
2028
  formatPoints(balance.availablePoints, pointsTitle),
2014
2029
  " ",
2015
- pointsConversionRatio && /* @__PURE__ */ jsx14("span", { className: "text-(--secondary)", children: formatUSD(balance.availablePoints * pointsConversionRatio) })
2030
+ pointsConversionRatio && /* @__PURE__ */ jsx14("span", { className: "text-(--secondary)", children: formatPointsValue(balance.availablePoints * pointsConversionRatio) })
2016
2031
  ] }) : null }),
2017
2032
  isBalanceLoading ? /* @__PURE__ */ jsx14("div", { className: "h-4 w-6 animate-pulse bg-(--s-secondary)" }) : !balance?.availablePoints && /* @__PURE__ */ jsx14("p", { className: "text-sm font-medium text-(--brand-primary)", children: "No points available" }),
2018
2033
  address && /* @__PURE__ */ jsx14("div", { className: "text-sm font-medium text-(--brand-primary)", children: address.length > 8 ? `${address.slice(0, 4)}...${address.slice(-4)}` : address })
@@ -2031,7 +2046,7 @@ var Points = () => {
2031
2046
  setUsePoints(enabled);
2032
2047
  if (!enabled) {
2033
2048
  setSelectedPointsType(null);
2034
- setSelectedPaymentMethod({ ...selectedPaymentMethod, splitAmount: void 0 });
2049
+ setSelectedPaymentMethod({ ...selectedPaymentMethod, pointsAmount: void 0 });
2035
2050
  }
2036
2051
  };
2037
2052
  return /* @__PURE__ */ jsxs8(Fragment3, { children: [
@@ -2073,7 +2088,7 @@ var CreditCardTab = ({ isLoggedIn }) => {
2073
2088
  async (data) => {
2074
2089
  try {
2075
2090
  let res = null;
2076
- const pointsAmount = selectedPaymentMethod.splitAmount ?? 0;
2091
+ const pointsAmount = selectedPaymentMethod.pointsAmount ?? 0;
2077
2092
  const usdAmount = getSplitAmount(appProps.amount ?? 0, pointsAmount, spreePayConfig?.pointsConversionRatio);
2078
2093
  if (usdAmount && pointsAmount) {
2079
2094
  res = await splitPayment({ ...data, points: pointsAmount });
@@ -2190,9 +2205,9 @@ var TabButtons = (props) => {
2190
2205
 
2191
2206
  // src/SpreePayContent.tsx
2192
2207
  import { jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
2193
- var CryptoTab = lazy(() => import("./CryptoTab-PJIZU5AQ.js").then((module) => ({ default: module.CryptoTab })));
2208
+ var CryptoTab = lazy(() => import("./CryptoTab-AK7WPMBJ.js").then((module) => ({ default: module.CryptoTab })));
2194
2209
  var CryptoComTab = lazy(
2195
- () => import("./CryptoComTab-BZZUG4BL.js").then((module) => ({ default: module.CryptoComTab }))
2210
+ () => import("./CryptoComTab-QOGLM3EB.js").then((module) => ({ default: module.CryptoComTab }))
2196
2211
  );
2197
2212
  var TabLoadingFallback = () => /* @__PURE__ */ jsx18("div", { className: "flex items-center justify-center px-5 py-8 md:px-7", children: /* @__PURE__ */ jsxs11("div", { className: "flex flex-col items-center gap-3", children: [
2198
2213
  /* @__PURE__ */ jsx18("div", { className: "h-8 w-8 animate-spin rounded-full border-4 border-(--border-component-specific-card) border-t-(--brand-primary)" }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superlogic/spree-pay",
3
- "version": "0.3.4",
3
+ "version": "0.4.0",
4
4
  "description": "Spree-pay React component and utilities",
5
5
  "private": false,
6
6
  "type": "module",