@flopay/js 1.0.3 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/dist/index.cjs +159 -104
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +41 -84
- package/dist/index.d.ts +41 -84
- package/dist/index.mjs +166 -108
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -154,6 +154,27 @@ const result = await createCheckoutSession({
|
|
|
154
154
|
|
|
155
155
|
Use `createCheckoutSessionWithRetries` for automatic retry with exponential backoff on timeout errors.
|
|
156
156
|
|
|
157
|
+
Coupon validation errors are surfaced as `FloPayError` with structured `code`:
|
|
158
|
+
|
|
159
|
+
- `CouponLimitExceeded` — more than 5 coupon codes supplied (also enforced client-side before the request leaves the browser).
|
|
160
|
+
- `CouponCurrencyUnsupported` — an amount-based coupon has no price for the cart currency.
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
import { FloPayError } from '@flopay/shared';
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
await createCheckoutSession({ /* … */ couponCodes });
|
|
167
|
+
} catch (err) {
|
|
168
|
+
if (err instanceof FloPayError) {
|
|
169
|
+
if (err.code === 'CouponLimitExceeded') {
|
|
170
|
+
// show "Too many coupons" toast
|
|
171
|
+
} else if (err.code === 'CouponCurrencyUnsupported') {
|
|
172
|
+
// show "Coupon is not valid for this currency" toast
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
157
178
|
## API Reference
|
|
158
179
|
|
|
159
180
|
### Exports
|
package/dist/index.cjs
CHANGED
|
@@ -643,9 +643,9 @@ var PaymentAPI = class {
|
|
|
643
643
|
/**
|
|
644
644
|
* Fetch and normalize a checkout session.
|
|
645
645
|
*
|
|
646
|
-
* Reads the backend's `
|
|
647
|
-
* then wraps the session in a `NormalizedCheckoutSession` for
|
|
648
|
-
*
|
|
646
|
+
* Reads the backend's `gateways` map to enumerate provider-specific data,
|
|
647
|
+
* then wraps the session in a `NormalizedCheckoutSession` for provider-
|
|
648
|
+
* agnostic consumption.
|
|
649
649
|
*/
|
|
650
650
|
async getUnifiedCheckoutSession(checkoutSessionId) {
|
|
651
651
|
const res = await this.getCheckoutSession(checkoutSessionId);
|
|
@@ -656,16 +656,18 @@ var PaymentAPI = class {
|
|
|
656
656
|
*
|
|
657
657
|
* The backend will either succeed, return `type: '3ds_required'`
|
|
658
658
|
* (with a `threeDSecureToken`), or return `type: 'paypal_redirect_required'`.
|
|
659
|
+
*
|
|
660
|
+
* @param userId Vestigial — backend's GatewayInterceptor routes via session,
|
|
661
|
+
* not headers, so this value is no longer sent on the wire. Kept in the
|
|
662
|
+
* signature for back-compat with existing callers; will be removed in a
|
|
663
|
+
* future major version.
|
|
659
664
|
*/
|
|
660
|
-
async processPayment(
|
|
665
|
+
async processPayment(_userId, data, options) {
|
|
661
666
|
const response = await fetch(
|
|
662
667
|
`${this.baseUrl}/v1/checkouts/sessions/process`,
|
|
663
668
|
{
|
|
664
669
|
method: "POST",
|
|
665
|
-
headers: {
|
|
666
|
-
"Content-Type": "application/json",
|
|
667
|
-
"x-user-id": userId
|
|
668
|
-
},
|
|
670
|
+
headers: { "Content-Type": "application/json" },
|
|
669
671
|
body: JSON.stringify(data)
|
|
670
672
|
}
|
|
671
673
|
);
|
|
@@ -742,19 +744,28 @@ var PaymentAPI = class {
|
|
|
742
744
|
* Falls back to create + GET if the backend doesn't support `expand`.
|
|
743
745
|
*/
|
|
744
746
|
async createAndFetchSession(params) {
|
|
747
|
+
const wireProducts = params.products ?? (0, import_shared3.foldIntoProducts)(params.items, params.subscriptions);
|
|
745
748
|
const sessionCurrency = (0, import_shared3.resolveSessionCurrency)(
|
|
746
749
|
params.currency,
|
|
747
750
|
params.items,
|
|
748
|
-
params.subscriptions
|
|
751
|
+
params.subscriptions,
|
|
752
|
+
wireProducts
|
|
749
753
|
);
|
|
754
|
+
if (!sessionCurrency) {
|
|
755
|
+
throw new import_shared3.FloPayError(
|
|
756
|
+
"currency is required: pass `currency` on the session, or include a `currency` on the first item/subscription/product.",
|
|
757
|
+
"validation_error",
|
|
758
|
+
{ code: "CurrencyRequired", param: "currency" }
|
|
759
|
+
);
|
|
760
|
+
}
|
|
750
761
|
const payload = {
|
|
751
762
|
clientId: params.clientId,
|
|
763
|
+
checkoutVersion: import_shared3.SDK_VERSION,
|
|
752
764
|
successUrl: params.successUrl,
|
|
753
765
|
cancelUrl: params.cancelUrl,
|
|
754
766
|
currency: sessionCurrency,
|
|
755
767
|
checkoutMode: params.checkoutMode ?? "full",
|
|
756
|
-
|
|
757
|
-
subscriptions: (params.subscriptions ?? []).map((sub) => (0, import_shared3.buildSubscriptionPayload)(sub, sessionCurrency)),
|
|
768
|
+
products: wireProducts.map((product) => (0, import_shared3.buildProductPayload)(product, sessionCurrency)),
|
|
758
769
|
accountData: {
|
|
759
770
|
userId: params.account.userId,
|
|
760
771
|
firstName: params.account.firstName ?? null,
|
|
@@ -796,7 +807,7 @@ var PaymentAPI = class {
|
|
|
796
807
|
throw await buildApiErrorFromResponse(response, "Failed to create checkout session");
|
|
797
808
|
}
|
|
798
809
|
const body = await response.json();
|
|
799
|
-
if (body.data && "
|
|
810
|
+
if (body.data && "gateways" in body.data) {
|
|
800
811
|
this.autoCacheDisplayData(body.data.uuid, params);
|
|
801
812
|
const merged = this.mergeCachedDisplayData(body.data);
|
|
802
813
|
return {
|
|
@@ -849,57 +860,57 @@ var PaymentAPI = class {
|
|
|
849
860
|
}
|
|
850
861
|
/** Normalize a raw session into a provider-agnostic shape. */
|
|
851
862
|
normalizeRawSession(session) {
|
|
852
|
-
const
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
}
|
|
861
|
-
if (gateway === "stripe") {
|
|
863
|
+
const gateways = session.gateways ?? {};
|
|
864
|
+
const providers = [];
|
|
865
|
+
const data = {
|
|
866
|
+
session: this.toCheckoutSession(session)
|
|
867
|
+
};
|
|
868
|
+
const stripeGateway = gateways.stripe;
|
|
869
|
+
if (stripeGateway?.publishableKey) {
|
|
870
|
+
providers.push("stripe");
|
|
862
871
|
const rawSession = session;
|
|
863
|
-
const gatewayDataRecord = session.gatewayData ?? {};
|
|
864
872
|
const stripeClientSecret = [
|
|
865
873
|
rawSession["stripeClientSecret"],
|
|
866
|
-
|
|
867
|
-
gatewayDataRecord["clientSecret"]
|
|
874
|
+
stripeGateway.stripeClientSecret
|
|
868
875
|
].find((value) => typeof value === "string" && value.length > 0);
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
876
|
+
data.stripe = {
|
|
877
|
+
clientSecret: stripeClientSecret ?? "",
|
|
878
|
+
publishableKey: stripeGateway.publishableKey ?? void 0,
|
|
879
|
+
paypalPublishableKey: stripeGateway.paypalPublishableKey ?? void 0,
|
|
880
|
+
environment: stripeGateway.environment
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
const paypalGateway = gateways.paypal;
|
|
884
|
+
if (paypalGateway?.publishableKey) {
|
|
885
|
+
providers.push("paypal");
|
|
886
|
+
data.paypal = {
|
|
887
|
+
publishableKey: paypalGateway.publishableKey,
|
|
888
|
+
environment: paypalGateway.environment
|
|
881
889
|
};
|
|
882
890
|
}
|
|
883
891
|
return {
|
|
884
|
-
|
|
892
|
+
providers,
|
|
885
893
|
mode: "tokenize",
|
|
886
|
-
data
|
|
894
|
+
data,
|
|
887
895
|
raw: { data: session }
|
|
888
896
|
};
|
|
889
897
|
}
|
|
890
898
|
/** Convert raw session to the SDK CheckoutSession shape. */
|
|
891
899
|
toCheckoutSession(raw) {
|
|
892
|
-
const
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
900
|
+
const rawProducts = raw.products ?? [];
|
|
901
|
+
const hasBackendTotal = typeof raw.totalAmount === "number" && Number.isFinite(raw.totalAmount);
|
|
902
|
+
const computedTotal = rawProducts.reduce(
|
|
903
|
+
(sum, p) => sum + (p.overrideAmount ?? p.totalAmount ?? 0),
|
|
904
|
+
0
|
|
905
|
+
);
|
|
906
|
+
const totalAmount = hasBackendTotal ? raw.totalAmount : computedTotal;
|
|
896
907
|
const amountInCents = Math.round(totalAmount * 100);
|
|
897
|
-
const currency = raw.currency ??
|
|
908
|
+
const currency = raw.currency ?? rawProducts[0]?.currency ?? "USD";
|
|
909
|
+
const mode = rawProducts.some((p) => p.type === "subscription") ? "subscription" : "payment";
|
|
898
910
|
return {
|
|
899
|
-
// Core fields (backward compat)
|
|
900
911
|
id: raw.uuid,
|
|
901
912
|
clientSecret: raw.nonce,
|
|
902
|
-
mode
|
|
913
|
+
mode,
|
|
903
914
|
status: this.toCheckoutSessionStatus(raw.status),
|
|
904
915
|
amount: amountInCents,
|
|
905
916
|
currency,
|
|
@@ -917,16 +928,22 @@ var PaymentAPI = class {
|
|
|
917
928
|
line2: raw.accountData.addressLine2 ?? void 0
|
|
918
929
|
},
|
|
919
930
|
metadata: {},
|
|
920
|
-
// Full session data from billing API
|
|
921
931
|
checkoutMode: raw.checkoutMode,
|
|
922
|
-
|
|
923
|
-
|
|
932
|
+
products: rawProducts.map((p) => ({
|
|
933
|
+
...p,
|
|
934
|
+
totalAmount: typeof p.totalAmount === "number" ? p.totalAmount : void 0,
|
|
935
|
+
overrideAmount: typeof p.overrideAmount === "number" ? p.overrideAmount : null,
|
|
936
|
+
currency: typeof p.currency === "string" ? p.currency : void 0,
|
|
937
|
+
metadata: p.metadata ?? null
|
|
938
|
+
})),
|
|
924
939
|
successUrl: raw.successUrl,
|
|
925
940
|
cancelUrl: raw.cancelUrl,
|
|
926
941
|
coupons: raw.coupons,
|
|
942
|
+
subtotalAmount: raw.subtotalAmount,
|
|
943
|
+
discountAmount: raw.discountAmount,
|
|
944
|
+
totalAmount: raw.totalAmount,
|
|
927
945
|
createdAt: raw.createdAt,
|
|
928
|
-
|
|
929
|
-
gatewayData: raw.gatewayData,
|
|
946
|
+
gateways: raw.gateways,
|
|
930
947
|
accountData: raw.accountData,
|
|
931
948
|
tagsData: raw.tagsData
|
|
932
949
|
};
|
|
@@ -983,70 +1000,63 @@ var PaymentAPI = class {
|
|
|
983
1000
|
* Stash the display-only fields the consumer passed into a create-session
|
|
984
1001
|
* call. Runs after the backend assigns a UUID so a later GET on the same
|
|
985
1002
|
* session (typically after a redirect) can fill in fields the backend no
|
|
986
|
-
* longer persists — `overrideAmount`, `totalAmount`, `
|
|
1003
|
+
* longer persists — `overrideAmount`, `totalAmount`, `name`, etc.
|
|
987
1004
|
*
|
|
988
1005
|
* No-op when no UUID is available.
|
|
989
1006
|
*/
|
|
990
1007
|
autoCacheDisplayData(sessionId, params) {
|
|
991
1008
|
if (!sessionId) return;
|
|
992
|
-
|
|
1009
|
+
const products = params.products ?? (0, import_shared3.foldIntoProducts)(params.items, params.subscriptions);
|
|
1010
|
+
if (products.length === 0 && !params.currency) {
|
|
993
1011
|
return;
|
|
994
1012
|
}
|
|
1013
|
+
const usingUnifiedProducts = params.products !== void 0;
|
|
1014
|
+
const sessionCurrency = (0, import_shared3.resolveSessionCurrency)(
|
|
1015
|
+
params.currency,
|
|
1016
|
+
usingUnifiedProducts ? void 0 : params.items,
|
|
1017
|
+
usingUnifiedProducts ? void 0 : params.subscriptions,
|
|
1018
|
+
products
|
|
1019
|
+
);
|
|
995
1020
|
cacheSessionDisplayData(sessionId, {
|
|
996
|
-
currency:
|
|
997
|
-
|
|
998
|
-
|
|
1021
|
+
currency: sessionCurrency ?? void 0,
|
|
1022
|
+
products: products.map((p) => ({
|
|
1023
|
+
code: p.code ?? p.providerItemId ?? p.providerPlanId,
|
|
1024
|
+
type: p.type,
|
|
1025
|
+
name: p.name ?? p.itemName ?? p.providerItemName ?? p.subscriptionName ?? p.providerPlanName ?? null,
|
|
1026
|
+
totalAmount: p.totalAmount,
|
|
1027
|
+
overrideAmount: p.overrideAmount,
|
|
1028
|
+
currency: p.currency ?? sessionCurrency ?? void 0
|
|
1029
|
+
}))
|
|
999
1030
|
});
|
|
1000
1031
|
}
|
|
1001
1032
|
/**
|
|
1002
1033
|
* Merge cached display-only fields (set by {@link cacheSessionDisplayData})
|
|
1003
|
-
* into a raw session response
|
|
1004
|
-
*
|
|
1005
|
-
*
|
|
1006
|
-
* Server values always win — cache fills in only where the server returned
|
|
1007
|
-
* `null` / `undefined`.
|
|
1034
|
+
* into a raw session response. Server values always win — cache fills in
|
|
1035
|
+
* only where the server returned `null` / `undefined`.
|
|
1008
1036
|
*/
|
|
1009
1037
|
mergeCachedDisplayData(raw) {
|
|
1010
1038
|
const cached = getSessionDisplayData(raw.uuid);
|
|
1011
|
-
const
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
const key =
|
|
1019
|
-
|
|
1020
|
-
|
|
1039
|
+
const cachedProducts = /* @__PURE__ */ new Map();
|
|
1040
|
+
const productKey = (type, code) => code && type ? `${type}:${code}` : void 0;
|
|
1041
|
+
for (const p of cached?.products ?? []) {
|
|
1042
|
+
const key = productKey(p.type, p.code);
|
|
1043
|
+
if (key) cachedProducts.set(key, p);
|
|
1044
|
+
}
|
|
1045
|
+
const mergedProducts = (raw.products ?? []).map((p) => {
|
|
1046
|
+
const key = productKey(p.type, p.code);
|
|
1047
|
+
const fallback = key ? cachedProducts.get(key) : void 0;
|
|
1048
|
+
return {
|
|
1049
|
+
...p,
|
|
1050
|
+
name: p.name ?? fallback?.name ?? null,
|
|
1051
|
+
totalAmount: p.totalAmount ?? fallback?.totalAmount,
|
|
1052
|
+
overrideAmount: p.overrideAmount ?? fallback?.overrideAmount,
|
|
1053
|
+
currency: p.currency ?? fallback?.currency
|
|
1054
|
+
};
|
|
1055
|
+
});
|
|
1021
1056
|
return {
|
|
1022
1057
|
...raw,
|
|
1023
1058
|
currency: raw.currency ?? cached?.currency,
|
|
1024
|
-
|
|
1025
|
-
const key = item.code ?? item.providerItemId;
|
|
1026
|
-
const fallback = key ? cachedItems.get(key) : void 0;
|
|
1027
|
-
const resolvedName = item.itemName ?? item.providerItemName ?? fallback?.itemName ?? fallback?.providerItemName;
|
|
1028
|
-
return {
|
|
1029
|
-
...item,
|
|
1030
|
-
itemName: resolvedName,
|
|
1031
|
-
providerItemName: resolvedName,
|
|
1032
|
-
totalAmount: item.totalAmount ?? fallback?.totalAmount,
|
|
1033
|
-
overrideAmount: item.overrideAmount ?? fallback?.overrideAmount,
|
|
1034
|
-
currency: item.currency ?? fallback?.currency
|
|
1035
|
-
};
|
|
1036
|
-
}),
|
|
1037
|
-
subscriptions: raw.subscriptions.map((sub) => {
|
|
1038
|
-
const key = sub.code ?? sub.providerPlanId;
|
|
1039
|
-
const fallback = key ? cachedSubs.get(key) : void 0;
|
|
1040
|
-
const resolvedName = sub.subscriptionName ?? sub.providerPlanName ?? fallback?.subscriptionName ?? fallback?.providerPlanName;
|
|
1041
|
-
return {
|
|
1042
|
-
...sub,
|
|
1043
|
-
subscriptionName: resolvedName,
|
|
1044
|
-
providerPlanName: resolvedName,
|
|
1045
|
-
totalAmount: sub.totalAmount ?? fallback?.totalAmount,
|
|
1046
|
-
overrideAmount: sub.overrideAmount ?? fallback?.overrideAmount,
|
|
1047
|
-
currency: sub.currency ?? fallback?.currency
|
|
1048
|
-
};
|
|
1049
|
-
})
|
|
1059
|
+
products: mergedProducts
|
|
1050
1060
|
};
|
|
1051
1061
|
}
|
|
1052
1062
|
};
|
|
@@ -1183,12 +1193,33 @@ async function loadFloPay(publishableKey, options) {
|
|
|
1183
1193
|
|
|
1184
1194
|
// src/create-checkout-session.ts
|
|
1185
1195
|
var import_shared6 = require("@flopay/shared");
|
|
1196
|
+
var MAX_COUPON_CODES = 5;
|
|
1197
|
+
function readString2(value) {
|
|
1198
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
1199
|
+
}
|
|
1200
|
+
function buildCheckoutSessionError(status, payload) {
|
|
1201
|
+
const nested = payload?.error;
|
|
1202
|
+
const code = readString2(payload?.code) ?? readString2(nested?.code) ?? `http_${status}`;
|
|
1203
|
+
const message = readString2(payload?.message) ?? readString2(nested?.message) ?? defaultMessageForCode(code, status);
|
|
1204
|
+
return new import_shared6.FloPayError(message, "api_error", { code, statusCode: status });
|
|
1205
|
+
}
|
|
1206
|
+
function defaultMessageForCode(code, status) {
|
|
1207
|
+
switch (code) {
|
|
1208
|
+
case "CouponLimitExceeded":
|
|
1209
|
+
return `Too many coupon codes \u2014 a checkout session accepts at most ${MAX_COUPON_CODES}.`;
|
|
1210
|
+
case "CouponCurrencyUnsupported":
|
|
1211
|
+
return "One of the applied coupons has no price configured for the cart currency.";
|
|
1212
|
+
default:
|
|
1213
|
+
return `Failed to create checkout session (HTTP ${status}).`;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1186
1216
|
async function createCheckoutSession(options) {
|
|
1187
1217
|
const {
|
|
1188
1218
|
billingApiUrl,
|
|
1189
1219
|
checkoutBaseUrl,
|
|
1190
1220
|
items = [],
|
|
1191
1221
|
subscriptions = [],
|
|
1222
|
+
products,
|
|
1192
1223
|
account,
|
|
1193
1224
|
successUrl,
|
|
1194
1225
|
cancelUrl,
|
|
@@ -1202,15 +1233,30 @@ async function createCheckoutSession(options) {
|
|
|
1202
1233
|
currency,
|
|
1203
1234
|
utmMetadata
|
|
1204
1235
|
} = options;
|
|
1205
|
-
|
|
1236
|
+
if (couponCodes.length > MAX_COUPON_CODES) {
|
|
1237
|
+
throw new import_shared6.FloPayError(
|
|
1238
|
+
`Too many coupon codes \u2014 a checkout session accepts at most ${MAX_COUPON_CODES}.`,
|
|
1239
|
+
"validation_error",
|
|
1240
|
+
{ code: "CouponLimitExceeded", param: "couponCodes" }
|
|
1241
|
+
);
|
|
1242
|
+
}
|
|
1243
|
+
const wireProducts = products ?? (0, import_shared6.foldIntoProducts)(items, subscriptions);
|
|
1244
|
+
const sessionCurrency = (0, import_shared6.resolveSessionCurrency)(currency, items, subscriptions, wireProducts);
|
|
1245
|
+
if (!sessionCurrency) {
|
|
1246
|
+
throw new import_shared6.FloPayError(
|
|
1247
|
+
"currency is required: pass `currency` on the session, or include a `currency` on the first item/subscription/product.",
|
|
1248
|
+
"validation_error",
|
|
1249
|
+
{ code: "CurrencyRequired", param: "currency" }
|
|
1250
|
+
);
|
|
1251
|
+
}
|
|
1206
1252
|
const payload = {
|
|
1207
1253
|
clientId,
|
|
1254
|
+
checkoutVersion: import_shared6.SDK_VERSION,
|
|
1208
1255
|
successUrl,
|
|
1209
1256
|
cancelUrl,
|
|
1210
1257
|
currency: sessionCurrency,
|
|
1211
1258
|
checkoutMode,
|
|
1212
|
-
|
|
1213
|
-
subscriptions: subscriptions.map((sub) => (0, import_shared6.buildSubscriptionPayload)(sub, sessionCurrency)),
|
|
1259
|
+
products: wireProducts.map((product) => (0, import_shared6.buildProductPayload)(product, sessionCurrency)),
|
|
1214
1260
|
accountData: {
|
|
1215
1261
|
userId: account.userId,
|
|
1216
1262
|
firstName: account.firstName ?? null,
|
|
@@ -1252,16 +1298,25 @@ async function createCheckoutSession(options) {
|
|
|
1252
1298
|
} finally {
|
|
1253
1299
|
clearTimeout(timer);
|
|
1254
1300
|
}
|
|
1301
|
+
if (status >= 400) {
|
|
1302
|
+
throw buildCheckoutSessionError(status, body);
|
|
1303
|
+
}
|
|
1255
1304
|
if (status === 201) {
|
|
1256
1305
|
const uuid = body?.data?.uuid;
|
|
1257
1306
|
if (!uuid) {
|
|
1258
1307
|
throw new Error("Checkout session created but no UUID was returned by the billing API");
|
|
1259
1308
|
}
|
|
1260
|
-
if (
|
|
1309
|
+
if (wireProducts.length || sessionCurrency) {
|
|
1261
1310
|
cacheSessionDisplayData(uuid, {
|
|
1262
|
-
currency,
|
|
1263
|
-
|
|
1264
|
-
|
|
1311
|
+
currency: sessionCurrency,
|
|
1312
|
+
products: wireProducts.map((p) => ({
|
|
1313
|
+
code: p.code ?? p.providerItemId ?? p.providerPlanId,
|
|
1314
|
+
type: p.type,
|
|
1315
|
+
name: p.name ?? p.itemName ?? p.providerItemName ?? p.subscriptionName ?? p.providerPlanName ?? null,
|
|
1316
|
+
totalAmount: p.totalAmount,
|
|
1317
|
+
overrideAmount: p.overrideAmount,
|
|
1318
|
+
currency: p.currency ?? sessionCurrency
|
|
1319
|
+
}))
|
|
1265
1320
|
});
|
|
1266
1321
|
}
|
|
1267
1322
|
const redirectUrl = new URL(`${checkoutBaseUrl.replace(/\/+$/, "")}/secure`);
|