@flopay/shared 1.1.0 → 1.1.4

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 CHANGED
@@ -79,12 +79,13 @@ throw validationError('Email is required', 'email');
79
79
  | `ButtonsLayoutStyles` | Style overrides for the `buttons` layout: `cardButton`, `cardButtonFontSize`, `cardFormContainer`, `cardInputBorder`, `cardInputColor`, `cardInputPlaceholderColor`, `cardInputFontSize`, `cardInputBackground`, `nameInput`, `backButton`, `backButtonFontSize`, `backButtonIcon`, `submitButton`, `submitButtonFontSize`, `title`, `titleFontSize`, `errorBanner` |
80
80
  | `ButtonsLayoutTheme` | Theme preset name: `'default'` / `'minimal'` / `'rounded'` / `'dark'` |
81
81
  | `CheckoutMode` | Checkout mode: `'full'` / `'auto'` / `'confirm'` |
82
- | `CheckoutSession` | Session object: `id`, `clientSecret`, `mode`, `status`, `amount`, `currency`, `customer?`, `checkoutMode?`, `items?`, `subscriptions?`, `successUrl?`, `cancelUrl?`, `coupons?`, `gateways?` (map keyed by gateway code — `stripe`, `paypal`, …), `accountData?`, `tagsData?` |
82
+ | `CheckoutSession` | Session object: `id`, `clientSecret`, `mode`, `status`, `amount`, `currency`, `customer?`, `checkoutMode?`, `products?` (unified `CheckoutSessionProduct[]`), `successUrl?`, `cancelUrl?`, `coupons?`, `subtotalAmount?` / `discountAmount?` / `totalAmount?` (cart-currency major units; populated by billing API ≥ v1.1.2), `gateways?` (map keyed by gateway code — `stripe`, `paypal`, …), `accountData?`, `tagsData?` |
83
+ | `CheckoutSessionProduct` | Unified product from session response: `uuid`, `checkoutSessionId`, `type` (`'item'`/`'subscription'`), `code?`, `name?`, `description?`, `quantity`, `totalAmount?`, `overrideAmount?`, `currency?`, `metadata?` |
84
+ | `CheckoutProductType` | `'item'` / `'subscription'` |
85
+ | `CheckoutProduct` | Unified product input for session creation: `type`, `code?`, `name?`, `quantity?`, `totalAmount?`, `overrideAmount?`, `currency?`, `metadata?` |
83
86
  | `CheckoutGateway` | Per-gateway config: `publishableKey?`, `environment?` (`'sandbox'`/`'live'`), `stripeClientSecret?` (Stripe-only) |
84
87
  | `CheckoutGateways` | Map keyed by gateway code (e.g. `stripe`, `paypal`). A session can advertise multiple concurrent gateways. |
85
88
  | `GatewayEnvironment` | `'sandbox'` / `'live'` — drives the PayPal JS SDK script and any other environment-scoped behavior |
86
- | `CheckoutSessionItem` | Session item from billing API: `uuid`, `providerItemId`, `providerItemName`, `quantity`, `totalAmount`, `overrideAmount`, `currency` |
87
- | `CheckoutSessionSubscription` | Session subscription from billing API: `uuid`, `providerPlanId`, `providerPlanName`, `quantity`, `totalAmount`, `overrideAmount`, `currency` |
88
89
  | `PaymentResult` | Payment outcome: `status` (`'succeeded'`/`'processing'`/`'requires_action'`/`'failed'`), `paymentIntentId?`, `error?` |
89
90
  | `ConfirmPaymentParams` | `clientSecret`, `returnUrl?` |
90
91
  | `CreatePaymentMethodResult` | `paymentMethodId`, `error?` |
@@ -103,7 +104,7 @@ throw validationError('Email is required', 'email');
103
104
  | `TokenizedBody` | `id?`, `type?`, `threeDSecureActionResultTokenId?`, `originalPaymentMethodId?`, `isPaypal?` |
104
105
  | `NormalizedCheckoutSession` | Provider-agnostic session: `provider`, `mode`, `data`, `raw?` |
105
106
  | `CheckoutModeKind` | `'tokenize'` / `'redirect'` |
106
- | `CreateSessionParams` | Full session creation params: `billingApiUrl`, `checkoutBaseUrl`, `clientId`, `items?`, `subscriptions?`, `account`, `successUrl`, `cancelUrl`, `checkoutMode?`, `couponCodes?`, `tagsData?`, `redirectParams?`, `setCookie?`, `timeoutMs?`, `utmMetadata?` |
107
+ | `CreateSessionParams` | Full session creation params: `billingApiUrl`, `checkoutBaseUrl`, `clientId`, `currency` (required — SDK throws `FloPayError({ type: 'validation_error', code: 'CurrencyRequired' })` before issuing the request when none can be resolved), `products?` (unified shape; takes precedence), `items?`, `subscriptions?` (legacy — folded into `products[]` before send), `account`, `successUrl`, `cancelUrl`, `checkoutMode?`, `couponCodes?`, `tagsData?`, `redirectParams?`, `setCookie?`, `timeoutMs?`, `utmMetadata?` |
107
108
  | `CheckoutSessionResult` | `{ status: 201; redirectUrl }` / `{ status: 204 }` / `{ status: number }` |
