@superlogic/spree-pay 0.3.5 → 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-D23MABH2.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-KQ25SXYH.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-FHY6YBXC.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-KQ25SXYH.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-KQ25SXYH.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.5";
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-KQ25SXYH.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.5";
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
  }
@@ -3088,7 +3117,7 @@ var CardListItem = ({ card, isSelected, onSelect }) => {
3088
3117
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
3089
3118
  "div",
3090
3119
  {
3091
- 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", {
3092
3121
  "border-(--primary)": isSelected
3093
3122
  }),
3094
3123
  children: [
@@ -3394,6 +3423,7 @@ init_common();
3394
3423
  var import_react11 = require("react");
3395
3424
  var import_airkit2 = require("@mocanetwork/airkit");
3396
3425
  init_SpreePayActionsContext();
3426
+ init_StaticConfigContext();
3397
3427
  init_useSlapiBalance();
3398
3428
  init_useSpreePayConfig();
3399
3429
  init_format();
@@ -4397,17 +4427,27 @@ var PointsSelector = (props) => {
4397
4427
  const { appProps } = useStaticConfig();
4398
4428
  const { spreePayConfig } = useSpreePayConfig();
4399
4429
  const { pointsConversionRatio } = spreePayConfig || {};
4430
+ const { currencyCode, foreignCurrencyAmount, exchangeRate, amount, transactionFeePercentage } = appProps;
4431
+ const hasForeignCurrency = !!(currencyCode && foreignCurrencyAmount && exchangeRate);
4400
4432
  const min = 0;
4401
- const maxByAmount = pointsConversionRatio && pointsConversionRatio > 0 ? (appProps.amount ?? 0) / pointsConversionRatio : 0;
4433
+ const maxByAmount = pointsConversionRatio && pointsConversionRatio > 0 ? (amount ?? 0) / pointsConversionRatio : 0;
4402
4434
  const max = Math.min(maxByAmount, balance?.availablePoints ?? 0);
4403
4435
  const step = 10;
4404
4436
  const [splitTokens, setSplitTokens] = (0, import_react10.useState)(0);
4405
- 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);
4406
4441
  const handleCommit = (value) => {
4407
- 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
+ });
4408
4450
  };
4409
- const pointsValue = String(Math.round(splitTokens));
4410
- const usdValue = formatUSD(usdAmount + getTransactionFee(usdAmount, appProps.transactionFeePercentage));
4411
4451
  return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
4412
4452
  "button",
4413
4453
  {
@@ -4467,10 +4507,10 @@ var PointsSelector = (props) => {
4467
4507
  Input,
4468
4508
  {
4469
4509
  readOnly: true,
4470
- name: "usd amount",
4471
- value: usdValue,
4510
+ name: "card amount",
4511
+ value: cashDisplayValue,
4472
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",
4473
- style: { width: `${usdValue.length}ch` },
4513
+ style: { width: `${cashDisplayValue.length}ch` },
4474
4514
  onClick: (e) => e.stopPropagation()
4475
4515
  }
4476
4516
  ),
@@ -4489,10 +4529,14 @@ var SplitBlock = (props) => {
4489
4529
  const { onToggle, isSelected, onSelect } = props;
4490
4530
  const { balance, isBalanceLoading } = useSlapiBalance();
4491
4531
  const { spreePayConfig } = useSpreePayConfig();
4532
+ const { appProps } = useStaticConfig();
4533
+ const { currencyCode, exchangeRate, foreignCurrencyAmount } = appProps;
4492
4534
  const [address, setAddress] = (0, import_react11.useState)(null);
4493
4535
  const [walletReady, setWalletReady] = (0, import_react11.useState)(false);
4494
4536
  const { pointsConversionRatio, pointsTitle } = spreePayConfig || {};
4495
4537
  const { useWeb3Points, environment } = useSpreePayEnv();
4538
+ const hasForeignCurrency = !!(currencyCode && exchangeRate && foreignCurrencyAmount);
4539
+ const formatPointsValue = (usd) => hasForeignCurrency ? formatCurrency(usd / exchangeRate, currencyCode) : formatCurrency(usd);
4496
4540
  const prevPointsChainRef = (0, import_react11.useRef)(spreePayConfig?.pointsChain);
4497
4541
  const initWallet = (0, import_react11.useCallback)(
4498
4542
  async (pointsChain) => {
@@ -4537,7 +4581,7 @@ var SplitBlock = (props) => {
4537
4581
  " ",
4538
4582
  formatPoints(balance.availablePoints, pointsTitle),
4539
4583
  " ",
4540
- 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) })
4541
4585
  ] }) : null }),
