@flopay/shared 1.2.8 → 1.3.1

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
@@ -175,3 +175,28 @@ throw validationError('Email is required', 'email');
175
175
  | `getCurrencyByCountry(countryCode)` | Returns `CurrencyInfo` for a country code, falls back to USD |
176
176
  | `isValidPublishableKey(key)` | Returns `true` if the key matches `pk_(test|live)_...` |
177
177
  | `isValidSecretKey(key)` | Returns `true` if the key matches `sk_(test|live)_...` |
178
+
179
+ ### Postal Code Helpers
180
+
181
+ Country-aware postcode format validation, backed by the [`validator`](https://www.npmjs.com/package/validator) package's `isPostalCode` (the same library the billing API uses, so client and server rules agree). `@flopay/react`'s AVS block uses these to block a malformed postcode before the card is captured and to show the expected format inline.
182
+
183
+ | Export | Description |
184
+ |--------|-------------|
185
+ | `getPostalCodeLabel(countryCode)` | Country-appropriate field label (`'ZIP Code'`, `'Postcode'`, `'Eircode'`, …); defaults to `'Postal Code'` |
186
+ | `isPostalCodeSupported(country)` | `true` when `validator` has a postcode pattern for the (ISO 3166-1 alpha-2, case-insensitive; `UK`→`GB`) country. Unsupported / no-postcode countries return `false` |
187
+ | `isValidPostalCode(country, postalCode)` | `true` when the (trimmed) postcode matches the country's format. **Fails open** — unsupported / no-postcode countries always return `true`, so callers never block them. A supported country with an empty value returns `false`; check emptiness first to distinguish "required" from "malformed" |
188
+ | `getPostalCodeExample(country)` | A curated example postcode for the field hint (US `12345 or 12345-6789`, GB `SW1A 1AA`, CA `A1A 1A1`, …), or `undefined` when there is no curated example |
189
+ | `getStateFromPostalCode(country, postalCode)` | Derives a US state / CA province code from a postcode for AVS enrichment; `null` for other countries or a malformed code |
190
+
191
+ ```ts
192
+ import { isPostalCodeSupported, isValidPostalCode, getPostalCodeExample } from '@flopay/shared';
193
+
194
+ isPostalCodeSupported('US'); // true
195
+ isPostalCodeSupported('AE'); // false — UAE has no postcodes
196
+ isValidPostalCode('GB', 'SW1A 1AA'); // true
197
+ isValidPostalCode('GB', '12345'); // false — US shape in GB
198
+ isValidPostalCode('AE', ''); // true — fail open, never blocks
199
+ getPostalCodeExample('CA'); // 'A1A 1A1'
200
+ ```
201
+
202
+ > **Runtime dependency.** Unlike the rest of `@flopay/shared`, these helpers pull in `validator` (imported via the `validator/lib/isPostalCode.js` submodule so bundlers only include the one check). It installs transitively when you depend on `@flopay/shared`.
package/dist/index.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,7 +17,15 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+ var __toESM = (mod2, isNodeMode, target) => (target = mod2 != null ? __create(__getProtoOf(mod2)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod2 || !mod2.__esModule ? __defProp(target, "default", { value: mod2, enumerable: true }) : target,
26
+ mod2
27
+ ));
28
+ var __toCommonJS = (mod2) => __copyProps(__defProp({}, "__esModule", { value: true }), mod2);
19
29
 
20
30
  // src/index.ts
21
31
  var index_exports = {};
@@ -44,6 +54,7 @@ __export(index_exports, {
44
54
  DEFAULT_CURRENCY: () => DEFAULT_CURRENCY,
45
55
  ELEMENT_TYPES: () => ELEMENT_TYPES,
46
56
  FLAT_APPEARANCE: () => FLAT_APPEARANCE,
57
+ FLO_SDK_VERSION_HEADER: () => FLO_SDK_VERSION_HEADER,
47
58
  FloPayError: () => FloPayError,
48
59
  GLASS_DARK_APPEARANCE: () => GLASS_DARK_APPEARANCE,
49
60
  GLASS_LIGHT_APPEARANCE: () => GLASS_LIGHT_APPEARANCE,
@@ -74,6 +85,7 @@ __export(index_exports, {
74
85
  getCountryByCode: () => getCountryByCode,
75
86
  getCurrencyByCountry: () => getCurrencyByCountry,
76
87
  getFloPayEnvironment: () => getFloPayEnvironment,
88
+ getPostalCodeExample: () => getPostalCodeExample,
77
89
  getPostalCodeLabel: () => getPostalCodeLabel,
78
90
  getStateFromPostalCode: () => getStateFromPostalCode,
79
91
  getStateLabel: () => getStateLabel,
@@ -82,7 +94,9 @@ __export(index_exports, {
82
94
  hasVendoredStripeMethodLogo: () => hasVendoredStripeMethodLogo,
83
95
  isAVSEnabled: () => isAVSEnabled,
84
96
  isAVSFieldVisible: () => isAVSFieldVisible,
97
+ isPostalCodeSupported: () => isPostalCodeSupported,
85
98
  isSetupIntentClientSecret: () => isSetupIntentClientSecret,
99
+ isValidPostalCode: () => isValidPostalCode,
86
100
  isValidPublishableKey: () => isValidPublishableKey,
87
101
  isValidSecretKey: () => isValidSecretKey,
88
102
  needsStripeMethodExplicitConfirm: () => needsStripeMethodExplicitConfirm,
@@ -182,7 +196,8 @@ var PAYMENT_METHOD_LOGOS = {
182
196
  };
183
197
 
184
198
  // src/constants.ts
185
- var SDK_VERSION = "1.2.8";
199
+ var SDK_VERSION = "1.3.1";
200
+ var FLO_SDK_VERSION_HEADER = "x-flo-sdk-version";
186
201
  var BILLING_API_URL_STAGING = "https://api.stage.flopay.com";
187
202
  var BILLING_API_URL_PRODUCTION = "https://api.flopay.com";
188
203
  var DEFAULT_API_BASE_URL = BILLING_API_URL_STAGING;
@@ -864,6 +879,9 @@ function stripeExpressMethodToOptionKey(method) {
864
879
  function buildPlaceholderMark(glyph, discColor, glyphColor) {
865
880
  return `<svg viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg" role="img" aria-hidden="true"><circle cx="11" cy="11" r="11" fill="${discColor}"/><text x="11" y="15.5" text-anchor="middle" font-size="13" font-weight="700" font-family="-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif" fill="${glyphColor}">${glyph}</text></svg>`;
866
881
  }
882
+ function buildBankMark(discColor, glyphColor) {
883
+ return `<svg viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg" role="img" aria-hidden="true"><circle cx="11" cy="11" r="11" fill="${discColor}"/><g fill="${glyphColor}"><path d="M11 4 L17.5 8 L4.5 8 Z"/><rect x="5" y="8.4" width="12" height="1.2" rx="0.3"/><rect x="5.9" y="10" width="1.5" height="5"/><rect x="8.55" y="10" width="1.5" height="5"/><rect x="11.95" y="10" width="1.5" height="5"/><rect x="14.6" y="10" width="1.5" height="5"/><rect x="4.6" y="15.4" width="12.8" height="1.5" rx="0.3"/></g></svg>`;
884
+ }
867
885
  var SEPA_COUNTRIES = [
868
886
  "AT",
869
887
  "BE",
@@ -904,6 +922,7 @@ var SEPA_COUNTRIES = [
904
922
  "VA"
905
923
  // non-EEA SEPA participants
906
924
  ];
925
+ var BANK_TRANSFER_COUNTRIES = [...SEPA_COUNTRIES, "JP", "MX", "US"];
907
926
  var STRIPE_METHOD_MATRIX = {
908
927
  // ─── Express wallets ────────────────────────────────────────────────────
909
928
  apple_pay: {
@@ -936,14 +955,42 @@ var STRIPE_METHOD_MATRIX = {
936
955
  express: true,
937
956
  needsExplicitConfirm: false
938
957
  },
958
+ // ─── Klarna: PaymentElement tile, explicit confirm ─────────────────────
959
+ // Klarna renders as a PaymentElement tile (NOT express) so it always appears
960
+ // when the backend includes it in `enabledPaymentMethods`. Stripe's
961
+ // ExpressCheckoutElement only paints a Klarna button in eligible *real*
962
+ // browsers (never headless), which made it effectively invisible and looked
963
+ // like an SDK-level disable — as a tile it's purely backend-driven. Klarna
964
+ // may redirect during confirm, but it still surfaces a deliberate "Pay with
965
+ // Klarna" step: it omits `needsExplicitConfirm: false`, so the default
966
+ // `needsStripeMethodExplicitConfirm(...) === true` applies — unlike the
967
+ // auto-confirm APMs below.
939
968
  klarna: {
940
969
  displayName: "Klarna",
941
970
  currencies: ["usd", "eur", "gbp", "aud", "nzd", "cad", "chf", "sek", "nok", "dkk", "pln", "czk", "ron"],
942
971
  amounts: { usd: { min: 100 }, eur: { min: 100 }, gbp: { min: 100 } },
943
- express: true,
944
- needsExplicitConfirm: false
972
+ // Default (explicit "Pay with Klarna" button) — Klarna's PaymentElement can
973
+ // collect inline data and isn't a pure redirect-only method, so surface a
974
+ // deliberate confirm rather than auto-submitting on tile click.
975
+ theme: {
976
+ light: {
977
+ backgroundColor: "#FFB3C7",
978
+ textColor: "#0A0B09",
979
+ borderColor: "#FFB3C7",
980
+ logoSvg: buildPlaceholderMark("K", "#0A0B09", "#FFB3C7")
981
+ },
982
+ dark: {
983
+ backgroundColor: "#FFB3C7",
984
+ textColor: "#0A0B09",
985
+ borderColor: "#FFB3C7",
986
+ logoSvg: buildPlaceholderMark("K", "#0A0B09", "#FFB3C7")
987
+ }
988
+ }
945
989
  },
946
990
  // ─── PaymentElement APMs: auto-confirm (redirect-only, no Pay button) ───
991
+ // Affirm and Cash App Pay are pure redirect/handoff methods with no inline
992
+ // inputs, so they auto-submit on tile click (`needsExplicitConfirm: false`).
993
+ //
947
994
  // Affirm currently rejects on non-USD currencies via
948
995
  // /v1/elements/sessions, hence the narrow currency list. Below $35.00 USD
949
996
  // it returns `amount_too_small` and blanks out the whole accordion, hence
@@ -1008,11 +1055,28 @@ var STRIPE_METHOD_MATRIX = {
1008
1055
  gbp: { min: 100, max: 12e4 },
1009
1056
  aud: { min: 100, max: 2e5 },
1010
1057
  nzd: { min: 100, max: 2e5 }
1058
+ },
1059
+ // Afterpay's brand mint (#B2FCE4) with black wordmark/icon — the signature
1060
+ // palette from afterpay.com's brand system.
1061
+ theme: {
1062
+ light: {
1063
+ backgroundColor: "#B2FCE4",
1064
+ textColor: "#0A0B09",
1065
+ borderColor: "#B2FCE4",
1066
+ logoSvg: buildPlaceholderMark("A", "#0A0B09", "#B2FCE4")
1067
+ },
1068
+ dark: {
1069
+ backgroundColor: "#B2FCE4",
1070
+ textColor: "#0A0B09",
1071
+ borderColor: "#B2FCE4",
1072
+ logoSvg: buildPlaceholderMark("A", "#0A0B09", "#B2FCE4")
1073
+ }
1011
1074
  }
1012
1075
  },
1013
1076
  alipay: {
1014
1077
  displayName: "Alipay",
1015
- currencies: ["cny", "aud", "cad", "eur", "gbp", "hkd", "jpy", "myr", "nzd", "sgd", "usd"],
1078
+ // Presentment currencies only Alipay is offered to USD / CNY carts.
1079
+ currencies: ["usd", "cny"],
1016
1080
  theme: {
1017
1081
  light: {
1018
1082
  backgroundColor: "#1677FF",
@@ -1049,6 +1113,25 @@ var STRIPE_METHOD_MATRIX = {
1049
1113
  }
1050
1114
  }
1051
1115
  },
1116
+ bizum: {
1117
+ displayName: "Bizum",
1118
+ currencies: ["eur"],
1119
+ countries: ["ES"],
1120
+ theme: {
1121
+ light: {
1122
+ backgroundColor: "#00BFB3",
1123
+ textColor: "#FFFFFF",
1124
+ borderColor: "#00BFB3",
1125
+ logoSvg: buildPlaceholderMark("B", "#FFFFFF", "#00BFB3")
1126
+ },
1127
+ dark: {
1128
+ backgroundColor: "#00BFB3",
1129
+ textColor: "#FFFFFF",
1130
+ borderColor: "#00BFB3",
1131
+ logoSvg: buildPlaceholderMark("B", "#FFFFFF", "#00BFB3")
1132
+ }
1133
+ }
1134
+ },
1052
1135
  blik: {
1053
1136
  displayName: "BLIK",
1054
1137
  currencies: ["pln"],
@@ -1069,7 +1152,25 @@ var STRIPE_METHOD_MATRIX = {
1069
1152
  }
1070
1153
  },
1071
1154
  boleto: { displayName: "Boleto", currencies: ["brl"], countries: ["BR"] },
1072
- customer_balance: { displayName: "Customer Balance", currencies: ["*"] },
1155
+ customer_balance: {
1156
+ displayName: "Bank Transfer",
1157
+ currencies: ["eur", "usd"],
1158
+ countries: BANK_TRANSFER_COUNTRIES,
1159
+ theme: {
1160
+ light: {
1161
+ backgroundColor: "#1F2937",
1162
+ textColor: "#FFFFFF",
1163
+ borderColor: "#1F2937",
1164
+ logoSvg: buildBankMark("#1F2937", "#FFFFFF")
1165
+ },
1166
+ dark: {
1167
+ backgroundColor: "#374151",
1168
+ textColor: "#FFFFFF",
1169
+ borderColor: "#374151",
1170
+ logoSvg: buildBankMark("#374151", "#FFFFFF")
1171
+ }
1172
+ }
1173
+ },
1073
1174
  eps: {
1074
1175
  displayName: "EPS",
1075
1176
  currencies: ["eur"],
@@ -1128,6 +1229,25 @@ var STRIPE_METHOD_MATRIX = {
1128
1229
  }
1129
1230
  },
1130
1231
  konbini: { displayName: "Konbini", currencies: ["jpy"], countries: ["JP"] },
1232
+ kr_card: {
1233
+ displayName: "Korean Card",
1234
+ currencies: ["krw"],
1235
+ countries: ["KR"],
1236
+ theme: {
1237
+ light: {
1238
+ backgroundColor: "#0B3D91",
1239
+ textColor: "#FFFFFF",
1240
+ borderColor: "#0B3D91",
1241
+ logoSvg: buildPlaceholderMark("K", "#FFFFFF", "#0B3D91")
1242
+ },
1243
+ dark: {
1244
+ backgroundColor: "#0B3D91",
1245
+ textColor: "#FFFFFF",
1246
+ borderColor: "#0B3D91",
1247
+ logoSvg: buildPlaceholderMark("K", "#FFFFFF", "#0B3D91")
1248
+ }
1249
+ }
1250
+ },
1131
1251
  multibanco: { displayName: "Multibanco", currencies: ["eur"], countries: ["PT"] },
1132
1252
  oxxo: { displayName: "OXXO", currencies: ["mxn"], countries: ["MX"] },
1133
1253
  p24: {
@@ -1193,10 +1313,30 @@ var STRIPE_METHOD_MATRIX = {
1193
1313
  }
1194
1314
  }
1195
1315
  },
1316
+ upi: {
1317
+ displayName: "UPI",
1318
+ currencies: ["inr"],
1319
+ countries: ["IN"],
1320
+ theme: {
1321
+ light: {
1322
+ backgroundColor: "#FF7909",
1323
+ textColor: "#FFFFFF",
1324
+ borderColor: "#FF7909",
1325
+ logoSvg: buildPlaceholderMark("U", "#FFFFFF", "#FF7909")
1326
+ },
1327
+ dark: {
1328
+ backgroundColor: "#FF7909",
1329
+ textColor: "#FFFFFF",
1330
+ borderColor: "#FF7909",
1331
+ logoSvg: buildPlaceholderMark("U", "#FFFFFF", "#FF7909")
1332
+ }
1333
+ }
1334
+ },
1196
1335
  us_bank_account: { displayName: "US Bank Account", currencies: ["usd"], countries: ["US"] },
1197
1336
  wechat_pay: {
1198
1337
  displayName: "WeChat Pay",
1199
- currencies: ["cny", "usd", "eur", "gbp", "aud", "cad", "jpy", "chf", "hkd", "sgd", "nzd", "nok", "sek", "dkk"],
1338
+ // Presentment currencies only WeChat Pay is offered to USD / CNY carts.
1339
+ currencies: ["usd", "cny"],
1200
1340
  theme: {
1201
1341
  light: {
1202
1342
  backgroundColor: "#07C160",
@@ -1833,6 +1973,67 @@ function getStateFromPostalCode(country, postalCode) {
1833
1973
  }
1834
1974
  }
1835
1975
 
1976
+ // src/postal-code-validation.ts
1977
+ var isPostalCodeModule = __toESM(require("validator/lib/isPostalCode.js"), 1);
1978
+ var mod = isPostalCodeModule;
1979
+ var isPostalCode = typeof mod.default === "function" ? mod.default : mod.default?.default;
1980
+ if (typeof isPostalCode !== "function") {
1981
+ throw new Error(
1982
+ "validator/lib/isPostalCode.js interop failed: unexpected module shape. This likely means the bundler's CJS/ESM interop changed."
1983
+ );
1984
+ }
1985
+ var SUPPORTED_LOCALES = new Set(
1986
+ mod.locales.map((code) => code.toUpperCase())
1987
+ );
1988
+ var LOCALE_ALIASES = {
1989
+ UK: "GB"
1990
+ };
1991
+ function normalizeCountry(country) {
1992
+ const upper = country.trim().toUpperCase();
1993
+ return LOCALE_ALIASES[upper] ?? upper;
1994
+ }
1995
+ var POSTAL_CODE_EXAMPLES = {
1996
+ US: "12345 or 12345-6789",
1997
+ GB: "SW1A 1AA",
1998
+ CA: "A1A 1A1",
1999
+ AU: "2000",
2000
+ NZ: "6011",
2001
+ IE: "D02 AF30",
2002
+ DE: "10115",
2003
+ FR: "75008",
2004
+ NL: "1011 AB",
2005
+ ES: "28001",
2006
+ IT: "00100",
2007
+ IN: "110001",
2008
+ JP: "100-0001",
2009
+ BR: "01310-100",
2010
+ MX: "01000",
2011
+ SE: "114 55",
2012
+ CH: "8001",
2013
+ PL: "00-001",
2014
+ PT: "1000-001",
2015
+ SG: "570150",
2016
+ ZA: "0001",
2017
+ NO: "0001",
2018
+ DK: "1050",
2019
+ FI: "00100",
2020
+ AT: "1010",
2021
+ BE: "1000",
2022
+ CZ: "100 00"
2023
+ };
2024
+ function isPostalCodeSupported(country) {
2025
+ if (!country) return false;
2026
+ return SUPPORTED_LOCALES.has(normalizeCountry(country));
2027
+ }
2028
+ function isValidPostalCode(country, postalCode) {
2029
+ if (!isPostalCodeSupported(country)) return true;
2030
+ return isPostalCode(postalCode.trim(), normalizeCountry(country));
2031
+ }
2032
+ function getPostalCodeExample(country) {
2033
+ if (!country) return void 0;
2034
+ return POSTAL_CODE_EXAMPLES[normalizeCountry(country)];
2035
+ }
2036
+
1836
2037
  // src/checkout-payload.ts
1837
2038
  function nonBlank(value) {
1838
2039
  if (typeof value !== "string") return void 0;
@@ -2059,6 +2260,7 @@ function isSetupIntentClientSecret(clientSecret) {
2059
2260
  DEFAULT_CURRENCY,
2060
2261
  ELEMENT_TYPES,
2061
2262
  FLAT_APPEARANCE,
2263
+ FLO_SDK_VERSION_HEADER,
2062
2264
  FloPayError,
2063
2265
  GLASS_DARK_APPEARANCE,
2064
2266
  GLASS_LIGHT_APPEARANCE,
@@ -2089,6 +2291,7 @@ function isSetupIntentClientSecret(clientSecret) {
2089
2291
  getCountryByCode,
2090
2292
  getCurrencyByCountry,
2091
2293
  getFloPayEnvironment,
2294
+ getPostalCodeExample,
2092
2295
  getPostalCodeLabel,
2093
2296
  getStateFromPostalCode,
2094
2297
  getStateLabel,
@@ -2097,7 +2300,9 @@ function isSetupIntentClientSecret(clientSecret) {
2097
2300
  hasVendoredStripeMethodLogo,
2098
2301
  isAVSEnabled,
2099
2302
  isAVSFieldVisible,
2303
+ isPostalCodeSupported,
2100
2304
  isSetupIntentClientSecret,
2305
+ isValidPostalCode,
2101
2306
  isValidPublishableKey,
2102
2307
  isValidSecretKey,
2103
2308
  needsStripeMethodExplicitConfirm,