108
109
  | `CheckoutItem` | `providerItemId`, `providerItemName?`, `quantity?`, `totalAmount`, `overrideAmount?`, `currency?` |
109
110
  | `CheckoutSubscription` | `providerPlanId`, `providerPlanName?`, `quantity?`, `totalAmount`, `overrideAmount?`, `currency?` |
package/dist/index.cjs CHANGED
@@ -45,8 +45,10 @@ __export(index_exports, {
45
45
  authenticationError: () => authenticationError,
46
46
  buildCheckoutDisplayData: () => buildCheckoutDisplayData,
47
47
  buildItemPayload: () => buildItemPayload,
48
+ buildProductPayload: () => buildProductPayload,
48
49
  buildSubscriptionPayload: () => buildSubscriptionPayload,
49
50
  configureFlopay: () => configureFlopay,
51
+ foldIntoProducts: () => foldIntoProducts,
50
52
  getConfiguredBillingApiUrl: () => getConfiguredBillingApiUrl,
51
53
  getCountryByCode: () => getCountryByCode,
52
54
  getCurrencyByCountry: () => getCurrencyByCountry,
@@ -135,7 +137,7 @@ function getFloPayEnvironment() {
135
137
  }
136
138
 
137
139
  // src/constants.ts
138
- var SDK_VERSION = "1.1.0";
140
+ var SDK_VERSION = "1.1.4";
139
141
  var BILLING_API_URL_STAGING = "https://api.stage.flopay.com";
140
142
  var BILLING_API_URL_PRODUCTION = "https://api.flopay.com";
141
143
  var DEFAULT_API_BASE_URL = BILLING_API_URL_STAGING;
@@ -794,7 +796,7 @@ function nonBlank(value) {
794
796
  const trimmed = value.trim();
795
797
  return trimmed.length > 0 ? trimmed : void 0;
796
798
  }
797
- function resolveSessionCurrency(sessionCurrency, items, subscriptions) {
799
+ function resolveSessionCurrency(sessionCurrency, items, subscriptions, products) {
798
800
  const session = nonBlank(sessionCurrency);
799
801
  if (session) return session;
800
802
  for (const item of items ?? []) {
@@ -805,7 +807,67 @@ function resolveSessionCurrency(sessionCurrency, items, subscriptions) {
805
807
  const c = nonBlank(sub.currency);
806
808
  if (c) return c;
807
809
  }
808
- return "USD";
810
+ for (const product of products ?? []) {
811
+ const c = nonBlank(product.currency);
812
+ if (c) return c;
813
+ }
814
+ return null;
815
+ }
816
+ function foldIntoProducts(items, subscriptions) {
817
+ const products = [];
818
+ for (const sub of subscriptions ?? []) {
819
+ products.push({
820
+ type: "subscription",
821
+ code: nonBlank(sub.code) ?? nonBlank(sub.providerPlanId),
822
+ providerPlanId: sub.providerPlanId,
823
+ name: nonBlank(sub.subscriptionName) ?? nonBlank(sub.providerPlanName) ?? null,
824
+ subscriptionName: sub.subscriptionName ?? null,
825
+ providerPlanName: sub.providerPlanName ?? null,
826
+ quantity: sub.quantity,
827
+ totalAmount: sub.totalAmount,
828
+ overrideAmount: sub.overrideAmount,
829
+ currency: sub.currency,
830
+ metadata: sub.metadata
831
+ });
832
+ }
833
+ for (const item of items ?? []) {
834
+ products.push({
835
+ type: "item",
836
+ code: nonBlank(item.code) ?? nonBlank(item.providerItemId),
837
+ providerItemId: item.providerItemId,
838
+ name: nonBlank(item.itemName) ?? nonBlank(item.providerItemName) ?? null,
839
+ itemName: item.itemName ?? null,
840
+ providerItemName: item.providerItemName ?? null,
841
+ quantity: item.quantity,
842
+ totalAmount: item.totalAmount,
843
+ overrideAmount: item.overrideAmount,
844
+ currency: item.currency,
845
+ metadata: item.metadata
846
+ });
847
+ }
848
+ return products;
849
+ }
850
+ function buildProductPayload(product, sessionCurrency) {
851
+ const code = nonBlank(product.code) ?? nonBlank(product.providerItemId) ?? nonBlank(product.providerPlanId);
852
+ if (!code) {
853
+ throw new Error(
854
+ "CheckoutProduct requires `code` (or the deprecated `providerItemId` / `providerPlanId`)."
855
+ );
856
+ }
857
+ const name = nonBlank(product.name) ?? nonBlank(product.itemName) ?? nonBlank(product.providerItemName) ?? nonBlank(product.subscriptionName) ?? nonBlank(product.providerPlanName) ?? null;
858
+ const payload = {
859
+ type: product.type,
860
+ code,
861
+ name,
862
+ quantity: product.quantity ?? 1,
863
+ totalAmount: product.totalAmount,
864
+ overrideAmount: product.overrideAmount ?? null,
865
+ currency: nonBlank(product.currency) ?? sessionCurrency
866
+ };
867
+ if (product.metadata) {
868
+ payload["metadata"] = product.metadata;
869
+ }
870
+ return payload;
809
871
  }
810
872
  function buildItemPayload(item, sessionCurrency) {
811
873
  const code = nonBlank(item.code) ?? nonBlank(item.providerItemId);
@@ -855,13 +917,15 @@ function buildSubscriptionPayload(subscription, sessionCurrency) {
855
917
  // src/display.ts
856
918
  function buildCheckoutDisplayData(session, options = {}) {
857
919
  const itemsList = [];
858
- const subscriptions = session.subscriptions ?? [];
859
- const items = session.items ?? [];
860
- const currency = resolveSessionCurrency(session.currency, items, subscriptions).toUpperCase();
920
+ const products = session.products ?? [];
921
+ const subscriptions = products.filter((p) => p.type === "subscription");
922
+ const items = products.filter((p) => p.type === "item");
923
+ const resolvedCurrency = resolveSessionCurrency(session.currency, void 0, void 0, products);
924
+ const currency = (resolvedCurrency ?? "USD").toUpperCase();
861
925
  for (const sub of subscriptions) {
862
926
  const originalPrice = sub.totalAmount ?? 0;
863
927
  const discountedPrice = sub.overrideAmount ?? originalPrice;
864
- let name = sub.subscriptionName || sub.providerPlanName || sub.code || "Subscription";
928
+ let name = sub.name || sub.code || "Subscription";
865
929
  if (name === "4-WEEK PLAN" && discountedPrice <= 1) {
866
930
  name = "7-DAY TRIAL: FULL ACCESS";
867
931
  }
@@ -878,7 +942,7 @@ function buildCheckoutDisplayData(session, options = {}) {
878
942
  const originalPrice = item.totalAmount ?? 0;
879
943
  const discountedPrice = item.overrideAmount ?? originalPrice;
880
944
  itemsList.push({
881
- name: item.itemName || item.providerItemName || item.code || "Item",
945
+ name: item.name || item.code || "Item",
882
946
  quantity: item.quantity,
883
947
  price: discountedPrice,
884
948
  originalPrice
@@ -893,8 +957,13 @@ function buildCheckoutDisplayData(session, options = {}) {
893
957
  originalPrice: session.amount / 100
894
958
  });
895
959
  }
896
- const originalTotal = itemsList.reduce((sum, i) => sum + i.originalPrice * i.quantity, 0);
897
- const total = itemsList.reduce((sum, i) => sum + i.price * i.quantity, 0);
960
+ const lineOriginalTotal = itemsList.reduce((sum, i) => sum + i.originalPrice * i.quantity, 0);
961
+ const lineTotal = itemsList.reduce((sum, i) => sum + i.price * i.quantity, 0);
962
+ const hasBackendTotals = typeof session.totalAmount === "number" && Number.isFinite(session.totalAmount);
963
+ const hasBackendSubtotal = typeof session.subtotalAmount === "number" && Number.isFinite(session.subtotalAmount);
964
+ const hasBackendDiscount = typeof session.discountAmount === "number" && Number.isFinite(session.discountAmount);
965
+ const total = hasBackendTotals ? session.totalAmount : lineTotal;
966
+ const originalTotal = hasBackendSubtotal ? session.subtotalAmount : lineOriginalTotal;
898
967
  const totalSave = Math.max(0, originalTotal - total);
899
968
  const discountPercent = originalTotal > 0 ? Math.round(totalSave / originalTotal * 100) : 0;
900
969
  return {
@@ -903,7 +972,8 @@ function buildCheckoutDisplayData(session, options = {}) {
903
972
  total,
904
973
  originalTotal,
905
974
  totalSave,
906
- discountPercent
975
+ discountPercent,
976
+ ...hasBackendDiscount ? { couponDiscount: session.discountAmount } : {}
907
977
  };
908
978
  }
909
979
 
@@ -944,8 +1014,10 @@ function isValidSecretKey(key) {
944
1014
  authenticationError,
945
1015
  buildCheckoutDisplayData,
946
1016
  buildItemPayload,
1017
+ buildProductPayload,
947
1018
  buildSubscriptionPayload,
948
1019
  configureFlopay,
1020
+ foldIntoProducts,
949
1021
  getConfiguredBillingApiUrl,
950
1022
  getCountryByCode,
951
1023
  getCurrencyByCountry,