@flopay/js 1.0.0 → 1.0.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/dist/index.cjs +204 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +150 -15
- package/dist/index.d.ts +150 -15
- package/dist/index.mjs +211 -38
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -34,8 +34,11 @@ __export(index_exports, {
|
|
|
34
34
|
FloPayElements: () => FloPayElements,
|
|
35
35
|
PaymentAPI: () => PaymentAPI,
|
|
36
36
|
StripeAdapter: () => StripeAdapter,
|
|
37
|
+
cacheSessionDisplayData: () => cacheSessionDisplayData,
|
|
38
|
+
clearSessionDisplayData: () => clearSessionDisplayData,
|
|
37
39
|
createCheckoutSession: () => createCheckoutSession,
|
|
38
40
|
createCheckoutSessionWithRetries: () => createCheckoutSessionWithRetries,
|
|
41
|
+
getSessionDisplayData: () => getSessionDisplayData,
|
|
39
42
|
loadFloPay: () => loadFloPay
|
|
40
43
|
});
|
|
41
44
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -490,6 +493,74 @@ var FloPayElements = class {
|
|
|
490
493
|
|
|
491
494
|
// src/payment-api.ts
|
|
492
495
|
var import_shared3 = require("@flopay/shared");
|
|
496
|
+
|
|
497
|
+
// src/session-display-cache.ts
|
|
498
|
+
var STORAGE_KEY_PREFIX = "flopay_session_display:";
|
|
499
|
+
var DEFAULT_TTL_MS = 60 * 60 * 1e3;
|
|
500
|
+
var memoryStore = /* @__PURE__ */ new Map();
|
|
501
|
+
function storageKey(sessionId) {
|
|
502
|
+
return `${STORAGE_KEY_PREFIX}${sessionId}`;
|
|
503
|
+
}
|
|
504
|
+
function getSessionStorage() {
|
|
505
|
+
if (typeof window === "undefined") return null;
|
|
506
|
+
try {
|
|
507
|
+
return window.sessionStorage;
|
|
508
|
+
} catch {
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
function cacheSessionDisplayData(sessionId, data, options) {
|
|
513
|
+
if (!sessionId) return;
|
|
514
|
+
const ttl = options?.ttlMs ?? DEFAULT_TTL_MS;
|
|
515
|
+
const entry = { data, expiresAt: Date.now() + ttl };
|
|
516
|
+
const storage = getSessionStorage();
|
|
517
|
+
if (storage) {
|
|
518
|
+
try {
|
|
519
|
+
storage.setItem(storageKey(sessionId), JSON.stringify(entry));
|
|
520
|
+
return;
|
|
521
|
+
} catch {
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
memoryStore.set(sessionId, entry);
|
|
525
|
+
}
|
|
526
|
+
function getSessionDisplayData(sessionId) {
|
|
527
|
+
if (!sessionId) return null;
|
|
528
|
+
const storage = getSessionStorage();
|
|
529
|
+
if (storage) {
|
|
530
|
+
try {
|
|
531
|
+
const raw = storage.getItem(storageKey(sessionId));
|
|
532
|
+
if (raw) {
|
|
533
|
+
const entry = JSON.parse(raw);
|
|
534
|
+
if (entry && typeof entry.expiresAt === "number" && entry.expiresAt > Date.now()) {
|
|
535
|
+
return entry.data;
|
|
536
|
+
}
|
|
537
|
+
storage.removeItem(storageKey(sessionId));
|
|
538
|
+
}
|
|
539
|
+
} catch {
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
const memEntry = memoryStore.get(sessionId);
|
|
543
|
+
if (memEntry) {
|
|
544
|
+
if (memEntry.expiresAt > Date.now()) {
|
|
545
|
+
return memEntry.data;
|
|
546
|
+
}
|
|
547
|
+
memoryStore.delete(sessionId);
|
|
548
|
+
}
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
function clearSessionDisplayData(sessionId) {
|
|
552
|
+
if (!sessionId) return;
|
|
553
|
+
memoryStore.delete(sessionId);
|
|
554
|
+
const storage = getSessionStorage();
|
|
555
|
+
if (storage) {
|
|
556
|
+
try {
|
|
557
|
+
storage.removeItem(storageKey(sessionId));
|
|
558
|
+
} catch {
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// src/payment-api.ts
|
|
493
564
|
var DEFAULT_PROCESSING_RETRY_AFTER_MS = 1e3;
|
|
494
565
|
var MIN_PROCESSING_RETRY_AFTER_MS = 500;
|
|
495
566
|
var DEFAULT_PROCESSING_TIMEOUT_MS = 15e3;
|
|
@@ -537,7 +608,37 @@ var PaymentAPI = class {
|
|
|
537
608
|
if (!response.ok) {
|
|
538
609
|
throw await buildApiErrorFromResponse(response, "Failed to get checkout session");
|
|
539
610
|
}
|
|
540
|
-
|
|
611
|
+
const body = await response.json();
|
|
612
|
+
return { ...body, data: this.mergeCachedDisplayData(body.data) };
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Stash display-only data for a session so subsequent fetches can fill in
|
|
616
|
+
* fields the backend no longer persists (`overrideAmount`, `totalAmount`,
|
|
617
|
+
* `providerItemName`, `providerPlanName`).
|
|
618
|
+
*
|
|
619
|
+
* Backed by `sessionStorage` in the browser, with an in-memory fallback in
|
|
620
|
+
* Node/SSR contexts. Default TTL: 1 hour.
|
|
621
|
+
*
|
|
622
|
+
* Server-returned values always win — cached values fill in only where the
|
|
623
|
+
* server returned `null` / `undefined`.
|
|
624
|
+
*
|
|
625
|
+
* @example
|
|
626
|
+
* ```ts
|
|
627
|
+
* paymentAPI.cacheSessionDisplayData(sessionId, {
|
|
628
|
+
* currency: 'USD',
|
|
629
|
+
* items: [{ code: 'pro_plan', overrideAmount: 24.99, providerItemName: 'Pro' }],
|
|
630
|
+
* });
|
|
631
|
+
* ```
|
|
632
|
+
*/
|
|
633
|
+
cacheSessionDisplayData(sessionId, data, options) {
|
|
634
|
+
cacheSessionDisplayData(sessionId, data, options);
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Drop any cached display data for a session. Call after the payment
|
|
638
|
+
* completes; otherwise the TTL handles cleanup.
|
|
639
|
+
*/
|
|
640
|
+
clearSessionDisplayData(sessionId) {
|
|
641
|
+
clearSessionDisplayData(sessionId);
|
|
541
642
|
}
|
|
542
643
|
/**
|
|
543
644
|
* Fetch and normalize a checkout session.
|
|
@@ -641,27 +742,19 @@ var PaymentAPI = class {
|
|
|
641
742
|
* Falls back to create + GET if the backend doesn't support `expand`.
|
|
642
743
|
*/
|
|
643
744
|
async createAndFetchSession(params) {
|
|
745
|
+
const sessionCurrency = (0, import_shared3.resolveSessionCurrency)(
|
|
746
|
+
params.currency,
|
|
747
|
+
params.items,
|
|
748
|
+
params.subscriptions
|
|
749
|
+
);
|
|
644
750
|
const payload = {
|
|
645
751
|
clientId: params.clientId,
|
|
646
752
|
successUrl: params.successUrl,
|
|
647
753
|
cancelUrl: params.cancelUrl,
|
|
754
|
+
currency: sessionCurrency,
|
|
648
755
|
checkoutMode: params.checkoutMode ?? "full",
|
|
649
|
-
items: (params.items ?? []).map((item) => (
|
|
650
|
-
|
|
651
|
-
providerItemName: item.providerItemName ?? null,
|
|
652
|
-
quantity: item.quantity ?? 1,
|
|
653
|
-
totalAmount: item.totalAmount,
|
|
654
|
-
overrideAmount: item.overrideAmount ?? null,
|
|
655
|
-
currency: item.currency ?? "USD"
|
|
656
|
-
})),
|
|
657
|
-
subscriptions: (params.subscriptions ?? []).map((sub) => ({
|
|
658
|
-
providerPlanId: sub.providerPlanId,
|
|
659
|
-
providerPlanName: sub.providerPlanName ?? null,
|
|
660
|
-
quantity: sub.quantity ?? 1,
|
|
661
|
-
totalAmount: sub.totalAmount,
|
|
662
|
-
overrideAmount: sub.overrideAmount ?? null,
|
|
663
|
-
currency: sub.currency ?? "USD"
|
|
664
|
-
})),
|
|
756
|
+
items: (params.items ?? []).map((item) => (0, import_shared3.buildItemPayload)(item, sessionCurrency)),
|
|
757
|
+
subscriptions: (params.subscriptions ?? []).map((sub) => (0, import_shared3.buildSubscriptionPayload)(sub, sessionCurrency)),
|
|
665
758
|
accountData: {
|
|
666
759
|
userId: params.account.userId,
|
|
667
760
|
firstName: params.account.firstName ?? null,
|
|
@@ -704,8 +797,10 @@ var PaymentAPI = class {
|
|
|
704
797
|
}
|
|
705
798
|
const body = await response.json();
|
|
706
799
|
if (body.data && "gateway" in body.data) {
|
|
800
|
+
this.autoCacheDisplayData(body.data.uuid, params);
|
|
801
|
+
const merged = this.mergeCachedDisplayData(body.data);
|
|
707
802
|
return {
|
|
708
|
-
...this.normalizeRawSession(
|
|
803
|
+
...this.normalizeRawSession(merged),
|
|
709
804
|
autoProcessingError: body.autoProcessingError,
|
|
710
805
|
autoProcessingAttempted: body.autoProcessingAttempted,
|
|
711
806
|
autoProcessingPending: body.autoProcessingPending
|
|
@@ -715,6 +810,7 @@ var PaymentAPI = class {
|
|
|
715
810
|
if (!uuid) {
|
|
716
811
|
throw new import_shared3.FloPayError("No session ID returned", "api_error");
|
|
717
812
|
}
|
|
813
|
+
this.autoCacheDisplayData(uuid, params);
|
|
718
814
|
const unifiedSession = await this.getUnifiedCheckoutSession(uuid);
|
|
719
815
|
return {
|
|
720
816
|
...unifiedSession,
|
|
@@ -794,10 +890,11 @@ var PaymentAPI = class {
|
|
|
794
890
|
/** Convert raw session to the SDK CheckoutSession shape. */
|
|
795
891
|
toCheckoutSession(raw) {
|
|
796
892
|
const totalAmount = [
|
|
797
|
-
...raw.subscriptions.map((s) => s.overrideAmount
|
|
798
|
-
...raw.items.map((i) => i.overrideAmount
|
|
893
|
+
...raw.subscriptions.map((s) => s.overrideAmount ?? s.totalAmount ?? 0),
|
|
894
|
+
...raw.items.map((i) => i.overrideAmount ?? i.totalAmount ?? 0)
|
|
799
895
|
].reduce((sum, val) => sum + val, 0);
|
|
800
896
|
const amountInCents = Math.round(totalAmount * 100);
|
|
897
|
+
const currency = raw.currency ?? raw.subscriptions[0]?.currency ?? raw.items[0]?.currency ?? "USD";
|
|
801
898
|
return {
|
|
802
899
|
// Core fields (backward compat)
|
|
803
900
|
id: raw.uuid,
|
|
@@ -805,7 +902,7 @@ var PaymentAPI = class {
|
|
|
805
902
|
mode: raw.subscriptions.length > 0 ? "subscription" : "payment",
|
|
806
903
|
status: this.toCheckoutSessionStatus(raw.status),
|
|
807
904
|
amount: amountInCents,
|
|
808
|
-
currency
|
|
905
|
+
currency,
|
|
809
906
|
customer: {
|
|
810
907
|
id: raw.accountData.userId,
|
|
811
908
|
email: raw.accountData.email,
|
|
@@ -882,6 +979,76 @@ var PaymentAPI = class {
|
|
|
882
979
|
clampRetryAfterMs(retryAfterMs) {
|
|
883
980
|
return Math.max(0, Math.min(retryAfterMs, MAX_PROCESSING_RETRY_AFTER_MS));
|
|
884
981
|
}
|
|
982
|
+
/**
|
|
983
|
+
* Stash the display-only fields the consumer passed into a create-session
|
|
984
|
+
* call. Runs after the backend assigns a UUID so a later GET on the same
|
|
985
|
+
* session (typically after a redirect) can fill in fields the backend no
|
|
986
|
+
* longer persists — `overrideAmount`, `totalAmount`, `itemName`, etc.
|
|
987
|
+
*
|
|
988
|
+
* No-op when no UUID is available.
|
|
989
|
+
*/
|
|
990
|
+
autoCacheDisplayData(sessionId, params) {
|
|
991
|
+
if (!sessionId) return;
|
|
992
|
+
if (!params.items?.length && !params.subscriptions?.length && !params.currency) {
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
cacheSessionDisplayData(sessionId, {
|
|
996
|
+
currency: params.currency,
|
|
997
|
+
items: params.items,
|
|
998
|
+
subscriptions: params.subscriptions
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
/**
|
|
1002
|
+
* Merge cached display-only fields (set by {@link cacheSessionDisplayData})
|
|
1003
|
+
* into a raw session response and mirror the new/legacy name aliases so
|
|
1004
|
+
* readers using either field always get a value when one exists.
|
|
1005
|
+
*
|
|
1006
|
+
* Server values always win — cache fills in only where the server returned
|
|
1007
|
+
* `null` / `undefined`.
|
|
1008
|
+
*/
|
|
1009
|
+
mergeCachedDisplayData(raw) {
|
|
1010
|
+
const cached = getSessionDisplayData(raw.uuid);
|
|
1011
|
+
const cachedItems = /* @__PURE__ */ new Map();
|
|
1012
|
+
for (const item of cached?.items ?? []) {
|
|
1013
|
+
const key = item.code ?? item.providerItemId;
|
|
1014
|
+
if (key) cachedItems.set(key, item);
|
|
1015
|
+
}
|
|
1016
|
+
const cachedSubs = /* @__PURE__ */ new Map();
|
|
1017
|
+
for (const sub of cached?.subscriptions ?? []) {
|
|
1018
|
+
const key = sub.code ?? sub.providerPlanId;
|
|
1019
|
+
if (key) cachedSubs.set(key, sub);
|
|
1020
|
+
}
|
|
1021
|
+
return {
|
|
1022
|
+
...raw,
|
|
1023
|
+
currency: raw.currency ?? cached?.currency,
|
|
1024
|
+
items: raw.items.map((item) => {
|
|
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
|
+
})
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
885
1052
|
};
|
|
886
1053
|
|
|
887
1054
|
// src/flopay.ts
|
|
@@ -1015,6 +1182,7 @@ async function loadFloPay(publishableKey, options) {
|
|
|
1015
1182
|
}
|
|
1016
1183
|
|
|
1017
1184
|
// src/create-checkout-session.ts
|
|
1185
|
+
var import_shared6 = require("@flopay/shared");
|
|
1018
1186
|
async function createCheckoutSession(options) {
|
|
1019
1187
|
const {
|
|
1020
1188
|
billingApiUrl,
|
|
@@ -1031,29 +1199,18 @@ async function createCheckoutSession(options) {
|
|
|
1031
1199
|
setCookie = true,
|
|
1032
1200
|
timeoutMs = 12e3,
|
|
1033
1201
|
clientId,
|
|
1202
|
+
currency,
|
|
1034
1203
|
utmMetadata
|
|
1035
1204
|
} = options;
|
|
1205
|
+
const sessionCurrency = (0, import_shared6.resolveSessionCurrency)(currency, items, subscriptions);
|
|
1036
1206
|
const payload = {
|
|
1037
1207
|
clientId,
|
|
1038
1208
|
successUrl,
|
|
1039
1209
|
cancelUrl,
|
|
1210
|
+
currency: sessionCurrency,
|
|
1040
1211
|
checkoutMode,
|
|
1041
|
-
items: items.map((item) => (
|
|
1042
|
-
|
|
1043
|
-
providerItemName: item.providerItemName ?? null,
|
|
1044
|
-
quantity: item.quantity ?? 1,
|
|
1045
|
-
totalAmount: item.totalAmount,
|
|
1046
|
-
overrideAmount: item.overrideAmount ?? null,
|
|
1047
|
-
currency: item.currency ?? "USD"
|
|
1048
|
-
})),
|
|
1049
|
-
subscriptions: subscriptions.map((sub) => ({
|
|
1050
|
-
providerPlanId: sub.providerPlanId,
|
|
1051
|
-
providerPlanName: sub.providerPlanName ?? null,
|
|
1052
|
-
quantity: sub.quantity ?? 1,
|
|
1053
|
-
totalAmount: sub.totalAmount,
|
|
1054
|
-
overrideAmount: sub.overrideAmount ?? null,
|
|
1055
|
-
currency: sub.currency ?? "USD"
|
|
1056
|
-
})),
|
|
1212
|
+
items: items.map((item) => (0, import_shared6.buildItemPayload)(item, sessionCurrency)),
|
|
1213
|
+
subscriptions: subscriptions.map((sub) => (0, import_shared6.buildSubscriptionPayload)(sub, sessionCurrency)),
|
|
1057
1214
|
accountData: {
|
|
1058
1215
|
userId: account.userId,
|
|
1059
1216
|
firstName: account.firstName ?? null,
|
|
@@ -1100,6 +1257,13 @@ async function createCheckoutSession(options) {
|
|
|
1100
1257
|
if (!uuid) {
|
|
1101
1258
|
throw new Error("Checkout session created but no UUID was returned by the billing API");
|
|
1102
1259
|
}
|
|
1260
|
+
if (items.length || subscriptions.length || currency) {
|
|
1261
|
+
cacheSessionDisplayData(uuid, {
|
|
1262
|
+
currency,
|
|
1263
|
+
items,
|
|
1264
|
+
subscriptions
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1103
1267
|
const redirectUrl = new URL(`${checkoutBaseUrl.replace(/\/+$/, "")}/secure`);
|
|
1104
1268
|
redirectUrl.searchParams.set("id", uuid);
|
|
1105
1269
|
for (const [key, value] of Object.entries(redirectParams)) {
|
|
@@ -1149,8 +1313,11 @@ async function createCheckoutSessionWithRetries(options) {
|
|
|
1149
1313
|
FloPayElements,
|
|
1150
1314
|
PaymentAPI,
|
|
1151
1315
|
StripeAdapter,
|
|
1316
|
+
cacheSessionDisplayData,
|
|
1317
|
+
clearSessionDisplayData,
|
|
1152
1318
|
createCheckoutSession,
|
|
1153
1319
|
createCheckoutSessionWithRetries,
|
|
1320
|
+
getSessionDisplayData,
|
|
1154
1321
|
loadFloPay
|
|
1155
1322
|
});
|
|
1156
1323
|
//# sourceMappingURL=index.cjs.map
|