@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/dist/index.mjs
CHANGED
|
@@ -449,8 +449,9 @@ var FloPayElements = class {
|
|
|
449
449
|
// src/payment-api.ts
|
|
450
450
|
import {
|
|
451
451
|
FloPayError as FloPayError3,
|
|
452
|
-
|
|
453
|
-
|
|
452
|
+
SDK_VERSION,
|
|
453
|
+
buildProductPayload,
|
|
454
|
+
foldIntoProducts,
|
|
454
455
|
resolveSessionCurrency
|
|
455
456
|
} from "@flopay/shared";
|
|
456
457
|
|
|
@@ -603,9 +604,9 @@ var PaymentAPI = class {
|
|
|
603
604
|
/**
|
|
604
605
|
* Fetch and normalize a checkout session.
|
|
605
606
|
*
|
|
606
|
-
* Reads the backend's `
|
|
607
|
-
* then wraps the session in a `NormalizedCheckoutSession` for
|
|
608
|
-
*
|
|
607
|
+
* Reads the backend's `gateways` map to enumerate provider-specific data,
|
|
608
|
+
* then wraps the session in a `NormalizedCheckoutSession` for provider-
|
|
609
|
+
* agnostic consumption.
|
|
609
610
|
*/
|
|
610
611
|
async getUnifiedCheckoutSession(checkoutSessionId) {
|
|
611
612
|
const res = await this.getCheckoutSession(checkoutSessionId);
|
|
@@ -616,16 +617,18 @@ var PaymentAPI = class {
|
|
|
616
617
|
*
|
|
617
618
|
* The backend will either succeed, return `type: '3ds_required'`
|
|
618
619
|
* (with a `threeDSecureToken`), or return `type: 'paypal_redirect_required'`.
|
|
620
|
+
*
|
|
621
|
+
* @param userId Vestigial — backend's GatewayInterceptor routes via session,
|
|
622
|
+
* not headers, so this value is no longer sent on the wire. Kept in the
|
|
623
|
+
* signature for back-compat with existing callers; will be removed in a
|
|
624
|
+
* future major version.
|
|
619
625
|
*/
|
|
620
|
-
async processPayment(
|
|
626
|
+
async processPayment(_userId, data, options) {
|
|
621
627
|
const response = await fetch(
|
|
622
628
|
`${this.baseUrl}/v1/checkouts/sessions/process`,
|
|
623
629
|
{
|
|
624
630
|
method: "POST",
|
|
625
|
-
headers: {
|
|
626
|
-
"Content-Type": "application/json",
|
|
627
|
-
"x-user-id": userId
|
|
628
|
-
},
|
|
631
|
+
headers: { "Content-Type": "application/json" },
|
|
629
632
|
body: JSON.stringify(data)
|
|
630
633
|
}
|
|
631
634
|
);
|
|
@@ -702,19 +705,28 @@ var PaymentAPI = class {
|
|
|
702
705
|
* Falls back to create + GET if the backend doesn't support `expand`.
|
|
703
706
|
*/
|
|
704
707
|
async createAndFetchSession(params) {
|
|
708
|
+
const wireProducts = params.products ?? foldIntoProducts(params.items, params.subscriptions);
|
|
705
709
|
const sessionCurrency = resolveSessionCurrency(
|
|
706
710
|
params.currency,
|
|
707
711
|
params.items,
|
|
708
|
-
params.subscriptions
|
|
712
|
+
params.subscriptions,
|
|
713
|
+
wireProducts
|
|
709
714
|
);
|
|
715
|
+
if (!sessionCurrency) {
|
|
716
|
+
throw new FloPayError3(
|
|
717
|
+
"currency is required: pass `currency` on the session, or include a `currency` on the first item/subscription/product.",
|
|
718
|
+
"validation_error",
|
|
719
|
+
{ code: "CurrencyRequired", param: "currency" }
|
|
720
|
+
);
|
|
721
|
+
}
|
|
710
722
|
const payload = {
|
|
711
723
|
clientId: params.clientId,
|
|
724
|
+
checkoutVersion: SDK_VERSION,
|
|
712
725
|
successUrl: params.successUrl,
|
|
713
726
|
cancelUrl: params.cancelUrl,
|
|
714
727
|
currency: sessionCurrency,
|
|
715
728
|
checkoutMode: params.checkoutMode ?? "full",
|
|
716
|
-
|
|
717
|
-
subscriptions: (params.subscriptions ?? []).map((sub) => buildSubscriptionPayload(sub, sessionCurrency)),
|
|
729
|
+
products: wireProducts.map((product) => buildProductPayload(product, sessionCurrency)),
|
|
718
730
|
accountData: {
|
|
719
731
|
userId: params.account.userId,
|
|
720
732
|
firstName: params.account.firstName ?? null,
|
|
@@ -756,7 +768,7 @@ var PaymentAPI = class {
|
|
|
756
768
|
throw await buildApiErrorFromResponse(response, "Failed to create checkout session");
|
|
757
769
|
}
|
|
758
770
|
const body = await response.json();
|
|
759
|
-
if (body.data && "
|
|
771
|
+
if (body.data && "gateways" in body.data) {
|
|
760
772
|
this.autoCacheDisplayData(body.data.uuid, params);
|
|
761
773
|
const merged = this.mergeCachedDisplayData(body.data);
|
|
762
774
|
return {
|
|
@@ -809,57 +821,57 @@ var PaymentAPI = class {
|
|
|
809
821
|
}
|
|
810
822
|
/** Normalize a raw session into a provider-agnostic shape. */
|
|
811
823
|
normalizeRawSession(session) {
|
|
812
|
-
const
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
}
|
|
821
|
-
if (gateway === "stripe") {
|
|
824
|
+
const gateways = session.gateways ?? {};
|
|
825
|
+
const providers = [];
|
|
826
|
+
const data = {
|
|
827
|
+
session: this.toCheckoutSession(session)
|
|
828
|
+
};
|
|
829
|
+
const stripeGateway = gateways.stripe;
|
|
830
|
+
if (stripeGateway?.publishableKey) {
|
|
831
|
+
providers.push("stripe");
|
|
822
832
|
const rawSession = session;
|
|
823
|
-
const gatewayDataRecord = session.gatewayData ?? {};
|
|
824
833
|
const stripeClientSecret = [
|
|
825
834
|
rawSession["stripeClientSecret"],
|
|
826
|
-
|
|
827
|
-
gatewayDataRecord["clientSecret"]
|
|
835
|
+
stripeGateway.stripeClientSecret
|
|
828
836
|
].find((value) => typeof value === "string" && value.length > 0);
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
837
|
+
data.stripe = {
|
|
838
|
+
clientSecret: stripeClientSecret ?? "",
|
|
839
|
+
publishableKey: stripeGateway.publishableKey ?? void 0,
|
|
840
|
+
paypalPublishableKey: stripeGateway.paypalPublishableKey ?? void 0,
|
|
841
|
+
environment: stripeGateway.environment
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
const paypalGateway = gateways.paypal;
|
|
845
|
+
if (paypalGateway?.publishableKey) {
|
|
846
|
+
providers.push("paypal");
|
|
847
|
+
data.paypal = {
|
|
848
|
+
publishableKey: paypalGateway.publishableKey,
|
|
849
|
+
environment: paypalGateway.environment
|
|
841
850
|
};
|
|
842
851
|
}
|
|
843
852
|
return {
|
|
844
|
-
|
|
853
|
+
providers,
|
|
845
854
|
mode: "tokenize",
|
|
846
|
-
data
|
|
855
|
+
data,
|
|
847
856
|
raw: { data: session }
|
|
848
857
|
};
|
|
849
858
|
}
|
|
850
859
|
/** Convert raw session to the SDK CheckoutSession shape. */
|
|
851
860
|
toCheckoutSession(raw) {
|
|
852
|
-
const
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
861
|
+
const rawProducts = raw.products ?? [];
|
|
862
|
+
const hasBackendTotal = typeof raw.totalAmount === "number" && Number.isFinite(raw.totalAmount);
|
|
863
|
+
const computedTotal = rawProducts.reduce(
|
|
864
|
+
(sum, p) => sum + (p.overrideAmount ?? p.totalAmount ?? 0),
|
|
865
|
+
0
|
|
866
|
+
);
|
|
867
|
+
const totalAmount = hasBackendTotal ? raw.totalAmount : computedTotal;
|
|
856
868
|
const amountInCents = Math.round(totalAmount * 100);
|
|
857
|
-
const currency = raw.currency ??
|
|
869
|
+
const currency = raw.currency ?? rawProducts[0]?.currency ?? "USD";
|
|
870
|
+
const mode = rawProducts.some((p) => p.type === "subscription") ? "subscription" : "payment";
|
|
858
871
|
return {
|
|
859
|
-
// Core fields (backward compat)
|
|
860
872
|
id: raw.uuid,
|
|
861
873
|
clientSecret: raw.nonce,
|
|
862
|
-
mode
|
|
874
|
+
mode,
|
|
863
875
|
status: this.toCheckoutSessionStatus(raw.status),
|
|
864
876
|
amount: amountInCents,
|
|
865
877
|
currency,
|
|
@@ -877,16 +889,22 @@ var PaymentAPI = class {
|
|
|
877
889
|
line2: raw.accountData.addressLine2 ?? void 0
|
|
878
890
|
},
|
|
879
891
|
metadata: {},
|
|
880
|
-
// Full session data from billing API
|
|
881
892
|
checkoutMode: raw.checkoutMode,
|
|
882
|
-
|
|
883
|
-
|
|
893
|
+
products: rawProducts.map((p) => ({
|
|
894
|
+
...p,
|
|
895
|
+
totalAmount: typeof p.totalAmount === "number" ? p.totalAmount : void 0,
|
|
896
|
+
overrideAmount: typeof p.overrideAmount === "number" ? p.overrideAmount : null,
|
|
897
|
+
currency: typeof p.currency === "string" ? p.currency : void 0,
|
|
898
|
+
metadata: p.metadata ?? null
|
|
899
|
+
})),
|
|
884
900
|
successUrl: raw.successUrl,
|
|
885
901
|
cancelUrl: raw.cancelUrl,
|
|
886
902
|
coupons: raw.coupons,
|
|
903
|
+
subtotalAmount: raw.subtotalAmount,
|
|
904
|
+
discountAmount: raw.discountAmount,
|
|
905
|
+
totalAmount: raw.totalAmount,
|
|
887
906
|
createdAt: raw.createdAt,
|
|
888
|
-
|
|
889
|
-
gatewayData: raw.gatewayData,
|
|
907
|
+
gateways: raw.gateways,
|
|
890
908
|
accountData: raw.accountData,
|
|
891
909
|
tagsData: raw.tagsData
|
|
892
910
|
};
|
|
@@ -943,70 +961,63 @@ var PaymentAPI = class {
|
|
|
943
961
|
* Stash the display-only fields the consumer passed into a create-session
|
|
944
962
|
* call. Runs after the backend assigns a UUID so a later GET on the same
|
|
945
963
|
* session (typically after a redirect) can fill in fields the backend no
|
|
946
|
-
* longer persists — `overrideAmount`, `totalAmount`, `
|
|
964
|
+
* longer persists — `overrideAmount`, `totalAmount`, `name`, etc.
|
|
947
965
|
*
|
|
948
966
|
* No-op when no UUID is available.
|
|
949
967
|
*/
|
|
950
968
|
autoCacheDisplayData(sessionId, params) {
|
|
951
969
|
if (!sessionId) return;
|
|
952
|
-
|
|
970
|
+
const products = params.products ?? foldIntoProducts(params.items, params.subscriptions);
|
|
971
|
+
if (products.length === 0 && !params.currency) {
|
|
953
972
|
return;
|
|
954
973
|
}
|
|
974
|
+
const usingUnifiedProducts = params.products !== void 0;
|
|
975
|
+
const sessionCurrency = resolveSessionCurrency(
|
|
976
|
+
params.currency,
|
|
977
|
+
usingUnifiedProducts ? void 0 : params.items,
|
|
978
|
+
usingUnifiedProducts ? void 0 : params.subscriptions,
|
|
979
|
+
products
|
|
980
|
+
);
|
|
955
981
|
cacheSessionDisplayData(sessionId, {
|
|
956
|
-
currency:
|
|
957
|
-
|
|
958
|
-
|
|
982
|
+
currency: sessionCurrency ?? void 0,
|
|
983
|
+
products: products.map((p) => ({
|
|
984
|
+
code: p.code ?? p.providerItemId ?? p.providerPlanId,
|
|
985
|
+
type: p.type,
|
|
986
|
+
name: p.name ?? p.itemName ?? p.providerItemName ?? p.subscriptionName ?? p.providerPlanName ?? null,
|
|
987
|
+
totalAmount: p.totalAmount,
|
|
988
|
+
overrideAmount: p.overrideAmount,
|
|
989
|
+
currency: p.currency ?? sessionCurrency ?? void 0
|
|
990
|
+
}))
|
|
959
991
|
});
|
|
960
992
|
}
|
|
961
993
|
/**
|
|
962
994
|
* Merge cached display-only fields (set by {@link cacheSessionDisplayData})
|
|
963
|
-
* into a raw session response
|
|
964
|
-
*
|
|
965
|
-
*
|
|
966
|
-
* Server values always win — cache fills in only where the server returned
|
|
967
|
-
* `null` / `undefined`.
|
|
995
|
+
* into a raw session response. Server values always win — cache fills in
|
|
996
|
+
* only where the server returned `null` / `undefined`.
|
|
968
997
|
*/
|
|
969
998
|
mergeCachedDisplayData(raw) {
|
|
970
999
|
const cached = getSessionDisplayData(raw.uuid);
|
|
971
|
-
const
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
const key =
|
|
979
|
-
|
|
980
|
-
|
|
1000
|
+
const cachedProducts = /* @__PURE__ */ new Map();
|
|
1001
|
+
const productKey = (type, code) => code && type ? `${type}:${code}` : void 0;
|
|
1002
|
+
for (const p of cached?.products ?? []) {
|
|
1003
|
+
const key = productKey(p.type, p.code);
|
|
1004
|
+
if (key) cachedProducts.set(key, p);
|
|
1005
|
+
}
|
|
1006
|
+
const mergedProducts = (raw.products ?? []).map((p) => {
|
|
1007
|
+
const key = productKey(p.type, p.code);
|
|
1008
|
+
const fallback = key ? cachedProducts.get(key) : void 0;
|
|
1009
|
+
return {
|
|
1010
|
+
...p,
|
|
1011
|
+
name: p.name ?? fallback?.name ?? null,
|
|
1012
|
+
totalAmount: p.totalAmount ?? fallback?.totalAmount,
|
|
1013
|
+
overrideAmount: p.overrideAmount ?? fallback?.overrideAmount,
|
|
1014
|
+
currency: p.currency ?? fallback?.currency
|
|
1015
|
+
};
|
|
1016
|
+
});
|
|
981
1017
|
return {
|
|
982
1018
|
...raw,
|
|
983
1019
|
currency: raw.currency ?? cached?.currency,
|
|
984
|
-
|
|
985
|
-
const key = item.code ?? item.providerItemId;
|
|
986
|
-
const fallback = key ? cachedItems.get(key) : void 0;
|
|
987
|
-
const resolvedName = item.itemName ?? item.providerItemName ?? fallback?.itemName ?? fallback?.providerItemName;
|
|
988
|
-
return {
|
|
989
|
-
...item,
|
|
990
|
-
itemName: resolvedName,
|
|
991
|
-
providerItemName: resolvedName,
|
|
992
|
-
totalAmount: item.totalAmount ?? fallback?.totalAmount,
|
|
993
|
-
overrideAmount: item.overrideAmount ?? fallback?.overrideAmount,
|
|
994
|
-
currency: item.currency ?? fallback?.currency
|
|
995
|
-
};
|
|
996
|
-
}),
|
|
997
|
-
subscriptions: raw.subscriptions.map((sub) => {
|
|
998
|
-
const key = sub.code ?? sub.providerPlanId;
|
|
999
|
-
const fallback = key ? cachedSubs.get(key) : void 0;
|
|
1000
|
-
const resolvedName = sub.subscriptionName ?? sub.providerPlanName ?? fallback?.subscriptionName ?? fallback?.providerPlanName;
|
|
1001
|
-
return {
|
|
1002
|
-
...sub,
|
|
1003
|
-
subscriptionName: resolvedName,
|
|
1004
|
-
providerPlanName: resolvedName,
|
|
1005
|
-
totalAmount: sub.totalAmount ?? fallback?.totalAmount,
|
|
1006
|
-
overrideAmount: sub.overrideAmount ?? fallback?.overrideAmount,
|
|
1007
|
-
currency: sub.currency ?? fallback?.currency
|
|
1008
|
-
};
|
|
1009
|
-
})
|
|
1020
|
+
products: mergedProducts
|
|
1010
1021
|
};
|
|
1011
1022
|
}
|
|
1012
1023
|
};
|
|
@@ -1143,16 +1154,39 @@ async function loadFloPay(publishableKey, options) {
|
|
|
1143
1154
|
|
|
1144
1155
|
// src/create-checkout-session.ts
|
|
1145
1156
|
import {
|
|
1146
|
-
|
|
1147
|
-
|
|
1157
|
+
FloPayError as FloPayError6,
|
|
1158
|
+
SDK_VERSION as SDK_VERSION2,
|
|
1159
|
+
buildProductPayload as buildProductPayload2,
|
|
1160
|
+
foldIntoProducts as foldIntoProducts2,
|
|
1148
1161
|
resolveSessionCurrency as resolveSessionCurrency2
|
|
1149
1162
|
} from "@flopay/shared";
|
|
1163
|
+
var MAX_COUPON_CODES = 5;
|
|
1164
|
+
function readString2(value) {
|
|
1165
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
1166
|
+
}
|
|
1167
|
+
function buildCheckoutSessionError(status, payload) {
|
|
1168
|
+
const nested = payload?.error;
|
|
1169
|
+
const code = readString2(payload?.code) ?? readString2(nested?.code) ?? `http_${status}`;
|
|
1170
|
+
const message = readString2(payload?.message) ?? readString2(nested?.message) ?? defaultMessageForCode(code, status);
|
|
1171
|
+
return new FloPayError6(message, "api_error", { code, statusCode: status });
|
|
1172
|
+
}
|
|
1173
|
+
function defaultMessageForCode(code, status) {
|
|
1174
|
+
switch (code) {
|
|
1175
|
+
case "CouponLimitExceeded":
|
|
1176
|
+
return `Too many coupon codes \u2014 a checkout session accepts at most ${MAX_COUPON_CODES}.`;
|
|
1177
|
+
case "CouponCurrencyUnsupported":
|
|
1178
|
+
return "One of the applied coupons has no price configured for the cart currency.";
|
|
1179
|
+
default:
|
|
1180
|
+
return `Failed to create checkout session (HTTP ${status}).`;
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1150
1183
|
async function createCheckoutSession(options) {
|
|
1151
1184
|
const {
|
|
1152
1185
|
billingApiUrl,
|
|
1153
1186
|
checkoutBaseUrl,
|
|
1154
1187
|
items = [],
|
|
1155
1188
|
subscriptions = [],
|
|
1189
|
+
products,
|
|
1156
1190
|
account,
|
|
1157
1191
|
successUrl,
|
|
1158
1192
|
cancelUrl,
|
|
@@ -1166,15 +1200,30 @@ async function createCheckoutSession(options) {
|
|
|
1166
1200
|
currency,
|
|
1167
1201
|
utmMetadata
|
|
1168
1202
|
} = options;
|
|
1169
|
-
|
|
1203
|
+
if (couponCodes.length > MAX_COUPON_CODES) {
|
|
1204
|
+
throw new FloPayError6(
|
|
1205
|
+
`Too many coupon codes \u2014 a checkout session accepts at most ${MAX_COUPON_CODES}.`,
|
|
1206
|
+
"validation_error",
|
|
1207
|
+
{ code: "CouponLimitExceeded", param: "couponCodes" }
|
|
1208
|
+
);
|
|
1209
|
+
}
|
|
1210
|
+
const wireProducts = products ?? foldIntoProducts2(items, subscriptions);
|
|
1211
|
+
const sessionCurrency = resolveSessionCurrency2(currency, items, subscriptions, wireProducts);
|
|
1212
|
+
if (!sessionCurrency) {
|
|
1213
|
+
throw new FloPayError6(
|
|
1214
|
+
"currency is required: pass `currency` on the session, or include a `currency` on the first item/subscription/product.",
|
|
1215
|
+
"validation_error",
|
|
1216
|
+
{ code: "CurrencyRequired", param: "currency" }
|
|
1217
|
+
);
|
|
1218
|
+
}
|
|
1170
1219
|
const payload = {
|
|
1171
1220
|
clientId,
|
|
1221
|
+
checkoutVersion: SDK_VERSION2,
|
|
1172
1222
|
successUrl,
|
|
1173
1223
|
cancelUrl,
|
|
1174
1224
|
currency: sessionCurrency,
|
|
1175
1225
|
checkoutMode,
|
|
1176
|
-
|
|
1177
|
-
subscriptions: subscriptions.map((sub) => buildSubscriptionPayload2(sub, sessionCurrency)),
|
|
1226
|
+
products: wireProducts.map((product) => buildProductPayload2(product, sessionCurrency)),
|
|
1178
1227
|
accountData: {
|
|
1179
1228
|
userId: account.userId,
|
|
1180
1229
|
firstName: account.firstName ?? null,
|
|
@@ -1216,16 +1265,25 @@ async function createCheckoutSession(options) {
|
|
|
1216
1265
|
} finally {
|
|
1217
1266
|
clearTimeout(timer);
|
|
1218
1267
|
}
|
|
1268
|
+
if (status >= 400) {
|
|
1269
|
+
throw buildCheckoutSessionError(status, body);
|
|
1270
|
+
}
|
|
1219
1271
|
if (status === 201) {
|
|
1220
1272
|
const uuid = body?.data?.uuid;
|
|
1221
1273
|
if (!uuid) {
|
|
1222
1274
|
throw new Error("Checkout session created but no UUID was returned by the billing API");
|
|
1223
1275
|
}
|
|
1224
|
-
if (
|
|
1276
|
+
if (wireProducts.length || sessionCurrency) {
|
|
1225
1277
|
cacheSessionDisplayData(uuid, {
|
|
1226
|
-
currency,
|
|
1227
|
-
|
|
1228
|
-
|
|
1278
|
+
currency: sessionCurrency,
|
|
1279
|
+
products: wireProducts.map((p) => ({
|
|
1280
|
+
code: p.code ?? p.providerItemId ?? p.providerPlanId,
|
|
1281
|
+
type: p.type,
|
|
1282
|
+
name: p.name ?? p.itemName ?? p.providerItemName ?? p.subscriptionName ?? p.providerPlanName ?? null,
|
|
1283
|
+
totalAmount: p.totalAmount,
|
|
1284
|
+
overrideAmount: p.overrideAmount,
|
|
1285
|
+
currency: p.currency ?? sessionCurrency
|
|
1286
|
+
}))
|
|
1229
1287
|
});
|
|
1230
1288
|
}
|
|
1231
1289
|
const redirectUrl = new URL(`${checkoutBaseUrl.replace(/\/+$/, "")}/secure`);
|