4542
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" }),
4543
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 })
@@ -4556,7 +4600,7 @@ var Points = () => {
4556
4600
  setUsePoints(enabled);
4557
4601
  if (!enabled) {
4558
4602
  setSelectedPointsType(null);
4559
- setSelectedPaymentMethod({ ...selectedPaymentMethod, splitAmount: void 0 });
4603
+ setSelectedPaymentMethod({ ...selectedPaymentMethod, pointsAmount: void 0 });
4560
4604
  }
4561
4605
  };
4562
4606
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
@@ -4598,7 +4642,7 @@ var CreditCardTab = ({ isLoggedIn }) => {
4598
4642
  async (data) => {
4599
4643
  try {
4600
4644
  let res = null;
4601
- const pointsAmount = selectedPaymentMethod.splitAmount ?? 0;
4645
+ const pointsAmount = selectedPaymentMethod.pointsAmount ?? 0;
4602
4646
  const usdAmount = getSplitAmount(appProps.amount ?? 0, pointsAmount, spreePayConfig?.pointsConversionRatio);
4603
4647
  if (usdAmount && pointsAmount) {
4604
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-FHY6YBXC.js";
11
+ } from "./chunk-STM24EZ3.js";
12
12
  import {
13
13
  Iframe3ds
14
- } from "./chunk-D23MABH2.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-KQ25SXYH.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";
@@ -587,7 +587,7 @@ var CardListItem = ({ card, isSelected, onSelect }) => {
587
587
  /* @__PURE__ */ jsxs(
588
588
  "div",
589
589
  {
590
- 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", {
591
591
  "border-(--primary)": isSelected
592
592
  }),
593
593
  children: [
@@ -1873,17 +1873,27 @@ var PointsSelector = (props) => {
1873
1873
  const { appProps } = useStaticConfig();
1874
1874
  const { spreePayConfig } = useSpreePayConfig();
1875
1875
  const { pointsConversionRatio } = spreePayConfig || {};
1876
+ const { currencyCode, foreignCurrencyAmount, exchangeRate, amount, transactionFeePercentage } = appProps;
1877
+ const hasForeignCurrency = !!(currencyCode && foreignCurrencyAmount && exchangeRate);
1876
1878
  const min = 0;
1877
- const maxByAmount = pointsConversionRatio && pointsConversionRatio > 0 ? (appProps.amount ?? 0) / pointsConversionRatio : 0;
1879
+ const maxByAmount = pointsConversionRatio && pointsConversionRatio > 0 ? (amount ?? 0) / pointsConversionRatio : 0;
1878
1880
  const max = Math.min(maxByAmount, balance?.availablePoints ?? 0);
1879
1881
  const step = 10;
1880
1882
  const [splitTokens, setSplitTokens] = useState6(0);
1881
- 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);
1882
1887
  const handleCommit = (value) => {
1883
- 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
+ });
1884
1896
  };
1885
- const pointsValue = String(Math.round(splitTokens));
1886
- const usdValue = formatUSD(usdAmount + getTransactionFee(usdAmount, appProps.transactionFeePercentage));
1887
1897
  return /* @__PURE__ */ jsxs6(
1888
1898
  "button",
1889
1899
  {
@@ -1943,10 +1953,10 @@ var PointsSelector = (props) => {
1943
1953
  Input,
1944
1954
  {
1945
1955
  readOnly: true,
1946
- name: "usd amount",
1947
- value: usdValue,
1956
+ name: "card amount",
1957
+ value: cashDisplayValue,
1948
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",
1949
- style: { width: `${usdValue.length}ch` },
1959
+ style: { width: `${cashDisplayValue.length}ch` },
1950
1960
  onClick: (e) => e.stopPropagation()
1951
1961
  }
1952
1962
  ),
@@ -1965,10 +1975,14 @@ var SplitBlock = (props) => {
1965
1975
  const { onToggle, isSelected, onSelect } = props;
1966
1976
  const { balance, isBalanceLoading } = useSlapiBalance();
1967
1977
  const { spreePayConfig } = useSpreePayConfig();
1978
+ const { appProps } = useStaticConfig();
1979
+ const { currencyCode, exchangeRate, foreignCurrencyAmount } = appProps;
1968
1980
  const [address, setAddress] = useState7(null);
1969
1981
  const [walletReady, setWalletReady] = useState7(false);
1970
1982
  const { pointsConversionRatio, pointsTitle } = spreePayConfig || {};
1971
1983
  const { useWeb3Points, environment } = useSpreePayEnv();
1984
+ const hasForeignCurrency = !!(currencyCode && exchangeRate && foreignCurrencyAmount);
1985
+ const formatPointsValue = (usd) => hasForeignCurrency ? formatCurrency(usd / exchangeRate, currencyCode) : formatCurrency(usd);
1972
1986
  const prevPointsChainRef = useRef6(spreePayConfig?.pointsChain);
1973
1987
  const initWallet = useCallback4(
1974
1988
  async (pointsChain) => {
@@ -2013,7 +2027,7 @@ var SplitBlock = (props) => {
2013
2027
  " ",
2014
2028
  formatPoints(balance.availablePoints, pointsTitle),
2015
2029
  " ",
2016
- 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) })
2017
2031
  ] }) : null }),
2018
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" }),
2019
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 })
@@ -2032,7 +2046,7 @@ var Points = () => {
2032
2046
  setUsePoints(enabled);
2033
2047
  if (!enabled) {
2034
2048
  setSelectedPointsType(null);
2035
- setSelectedPaymentMethod({ ...selectedPaymentMethod, splitAmount: void 0 });
2049
+ setSelectedPaymentMethod({ ...selectedPaymentMethod, pointsAmount: void 0 });
2036
2050
  }
2037
2051
  };
