@flopay/shared 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 CHANGED
@@ -79,9 +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?`, `gateway?`, `gatewayData?` (includes `publishableKey?` + `paypalPublishableKey?` — null disables PayPal), `accountData?`, `tagsData?` |
83
- | `CheckoutSessionItem` | Session item from billing API: `uuid`, `providerItemId`, `providerItemName`, `quantity`, `totalAmount`, `overrideAmount`, `currency` |
84
- | `CheckoutSessionSubscription` | Session subscription from billing API: `uuid`, `providerPlanId`, `providerPlanName`, `quantity`, `totalAmount`, `overrideAmount`, `currency` |
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?` |
86
+ | `CheckoutGateway` | Per-gateway config: `publishableKey?`, `environment?` (`'sandbox'`/`'live'`), `stripeClientSecret?` (Stripe-only) |
87
+ | `CheckoutGateways` | Map keyed by gateway code (e.g. `stripe`, `paypal`). A session can advertise multiple concurrent gateways. |
88
+ | `GatewayEnvironment` | `'sandbox'` / `'live'` — drives the PayPal JS SDK script and any other environment-scoped behavior |
85
89
  | `PaymentResult` | Payment outcome: `status` (`'succeeded'`/`'processing'`/`'requires_action'`/`'failed'`), `paymentIntentId?`, `error?` |
86
90
  | `ConfirmPaymentParams` | `clientSecret`, `returnUrl?` |
87
91
  | `CreatePaymentMethodResult` | `paymentMethodId`, `error?` |
@@ -100,7 +104,7 @@ throw validationError('Email is required', 'email');
100
104
  | `TokenizedBody` | `id?`, `type?`, `threeDSecureActionResultTokenId?`, `originalPaymentMethodId?`, `isPaypal?` |
101
105
  | `NormalizedCheckoutSession` | Provider-agnostic session: `provider`, `mode`, `data`, `raw?` |
102
106
  | `CheckoutModeKind` | `'tokenize'` / `'redirect'` |
103
- | `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?` |
104
108
  | `CheckoutSessionResult` | `{ status: 201; redirectUrl }` / `{ status: 204 }` / `{ status: number }` |
105
109
  | `CheckoutItem` | `providerItemId`, `providerItemName?`, `quantity?`, `totalAmount`, `overrideAmount?`, `currency?` |
