@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.mjs CHANGED
@@ -447,7 +447,80 @@ var FloPayElements = class {
447
447
  };
448
448
 
449
449
  // src/payment-api.ts
450
- import { FloPayError as FloPayError3 } from "@flopay/shared";
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
- return response.json();
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
- providerItemId: item.providerItemId,
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(body.data),
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 || s.totalAmount),
756
- ...raw.items.map((i) => i.overrideAmount || i.totalAmount)
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: raw.subscriptions[0]?.currency ?? raw.items[0]?.currency ?? "USD",
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
- providerItemId: item.providerItemId,
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