2038
2052
  return /* @__PURE__ */ jsxs8(Fragment3, { children: [
@@ -2074,7 +2088,7 @@ var CreditCardTab = ({ isLoggedIn }) => {
2074
2088
  async (data) => {
2075
2089
  try {
2076
2090
  let res = null;
2077
- const pointsAmount = selectedPaymentMethod.splitAmount ?? 0;
2091
+ const pointsAmount = selectedPaymentMethod.pointsAmount ?? 0;
2078
2092
  const usdAmount = getSplitAmount(appProps.amount ?? 0, pointsAmount, spreePayConfig?.pointsConversionRatio);
2079
2093
  if (usdAmount && pointsAmount) {
2080
2094
  res = await splitPayment({ ...data, points: pointsAmount });
@@ -2191,9 +2205,9 @@ var TabButtons = (props) => {
2191
2205
 
2192
2206
  // src/SpreePayContent.tsx
2193
2207
  import { jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
2194
- var CryptoTab = lazy(() => import("./CryptoTab-CI5C22BD.js").then((module) => ({ default: module.CryptoTab })));
2208
+ var CryptoTab = lazy(() => import("./CryptoTab-AK7WPMBJ.js").then((module) => ({ default: module.CryptoTab })));
2195
2209
  var CryptoComTab = lazy(
2196
- () => import("./CryptoComTab-P4MC2R2N.js").then((module) => ({ default: module.CryptoComTab }))
2210
+ () => import("./CryptoComTab-QOGLM3EB.js").then((module) => ({ default: module.CryptoComTab }))
2197
2211
  );
2198
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: [
2199
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.5",
3
+ "version": "0.4.0",
4
4
  "description": "Spree-pay React component and utilities",
5
5
  "private": false,
6
6
  "type": "module",