106
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,
@@ -60,6 +62,7 @@ __export(index_exports, {
60
62
  isValidPublishableKey: () => isValidPublishableKey,
61
63
  isValidSecretKey: () => isValidSecretKey,
62
64
  networkError: () => networkError,
65
+ normalizeGatewayEnvironment: () => normalizeGatewayEnvironment,
63
66
  rateLimitError: () => rateLimitError,
64
67
  resolveAVSConfig: () => resolveAVSConfig,
65
68
  resolveBillingApiUrl: () => resolveBillingApiUrl,
@@ -69,6 +72,24 @@ __export(index_exports, {
69
72
  });
70
73
  module.exports = __toCommonJS(index_exports);
71
74
 
75
+ // src/types.ts
76
+ function normalizeGatewayEnvironment(env) {
77
+ if (env == null) return void 0;
78
+ switch (env) {
79
+ case "sandbox":
80
+ case "stage":
81
+ return "sandbox";
82
+ case "live":
83
+ case "production":
84
+ return "live";
85
+ default:
86
+ console.warn(
87
+ `[FloPay] Unrecognized gateway environment "${env}". Expected one of: sandbox, live, stage, production. Falling back to undefined.`
88
+ );
89
+ return void 0;
90
+ }
91
+ }
92
+
72
93
  // src/errors.ts
73
94
  var FloPayError = class extends Error {
74
95
  constructor(message, type, options) {
@@ -116,7 +137,7 @@ function getFloPayEnvironment() {
116
137
  }
117
138
 
118
139
  // src/constants.ts
119
- var SDK_VERSION = "1.0.3";
140
+ var SDK_VERSION = "1.1.3";
120
141
  var BILLING_API_URL_STAGING = "https://api.stage.flopay.com";
121
142
  var BILLING_API_URL_PRODUCTION = "https://api.flopay.com";
122
143
  var DEFAULT_API_BASE_URL = BILLING_API_URL_STAGING;
@@ -775,7 +796,7 @@ function nonBlank(value) {
775
796
  const trimmed = value.trim();
776
797
  return trimmed.length > 0 ? trimmed : void 0;
777
798
  }
778
- function resolveSessionCurrency(sessionCurrency, items, subscriptions) {
799
+ function resolveSessionCurrency(sessionCurrency, items, subscriptions, products) {
779
800
  const session = nonBlank(sessionCurrency);
780
801
  if (session) return session;
781
802
  for (const item of items ?? []) {
@@ -786,7 +807,67 @@ function resolveSessionCurrency(sessionCurrency, items, subscriptions) {
786
807
  const c = nonBlank(sub.currency);
787
808
  if (c) return c;
788
809
  }
789
- 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;
790
871
  }
791
872
  function buildItemPayload(item, sessionCurrency) {
792
873
  const code = nonBlank(item.code) ?? nonBlank(item.providerItemId);
@@ -836,13 +917,15 @@ function buildSubscriptionPayload(subscription, sessionCurrency) {
836
917
  // src/display.ts
837
918
  function buildCheckoutDisplayData(session, options = {}) {
838
919
  const itemsList = [];
839
- const subscriptions = session.subscriptions ?? [];
840
- const items = session.items ?? [];
841
- 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();
842
925
  for (const sub of subscriptions) {
843
926
  const originalPrice = sub.totalAmount ?? 0;
844
927
  const discountedPrice = sub.overrideAmount ?? originalPrice;
845
- let name = sub.subscriptionName || sub.providerPlanName || sub.code || "Subscription";
928
+ let name = sub.name || sub.code || "Subscription";
846
929
  if (name === "4-WEEK PLAN" && discountedPrice <= 1) {
847
930
  name = "7-DAY TRIAL: FULL ACCESS";
848
931
  }
@@ -859,7 +942,7 @@ function buildCheckoutDisplayData(session, options = {}) {
859
942
  const originalPrice = item.totalAmount ?? 0;
860
943
  const discountedPrice = item.overrideAmount ?? originalPrice;
861
944
  itemsList.push({
862
- name: item.itemName || item.providerItemName || item.code || "Item",
945
+ name: item.name || item.code || "Item",
863
946
  quantity: item.quantity,
864
947
  price: discountedPrice,
865
948
  originalPrice
@@ -874,8 +957,13 @@ function buildCheckoutDisplayData(session, options = {}) {
874
957
  originalPrice: session.amount / 100
875
958
  });
876
959
  }
877
- const originalTotal = itemsList.reduce((sum, i) => sum + i.originalPrice * i.quantity, 0);
878
- 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;
879
967
  const totalSave = Math.max(0, originalTotal - total);
880
968
  const discountPercent = originalTotal > 0 ? Math.round(totalSave / originalTotal * 100) : 0;
881
969
  return {
@@ -884,7 +972,8 @@ function buildCheckoutDisplayData(session, options = {}) {
884
972
  total,
885
973
  originalTotal,
886
974
  totalSave,
887
- discountPercent
975
+ discountPercent,
976
+ ...hasBackendDiscount ? { couponDiscount: session.discountAmount } : {}
888
977
  };
889
978
  }
890
979
 
@@ -925,8 +1014,10 @@ function isValidSecretKey(key) {
925
1014
  authenticationError,
926
1015
  buildCheckoutDisplayData,
927
1016
  buildItemPayload,
1017
+ buildProductPayload,
928
1018
  buildSubscriptionPayload,
929
1019
  configureFlopay,
1020
+ foldIntoProducts,
930
1021
  getConfiguredBillingApiUrl,
931
1022
  getCountryByCode,
932
1023
  getCurrencyByCountry,
@@ -940,6 +1031,7 @@ function isValidSecretKey(key) {
940
1031
  isValidPublishableKey,
941
1032
  isValidSecretKey,
942
1033
  networkError,
1034
+ normalizeGatewayEnvironment,
943
1035
  rateLimitError,
944
1036
  resolveAVSConfig,
945
1037
  resolveBillingApiUrl,