@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.mjs
CHANGED
|
@@ -447,7 +447,80 @@ var FloPayElements = class {
|
|
|
447
447
|
};
|
|
448
448
|
|
|
449
449
|
// src/payment-api.ts
|
|
450
|
-
import {
|
|
450
|
+
import {
|
|
451
|
+
FloPayError as FloPayError3,
|
|
452
|
+
buildItemPayload,
|
|
453
|
+
buildSubscriptionPayload,
|
|
454
|
+
resolveSessionCurrency
|
|
455
|
+
} from "@flopay/shared";
|
|
456
|
+
|
|
457
|
+
// src/session-display-cache.ts
|
|
458
|
+
var STORAGE_KEY_PREFIX = "flopay_session_display:";
|
|
459
|
+
var DEFAULT_TTL_MS = 60 * 60 * 1e3;
|
|
460
|
+
var memoryStore = /* @__PURE__ */ new Map();
|
|
461
|
+
function storageKey(sessionId) {
|
|
462
|
+
return `${STORAGE_KEY_PREFIX}${sessionId}`;
|
|
463
|
+
}
|
|
464
|
+
function getSessionStorage() {
|
|
465
|
+
if (typeof window === "undefined") return null;
|
|
466
|
+
try {
|
|
467
|
+
return window.sessionStorage;
|
|
468
|
+
} catch {
|
|
469
|
+
return null;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
function cacheSessionDisplayData(sessionId, data, options) {
|
|
473
|
+
if (!sessionId) return;
|
|
474
|
+
const ttl = options?.ttlMs ?? DEFAULT_TTL_MS;
|
|
475
|
+
const entry = { data, expiresAt: Date.now() + ttl };
|
|
476
|
+
const storage = getSessionStorage();
|
|
477
|
+
if (storage) {
|
|
478
|
+
try {
|
|
479
|
+
storage.setItem(storageKey(sessionId), JSON.stringify(entry));
|
|
480
|
+
return;
|
|
481
|
+
} catch {
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
memoryStore.set(sessionId, entry);
|
|
485
|
+
}
|
|
486
|
+
function getSessionDisplayData(sessionId) {
|
|
487
|
+
if (!sessionId) return null;
|
|
488
|
+
const storage = getSessionStorage();
|
|
489
|
+
if (storage) {
|
|
490
|
+
try {
|
|
491
|
+
const raw = storage.getItem(storageKey(sessionId));
|
|
492
|
+
if (raw) {
|
|
493
|
+
const entry = JSON.parse(raw);
|
|
494
|
+
if (entry && typeof entry.expiresAt === "number" && entry.expiresAt > Date.now()) {
|
|
495
|
+
return entry.data;
|
|
496
|
+
}
|
|
497
|
+
storage.removeItem(storageKey(sessionId));
|
|
498
|
+
}
|
|
499
|
+
} catch {
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
const memEntry = memoryStore.get(sessionId);
|
|
503
|
+
if (memEntry) {
|
|
504
|
+
if (memEntry.expiresAt > Date.now()) {
|
|
505
|
+
return memEntry.data;
|
|
506
|
+
}
|
|
507
|
+
memoryStore.delete(sessionId);
|
|
508
|
+
}
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
function clearSessionDisplayData(sessionId) {
|
|
512
|
+
if (!sessionId) return;
|
|
513
|
+
memoryStore.delete(sessionId);
|
|
514
|
+
const storage = getSessionStorage();
|
|
515
|
+
if (storage) {
|
|
516
|
+
try {
|
|
517
|
+
storage.removeItem(storageKey(sessionId));
|
|
518
|
+
} catch {
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// src/payment-api.ts
|
|
451
524
|
var DEFAULT_PROCESSING_RETRY_AFTER_MS = 1e3;
|
|
452
525
|
var MIN_PROCESSING_RETRY_AFTER_MS = 500;
|
|
453
526
|
var DEFAULT_PROCESSING_TIMEOUT_MS = 15e3;
|
|
@@ -495,7 +568,37 @@ var PaymentAPI = class {
|
|
|
495
568
|
if (!response.ok) {
|
|
496
569
|
throw await buildApiErrorFromResponse(response, "Failed to get checkout session");
|
|
497
570
|
}
|
|
498
|
-
|
|
571
|
+
const body = await response.json();
|
|
572
|
+
return { ...body, data: this.mergeCachedDisplayData(body.data) };
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Stash display-only data for a session so subsequent fetches can fill in
|
|
576
|
+
* fields the backend no longer persists (`overrideAmount`, `totalAmount`,
|
|
577
|
+
* `providerItemName`, `providerPlanName`).
|
|
578
|
+
*
|
|
579
|
+
* Backed by `sessionStorage` in the browser, with an in-memory fallback in
|
|
580
|
+
* Node/SSR contexts. Default TTL: 1 hour.
|
|
581
|
+
*
|
|
582
|
+
* Server-returned values always win — cached values fill in only where the
|
|
583
|
+
* server returned `null` / `undefined`.
|
|
584
|
+
*
|
|
585
|
+
* @example
|
|
586
|
+
* ```ts
|
|
587
|
+
* paymentAPI.cacheSessionDisplayData(sessionId, {
|
|
588
|
+
* currency: 'USD',
|
|
589
|
+
* items: [{ code: 'pro_plan', overrideAmount: 24.99, providerItemName: 'Pro' }],
|
|
590
|
+
* });
|
|
591
|
+
* ```
|
|
592
|
+
*/
|
|
593
|
+
cacheSessionDisplayData(sessionId, data, options) {
|
|
594
|
+
cacheSessionDisplayData(sessionId, data, options);
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Drop any cached display data for a session. Call after the payment
|
|
598
|
+
* completes; otherwise the TTL handles cleanup.
|
|
599
|
+
*/
|
|
600
|
+
clearSessionDisplayData(sessionId) {
|
|
601
|
+
clearSessionDisplayData(sessionId);
|
|
499
602
|
}
|
|
500
603
|
/**
|
|
501
604
|
* Fetch and normalize a checkout session.
|
|
@@ -599,27 +702,19 @@ var PaymentAPI = class {
|
|
|
599
702
|
* Falls back to create + GET if the backend doesn't support `expand`.
|
|
600
703
|
*/
|
|
601
704
|
async createAndFetchSession(params) {
|
|
705
|
+
const sessionCurrency = resolveSessionCurrency(
|
|
706
|
+
params.currency,
|
|
707
|
+
params.items,
|
|
708
|
+
params.subscriptions
|
|
709
|
+
);
|
|
602
710
|
const payload = {
|
|
603
711
|
clientId: params.clientId,
|
|
604
712
|
successUrl: params.successUrl,
|
|
605
713
|
cancelUrl: params.cancelUrl,
|
|
714
|
+
currency: sessionCurrency,
|
|
606
715
|
checkoutMode: params.checkoutMode ?? "full",
|
|
607
|
-
items: (params.items ?? []).map((item) => (
|
|
608
|
-
|
|
609
|
-
providerItemName: item.providerItemName ?? null,
|
|
610
|
-
quantity: item.quantity ?? 1,
|
|
611
|
-
totalAmount: item.totalAmount,
|
|
612
|
-
overrideAmount: item.overrideAmount ?? null,
|
|
613
|
-
currency: item.currency ?? "USD"
|
|
614
|
-
})),
|
|
615
|
-
subscriptions: (params.subscriptions ?? []).map((sub) => ({
|
|
616
|
-
providerPlanId: sub.providerPlanId,
|
|
617
|
-
providerPlanName: sub.providerPlanName ?? null,
|
|
618
|
-
quantity: sub.quantity ?? 1,
|
|
619
|
-
totalAmount: sub.totalAmount,
|
|
620
|
-
overrideAmount: sub.overrideAmount ?? null,
|
|
621
|
-
currency: sub.currency ?? "USD"
|
|
622
|
-
})),
|
|
716
|
+
items: (params.items ?? []).map((item) => buildItemPayload(item, sessionCurrency)),
|
|
717
|
+
subscriptions: (params.subscriptions ?? []).map((sub) => buildSubscriptionPayload(sub, sessionCurrency)),
|
|
623
718
|
accountData: {
|
|
624
719
|
userId: params.account.userId,
|
|
625
720
|
firstName: params.account.firstName ?? null,
|
|
@@ -662,8 +757,10 @@ var PaymentAPI = class {
|
|
|
662
757
|
}
|
|
663
758
|
const body = await response.json();
|
|
664
759
|
if (body.data && "gateway" in body.data) {
|
|
760
|
+
this.autoCacheDisplayData(body.data.uuid, params);
|
|
761
|
+
const merged = this.mergeCachedDisplayData(body.data);
|
|
665
762
|
return {
|
|
666
|
-
...this.normalizeRawSession(
|
|
763
|
+
...this.normalizeRawSession(merged),
|
|
667
764
|
autoProcessingError: body.autoProcessingError,
|
|
668
765
|
autoProcessingAttempted: body.autoProcessingAttempted,
|
|
669
766
|
autoProcessingPending: body.autoProcessingPending
|
|
@@ -673,6 +770,7 @@ var PaymentAPI = class {
|
|
|
673
770
|
if (!uuid) {
|
|
674
771
|
throw new FloPayError3("No session ID returned", "api_error");
|
|
675
772
|
}
|
|
773
|
+
this.autoCacheDisplayData(uuid, params);
|
|
676
774
|
const unifiedSession = await this.getUnifiedCheckoutSession(uuid);
|
|
677
775
|
return {
|
|
678
776
|
...unifiedSession,
|
|
@@ -752,10 +850,11 @@ var PaymentAPI = class {
|
|
|
752
850
|
/** Convert raw session to the SDK CheckoutSession shape. */
|
|
753
851
|
toCheckoutSession(raw) {
|
|
754
852
|
const totalAmount = [
|
|
755
|
-
...raw.subscriptions.map((s) => s.overrideAmount
|
|
756
|
-
...raw.items.map((i) => i.overrideAmount
|
|
853
|
+
...raw.subscriptions.map((s) => s.overrideAmount ?? s.totalAmount ?? 0),
|
|
854
|
+
...raw.items.map((i) => i.overrideAmount ?? i.totalAmount ?? 0)
|
|
757
855
|
].reduce((sum, val) => sum + val, 0);
|
|
758
856
|
const amountInCents = Math.round(totalAmount * 100);
|
|
857
|
+
const currency = raw.currency ?? raw.subscriptions[0]?.currency ?? raw.items[0]?.currency ?? "USD";
|
|
759
858
|
return {
|
|
760
859
|
// Core fields (backward compat)
|
|
761
860
|
id: raw.uuid,
|
|
@@ -763,7 +862,7 @@ var PaymentAPI = class {
|
|
|
763
862
|
mode: raw.subscriptions.length > 0 ? "subscription" : "payment",
|
|
764
863
|
status: this.toCheckoutSessionStatus(raw.status),
|
|
765
864
|
amount: amountInCents,
|
|
766
|
-
currency
|
|
865
|
+
currency,
|
|
767
866
|
customer: {
|
|
768
867
|
id: raw.accountData.userId,
|
|
769
868
|
email: raw.accountData.email,
|
|
@@ -840,6 +939,76 @@ var PaymentAPI = class {
|
|
|
840
939
|
clampRetryAfterMs(retryAfterMs) {
|
|
841
940
|
return Math.max(0, Math.min(retryAfterMs, MAX_PROCESSING_RETRY_AFTER_MS));
|
|
842
941
|
}
|
|
942
|
+
/**
|
|
943
|
+
* Stash the display-only fields the consumer passed into a create-session
|
|
944
|
+
* call. Runs after the backend assigns a UUID so a later GET on the same
|
|
945
|
+
* session (typically after a redirect) can fill in fields the backend no
|
|
946
|
+
* longer persists — `overrideAmount`, `totalAmount`, `itemName`, etc.
|
|
947
|
+
*
|
|
948
|
+
* No-op when no UUID is available.
|
|
949
|
+
*/
|
|
950
|
+
autoCacheDisplayData(sessionId, params) {
|
|
951
|
+
if (!sessionId) return;
|
|
952
|
+
if (!params.items?.length && !params.subscriptions?.length && !params.currency) {
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
cacheSessionDisplayData(sessionId, {
|
|
956
|
+
currency: params.currency,
|
|
957
|
+
items: params.items,
|
|
958
|
+
subscriptions: params.subscriptions
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* Merge cached display-only fields (set by {@link cacheSessionDisplayData})
|
|
963
|
+
* into a raw session response and mirror the new/legacy name aliases so
|
|
964
|
+
* readers using either field always get a value when one exists.
|
|
965
|
+
*
|
|
966
|
+
* Server values always win — cache fills in only where the server returned
|
|
967
|
+
* `null` / `undefined`.
|
|
968
|
+
*/
|
|
969
|
+
mergeCachedDisplayData(raw) {
|
|
970
|
+
const cached = getSessionDisplayData(raw.uuid);
|
|
971
|
+
const cachedItems = /* @__PURE__ */ new Map();
|
|
972
|
+
for (const item of cached?.items ?? []) {
|
|
973
|
+
const key = item.code ?? item.providerItemId;
|
|
974
|
+
if (key) cachedItems.set(key, item);
|
|
975
|
+
}
|
|
976
|
+
const cachedSubs = /* @__PURE__ */ new Map();
|
|
977
|
+
for (const sub of cached?.subscriptions ?? []) {
|
|
978
|
+
const key = sub.code ?? sub.providerPlanId;
|
|
979
|
+
if (key) cachedSubs.set(key, sub);
|
|
980
|
+
}
|
|
981
|
+
return {
|
|
982
|
+
...raw,
|
|
983
|
+
currency: raw.currency ?? cached?.currency,
|
|
984
|
+
items: raw.items.map((item) => {
|
|
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
|
+
})
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
843
1012
|
};
|
|
844
1013
|
|
|
845
1014
|
// src/flopay.ts
|
|
@@ -973,6 +1142,11 @@ async function loadFloPay(publishableKey, options) {
|
|
|
973
1142
|
}
|
|
974
1143
|
|
|
975
1144
|
// src/create-checkout-session.ts
|
|
1145
|
+
import {
|
|
1146
|
+
buildItemPayload as buildItemPayload2,
|
|
1147
|
+
buildSubscriptionPayload as buildSubscriptionPayload2,
|
|
1148
|
+
resolveSessionCurrency as resolveSessionCurrency2
|
|
1149
|
+
} from "@flopay/shared";
|
|
976
1150
|
async function createCheckoutSession(options) {
|
|
977
1151
|
const {
|
|
978
1152
|
billingApiUrl,
|
|
@@ -989,29 +1163,18 @@ async function createCheckoutSession(options) {
|
|
|
989
1163
|
setCookie = true,
|
|
990
1164
|
timeoutMs = 12e3,
|
|
991
1165
|
clientId,
|
|
1166
|
+
currency,
|
|
992
1167
|
utmMetadata
|
|
993
1168
|
} = options;
|
|
1169
|
+
const sessionCurrency = resolveSessionCurrency2(currency, items, subscriptions);
|
|
994
1170
|
const payload = {
|
|
995
1171
|
clientId,
|
|
996
1172
|
successUrl,
|
|
997
1173
|
cancelUrl,
|
|
1174
|
+
currency: sessionCurrency,
|
|
998
1175
|
checkoutMode,
|
|
999
|
-
items: items.map((item) => (
|
|
1000
|
-
|
|
1001
|
-
providerItemName: item.providerItemName ?? null,
|
|
1002
|
-
quantity: item.quantity ?? 1,
|
|
1003
|
-
totalAmount: item.totalAmount,
|
|
1004
|
-
overrideAmount: item.overrideAmount ?? null,
|
|
1005
|
-
currency: item.currency ?? "USD"
|
|
1006
|
-
})),
|
|
1007
|
-
subscriptions: subscriptions.map((sub) => ({
|
|
1008
|
-
providerPlanId: sub.providerPlanId,
|
|
1009
|
-
providerPlanName: sub.providerPlanName ?? null,
|
|
1010
|
-
quantity: sub.quantity ?? 1,
|
|
1011
|
-
totalAmount: sub.totalAmount,
|
|
1012
|
-
overrideAmount: sub.overrideAmount ?? null,
|
|
1013
|
-
currency: sub.currency ?? "USD"
|
|
1014
|
-
})),
|
|
1176
|
+
items: items.map((item) => buildItemPayload2(item, sessionCurrency)),
|
|
1177
|
+
subscriptions: subscriptions.map((sub) => buildSubscriptionPayload2(sub, sessionCurrency)),
|
|
1015
1178
|
accountData: {
|
|
1016
1179
|
userId: account.userId,
|
|
1017
1180
|
firstName: account.firstName ?? null,
|
|
@@ -1058,6 +1221,13 @@ async function createCheckoutSession(options) {
|
|
|
1058
1221
|
if (!uuid) {
|
|
1059
1222
|
throw new Error("Checkout session created but no UUID was returned by the billing API");
|
|
1060
1223
|
}
|
|
1224
|
+
if (items.length || subscriptions.length || currency) {
|
|
1225
|
+
cacheSessionDisplayData(uuid, {
|
|
1226
|
+
currency,
|
|
1227
|
+
items,
|
|
1228
|
+
subscriptions
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1061
1231
|
const redirectUrl = new URL(`${checkoutBaseUrl.replace(/\/+$/, "")}/secure`);
|
|
1062
1232
|
redirectUrl.searchParams.set("id", uuid);
|
|
1063
1233
|
for (const [key, value] of Object.entries(redirectParams)) {
|
|
@@ -1106,8 +1276,11 @@ export {
|
|
|
1106
1276
|
FloPayElements,
|
|
1107
1277
|
PaymentAPI,
|
|
1108
1278
|
StripeAdapter,
|
|
1279
|
+
cacheSessionDisplayData,
|
|
1280
|
+
clearSessionDisplayData,
|
|
1109
1281
|
createCheckoutSession,
|
|
1110
1282
|
createCheckoutSessionWithRetries,
|
|
1283
|
+
getSessionDisplayData,
|
|
1111
1284
|
loadFloPay
|
|
1112
1285
|
};
|
|
1113
1286
|
//# sourceMappingURL=index.mjs.map
|