@flopay/react 1.3.0 → 1.3.2

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
@@ -464,6 +464,53 @@ The `SplitCardForm` layout:
464
464
  6. Full Name input
465
465
  7. Submit button
466
466
 
467
+ #### AVS postcode validation
468
+
469
+ When AVS collection is enabled (`enableAVS`) and the postcode field is shown,
470
+ `SplitCardForm` validates the postcode **format against the live selected
471
+ country before the card is captured**, on both the Stripe and vault card paths.
472
+ It reuses `@flopay/shared`'s country-aware
473
+ [postcode helpers](../shared/README.md#postal-code-helpers) (the same
474
+ `validator` rules the billing API applies), so client and server agree.
475
+
476
+ - **Supported country + malformed postcode** → the submit is blocked (the vault
477
+ widget's submit button is gated; the Stripe path returns before tokenizing)
478
+ and an inline, country-specific message shows the expected format
479
+ (e.g. *"Enter a valid ZIP Code (e.g. 12345 or 12345-6789)"*). On the vault
480
+ path the inline message appears once the field is blurred, since the disabled
481
+ submit means it can't be reached by a submit attempt.
482
+ - **Supported country + empty postcode** → the same submit gate applies, and
483
+ once the field is blurred (or a submit is attempted) an inline *required*
484
+ message shows (e.g. *"ZIP Code is required"*) rather than the expected-format
485
+ copy.
486
+ - **Country with no postal system** (or a locale `validator` doesn't recognise)
487
+ → the postcode field is shown but **optional**: empty and format checks are
488
+ both skipped, so these buyers are never blocked.
489
+
490
+ On the **vault card path** the widget's submit button is gated **synchronously**
491
+ on the client-side AVS check above. Because the client postcode rules mirror the
492
+ backend validator (`validator@13.15.35`) verbatim, a postcode the server's
493
+ `PATCH /v1/checkouts/sessions/{id}/account` would reject is already blocked here —
494
+ before the button ever enables — with no network round-trip in the critical path.
495
+ The gate is **never** held pending an async request: the hosted widget silently
496
+ swallows a click that lands while the button is disabled and offers no way to
497
+ auto-resubmit on release, so an async gate would strand any buyer (or single-click
498
+ preview) who clicked before it resolved — the checkout would write no attempt and
499
+ time out (sdk#124).
500
+
501
+ When the buyer submits, `SplitCardForm` persists the billing snapshot
502
+ best-effort **in parallel** with the widget's charge:
503
+
504
+ - **Backend rejects the address (a `4xx`)** — a residual, non-postcode drift, since
505
+ the postcode is already client-validated — the specific message (e.g. *"Postal/ZIP
506
+ code is not valid for the selected country…"*) shows inline **and** fires `onError`
507
+ with the parsed `FloPayError`, and the processing overlay clears so the buyer can
508
+ correct and retry. The charge cannot be cancelled once submitted, so this surfaces
509
+ for the buyer's *next* attempt rather than stopping the in-flight one.
510
+ - **Transient failure (`5xx` / network)** → best-effort and silent: the backend
511
+ listener falls back to the session's baseline address while the widget completes
512
+ the charge.
513
+
467
514
  ### Vault PCI card form
468
515
 
469
516
  When the billing API returns a hosted vault card form on the session
package/dist/index.cjs CHANGED
@@ -1365,6 +1365,9 @@ var import_jsx_runtime7 = require("react/jsx-runtime");
1365
1365
  var STRIPE_RESUME_KEY = "flopay_stripe_resume";
1366
1366
  var LEGACY_WALLET_RESUME_KEY = "flopay_wallet_resume";
1367
1367
  var PAYPAL_RESUME_KEY = "flopay_paypal_resume";
1368
+ function isAccountValidationError(err) {
1369
+ return err instanceof import_shared6.FloPayError && typeof err.statusCode === "number" && err.statusCode >= 400 && err.statusCode < 500;
1370
+ }
1368
1371
  function toVaultMount(block) {
1369
1372
  if (!block?.html) return null;
1370
1373
  return {
@@ -1447,6 +1450,18 @@ function getButtonMethodLabel(method) {
1447
1450
  return "Card";
1448
1451
  }
1449
1452
  }
1453
+ function malformedPostcodeMessage(country) {
1454
+ const example = (0, import_shared5.getPostalCodeExample)(country);
1455
+ return `Enter a valid ${(0, import_shared5.getPostalCodeLabel)(country)}${example ? ` (e.g. ${example})` : ""}`;
1456
+ }
1457
+ function computePostalCodeState(country, zip, visible) {
1458
+ const supported = (0, import_shared5.isPostalCodeSupported)(country);
1459
+ const trimmed = zip.trim();
1460
+ const required = visible && supported;
1461
+ const empty = required && !trimmed;
1462
+ const malformed = required && !!trimmed && !(0, import_shared5.isValidPostalCode)(country, trimmed);
1463
+ return { visible, supported, required, empty, malformed };
1464
+ }
1450
1465
  function normalizeBeforeButtonClickError(method, err) {
1451
1466
  return err instanceof import_shared6.FloPayError ? err : new import_shared6.FloPayError(
1452
1467
  err instanceof Error ? err.message : `${getButtonMethodLabel(method)} before-click hook failed.`,
@@ -2679,6 +2694,11 @@ function SplitCardFormInner({
2679
2694
  if (!flopay || !vaultActive || !sessionId) return null;
2680
2695
  return flopay.cardCapture({ sessionId });
2681
2696
  }, [flopay, vaultActive, sessionId]);
2697
+ const postalCodeState = (0, import_react9.useMemo)(() => {
2698
+ const cc = selectedCountry;
2699
+ const visible = avsConfig ? (0, import_shared5.isAVSFieldVisible)(avsConfig.postal_code, cc) : false;
2700
+ return computePostalCodeState(cc, zipCode, visible);
2701
+ }, [avsConfig, selectedCountry, zipCode]);
2682
2702
  const { avsInvalid, invalidAvsFields } = (0, import_react9.useMemo)(() => {
2683
2703
  const none = { line1: false, city: false, state: false, zip: false };
2684
2704
  if (!vaultActive || !avsConfig) return { avsInvalid: false, invalidAvsFields: none };
@@ -2688,12 +2708,15 @@ function SplitCardFormInner({
2688
2708
  line1: isEmpty(avsConfig.address_line_1, addressLine1),
2689
2709
  city: isEmpty(avsConfig.city, city),
2690
2710
  state: isEmpty(avsConfig.state, stateValue),
2691
- zip: isEmpty(avsConfig.postal_code, zipCode)
2711
+ // Empty (required) or malformed both block; unsupported/no-postcode
2712
+ // locales never block (`postalCodeState` fails open above).
2713
+ zip: postalCodeState.empty || postalCodeState.malformed
2692
2714
  };
2693
2715
  const avsInvalid2 = invalidAvsFields2.line1 || invalidAvsFields2.city || invalidAvsFields2.state || invalidAvsFields2.zip;
2694
2716
  return { avsInvalid: avsInvalid2, invalidAvsFields: invalidAvsFields2 };
2695
- }, [vaultActive, avsConfig, selectedCountry, addressLine1, city, stateValue, zipCode]);
2717
+ }, [vaultActive, avsConfig, selectedCountry, addressLine1, city, stateValue, postalCodeState]);
2696
2718
  const [hasAttemptedSubmit, setHasAttemptedSubmit] = (0, import_react9.useState)(false);
2719
+ const [zipTouched, setZipTouched] = (0, import_react9.useState)(false);
2697
2720
  const [vaultMount, setVaultMount] = (0, import_react9.useState)(
2698
2721
  () => toVaultMount(session?.vault)
2699
2722
  );
@@ -2846,10 +2869,6 @@ function SplitCardFormInner({
2846
2869
  },
2847
2870
  [onErrorChange]
2848
2871
  );
2849
- (0, import_react9.useEffect)(() => {
2850
- if (!vaultActive || !cardCapture) return;
2851
- cardCapture.setSubmitGate?.(avsInvalid);
2852
- }, [vaultActive, cardCapture, avsInvalid]);
2853
2872
  (0, import_react9.useEffect)(() => {
2854
2873
  if (!vaultActive || !cardCapture) return;
2855
2874
  cardCapture.setCardFieldOrder?.(cardFieldOrder ?? null, avsConfig == null);
@@ -2903,73 +2922,118 @@ function SplitCardFormInner({
2903
2922
  nonce,
2904
2923
  updateError
2905
2924
  ]);
2925
+ const vaultOutcomeRef = (0, import_react9.useRef)({
2926
+ onComplete,
2927
+ onError,
2928
+ updateError,
2929
+ emitDecline,
2930
+ resolvedAccount,
2931
+ fullName,
2932
+ avsConfig,
2933
+ avsCheckProp,
2934
+ sessionId,
2935
+ nonce,
2936
+ baseUrl
2937
+ });
2938
+ vaultOutcomeRef.current = {
2939
+ onComplete,
2940
+ onError,
2941
+ updateError,
2942
+ emitDecline,
2943
+ resolvedAccount,
2944
+ fullName,
2945
+ avsConfig,
2946
+ avsCheckProp,
2947
+ sessionId,
2948
+ nonce,
2949
+ baseUrl
2950
+ };
2951
+ const vaultCompletedRef = (0, import_react9.useRef)(false);
2952
+ const buildVaultAccountSnapshot = (0, import_react9.useCallback)(() => {
2953
+ const { resolvedAccount: resolvedAccount2, avsConfig: avsConfig2, fullName: fullName2, avsCheckProp: avsCheckProp2 } = vaultOutcomeRef.current;
2954
+ const cc = selectedCountryRef.current || resolvedAccount2.country || "US";
2955
+ const stateVisible = avsConfig2 ? (0, import_shared5.isAVSFieldVisible)(avsConfig2.state, cc) : false;
2956
+ const line1Visible = avsConfig2 ? (0, import_shared5.isAVSFieldVisible)(avsConfig2.address_line_1, cc) : false;
2957
+ const zipVisible = avsConfig2 ? (0, import_shared5.isAVSFieldVisible)(avsConfig2.postal_code, cc) : false;
2958
+ const cityVisible = avsConfig2 ? (0, import_shared5.isAVSFieldVisible)(avsConfig2.city, cc) : false;
2959
+ const line2Visible = avsConfig2 ? (0, import_shared5.isAVSFieldVisible)(avsConfig2.address_line_2, cc) : false;
2960
+ const derivedState = line1Visible && !stateVisible && zipVisible ? (0, import_shared5.getStateFromPostalCode)(cc, (zipCodeRef.current ?? "").trim()) : null;
2961
+ const stateValue2 = stateVisible ? stateRef.current : derivedState;
2962
+ return {
2963
+ accountData: {
2964
+ userId: resolvedAccount2.userId ?? "",
2965
+ email: resolvedAccount2.email ?? "",
2966
+ firstName: resolvedAccount2.firstName ?? fullName2.trim().split(/\s+/)[0] ?? "",
2967
+ lastName: resolvedAccount2.lastName ?? fullName2.trim().split(/\s+/).slice(1).join(" ") ?? "",
2968
+ ...zipVisible && zipCodeRef.current ? { zip: zipCodeRef.current } : {},
2969
+ ...cityVisible && cityRef.current ? { city: cityRef.current } : {},
2970
+ ...stateValue2 ? { state: stateValue2 } : {},
2971
+ ...line1Visible && addressLine1Ref.current ? { addressLine1: addressLine1Ref.current } : {},
2972
+ ...line2Visible && addressLine2Ref.current ? { addressLine2: addressLine2Ref.current } : {},
2973
+ country: cc
2974
+ },
2975
+ ...avsCheckProp2 !== void 0 ? { avsCheck: avsCheckProp2 } : {},
2976
+ ...avsConfig2 ? {
2977
+ avsConfig: {
2978
+ country: (0, import_shared5.isAVSFieldVisible)(avsConfig2.country, cc),
2979
+ postal_code: zipVisible,
2980
+ address_line_1: line1Visible,
2981
+ address_line_2: line2Visible,
2982
+ city: cityVisible,
2983
+ state: stateVisible
2984
+ }
2985
+ } : {}
2986
+ };
2987
+ }, []);
2906
2988
  (0, import_react9.useEffect)(() => {
2907
2989
  if (!vaultActive || !cardCapture) return;
2990
+ cardCapture.setSubmitGate?.(avsInvalid);
2991
+ }, [vaultActive, cardCapture, avsInvalid]);
2992
+ (0, import_react9.useEffect)(() => {
2993
+ if (!vaultActive || !cardCapture) return;
2994
+ vaultCompletedRef.current = false;
2908
2995
  let cancelled = false;
2909
2996
  const offSubmitting = cardCapture.on("submitting", () => {
2910
2997
  setHasAttemptedSubmit(true);
2911
2998
  setOverlayStatus("processing");
2912
- if (!sessionId || !nonce) return;
2913
- const cc = selectedCountryRef.current || resolvedAccount.country || "US";
2914
- const stateVisible = avsConfig ? (0, import_shared5.isAVSFieldVisible)(avsConfig.state, cc) : false;
2915
- const line1Visible = avsConfig ? (0, import_shared5.isAVSFieldVisible)(avsConfig.address_line_1, cc) : false;
2916
- const zipVisible = avsConfig ? (0, import_shared5.isAVSFieldVisible)(avsConfig.postal_code, cc) : false;
2917
- const cityVisible = avsConfig ? (0, import_shared5.isAVSFieldVisible)(avsConfig.city, cc) : false;
2918
- const line2Visible = avsConfig ? (0, import_shared5.isAVSFieldVisible)(avsConfig.address_line_2, cc) : false;
2919
- const derivedState = line1Visible && !stateVisible && zipVisible ? (0, import_shared5.getStateFromPostalCode)(cc, zipCodeRef.current ?? "") : null;
2920
- const stateValue2 = stateVisible ? stateRef.current : derivedState;
2921
- void new import_js2.PaymentAPI(baseUrl).patchAccountSnapshot(sessionId, nonce, {
2922
- accountData: {
2923
- userId: resolvedAccount.userId ?? "",
2924
- email: resolvedAccount.email ?? "",
2925
- firstName: resolvedAccount.firstName ?? fullName.trim().split(/\s+/)[0] ?? "",
2926
- lastName: resolvedAccount.lastName ?? fullName.trim().split(/\s+/).slice(1).join(" ") ?? "",
2927
- ...zipVisible && zipCodeRef.current ? { zip: zipCodeRef.current } : {},
2928
- ...cityVisible && cityRef.current ? { city: cityRef.current } : {},
2929
- ...stateValue2 ? { state: stateValue2 } : {},
2930
- ...line1Visible && addressLine1Ref.current ? { addressLine1: addressLine1Ref.current } : {},
2931
- ...line2Visible && addressLine2Ref.current ? { addressLine2: addressLine2Ref.current } : {},
2932
- country: cc
2933
- },
2934
- ...avsCheckProp !== void 0 ? { avsCheck: avsCheckProp } : {},
2935
- ...avsConfig ? {
2936
- avsConfig: {
2937
- country: (0, import_shared5.isAVSFieldVisible)(avsConfig.country, cc),
2938
- postal_code: zipVisible,
2939
- address_line_1: line1Visible,
2940
- address_line_2: line2Visible,
2941
- city: cityVisible,
2942
- state: stateVisible
2943
- }
2944
- } : {}
2945
- }).catch(() => {
2999
+ const { baseUrl: baseUrl2, sessionId: sessionId2, nonce: nonce2, updateError: updateError2, onError: onError2 } = vaultOutcomeRef.current;
3000
+ if (!sessionId2 || !nonce2) return;
3001
+ void new import_js2.PaymentAPI(baseUrl2).patchAccountSnapshot(sessionId2, nonce2, buildVaultAccountSnapshot()).catch((err) => {
3002
+ if (!isAccountValidationError(err)) return;
3003
+ const message = err instanceof Error ? err.message : "Please check your billing address and try again.";
3004
+ updateError2(message);
3005
+ setOverlayStatus(null);
3006
+ onError2?.(err instanceof import_shared6.FloPayError ? err : new import_shared6.FloPayError(message, "api_error"));
2946
3007
  });
2947
3008
  });
2948
3009
  const offComplete = cardCapture.on("complete", async (event) => {
2949
- markSessionRecentlyCompleted(event.sessionId ?? sessionId);
3010
+ markSessionRecentlyCompleted(event.sessionId ?? vaultOutcomeRef.current.sessionId);
2950
3011
  setOverlayStatus("success");
2951
3012
  await new Promise((r) => setTimeout(r, PROCESSING_OVERLAY_SUCCESS_DELAY_MS));
2952
- if (cancelled) return;
2953
- onComplete?.({
3013
+ if (vaultCompletedRef.current) return;
3014
+ vaultCompletedRef.current = true;
3015
+ vaultOutcomeRef.current.onComplete?.({
2954
3016
  status: "succeeded",
2955
3017
  paymentIntentId: event.intentId,
2956
3018
  checkoutMethod: "card"
2957
3019
  });
2958
3020
  });
2959
3021
  const offDecline = cardCapture.on("decline", async (event) => {
3022
+ const { updateError: updateError2, emitDecline: emitDecline2 } = vaultOutcomeRef.current;
2960
3023
  const message = event.message ?? "Your payment was declined. Please try another card or contact your bank.";
2961
- updateError(message);
3024
+ updateError2(message);
2962
3025
  setOverlayStatus("error");
2963
- emitDecline("card", message, event.declineReason ? { declineCode: event.declineReason } : void 0);
3026
+ emitDecline2("card", message, event.declineReason ? { declineCode: event.declineReason } : void 0);
2964
3027
  await new Promise((r) => setTimeout(r, PROCESSING_OVERLAY_ERROR_DELAY_MS));
2965
3028
  if (cancelled) return;
2966
3029
  setOverlayStatus(null);
2967
3030
  });
2968
3031
  const offError = cardCapture.on("error", async (event) => {
3032
+ const { updateError: updateError2, onError: onError2 } = vaultOutcomeRef.current;
2969
3033
  const message = event.message ?? "There was a problem processing your payment. Please try again.";
2970
- updateError(message);
3034
+ updateError2(message);
2971
3035
  setOverlayStatus("error");
2972
- onError?.(new import_shared6.FloPayError(message, "api_error"));
3036
+ onError2?.(new import_shared6.FloPayError(message, "api_error"));
2973
3037
  await new Promise((r) => setTimeout(r, PROCESSING_OVERLAY_ERROR_DELAY_MS));
2974
3038
  if (cancelled) return;
2975
3039
  setOverlayStatus(null);
@@ -2981,25 +3045,7 @@ function SplitCardFormInner({
2981
3045
  offDecline();
2982
3046
  offError();
2983
3047
  };
2984
- }, [
2985
- vaultActive,
2986
- cardCapture,
2987
- sessionId,
2988
- nonce,
2989
- baseUrl,
2990
- avsConfig,
2991
- avsCheckProp,
2992
- resolvedAccount.userId,
2993
- resolvedAccount.email,
2994
- resolvedAccount.firstName,
2995
- resolvedAccount.lastName,
2996
- resolvedAccount.country,
2997
- fullName,
2998
- onComplete,
2999
- onError,
3000
- updateError,
3001
- emitDecline
3002
- ]);
3048
+ }, [vaultActive, cardCapture]);
3003
3049
  const directPaypalConfigured = !!directPaypal?.clientId;
3004
3050
  const hasEnabledMethodsPayload = Array.isArray(enabledPaymentMethods);
3005
3051
  const hasEnabledMethods = hasEnabledMethodsPayload && enabledPaymentMethods.length > 0;
@@ -3187,7 +3233,7 @@ function SplitCardFormInner({
3187
3233
  const stateVisible = (0, import_shared5.isAVSFieldVisible)(avsConfig.state, c);
3188
3234
  const line1Visible = (0, import_shared5.isAVSFieldVisible)(avsConfig.address_line_1, c);
3189
3235
  const zipVisible = (0, import_shared5.isAVSFieldVisible)(avsConfig.postal_code, c);
3190
- const derivedState = line1Visible && !stateVisible && zipVisible ? (0, import_shared5.getStateFromPostalCode)(c, zipCodeRef.current ?? "") : null;
3236
+ const derivedState = line1Visible && !stateVisible && zipVisible ? (0, import_shared5.getStateFromPostalCode)(c, (zipCodeRef.current ?? "").trim()) : null;
3191
3237
  const stateValue2 = stateVisible ? stateRef.current : derivedState;
3192
3238
  return {
3193
3239
  country: c,
@@ -3501,10 +3547,21 @@ function SplitCardFormInner({
3501
3547
  try {
3502
3548
  if (avsConfig) {
3503
3549
  const country = selectedCountryRef.current;
3504
- if ((0, import_shared5.isAVSFieldVisible)(avsConfig.postal_code, country) && !zipCodeRef.current.trim()) {
3550
+ const postal = computePostalCodeState(
3551
+ country,
3552
+ zipCodeRef.current,
3553
+ (0, import_shared5.isAVSFieldVisible)(avsConfig.postal_code, country)
3554
+ );
3555
+ if (postal.empty) {
3556
+ setZipTouched(true);
3505
3557
  updateError((0, import_shared5.getPostalCodeLabel)(country) + " is required");
3506
3558
  return;
3507
3559
  }
3560
+ if (postal.malformed) {
3561
+ setZipTouched(true);
3562
+ updateError(malformedPostcodeMessage(country));
3563
+ return;
3564
+ }
3508
3565
  if ((0, import_shared5.isAVSFieldVisible)(avsConfig.address_line_1, country) && !addressLine1Ref.current.trim()) {
3509
3566
  updateError("Street address is required");
3510
3567
  return;
@@ -3645,6 +3702,8 @@ function SplitCardFormInner({
3645
3702
  const resolvedBorder = bStyles.cardInputBorder ?? (isButtons ? "#e5e7eb" : "#A4A4FF");
3646
3703
  const resolvedDangerColor = appearanceVars?.colorDanger ?? "#dc2626";
3647
3704
  const avsBorderColor = (fieldInvalid) => hasAttemptedSubmit && fieldInvalid ? resolvedDangerColor : resolvedBorder;
3705
+ const showPostcodeError = (postalCodeState.malformed || postalCodeState.empty) && (zipTouched || hasAttemptedSubmit);
3706
+ const zipBorderColor = showPostcodeError ? resolvedDangerColor : avsBorderColor(invalidAvsFields.zip);
3648
3707
  const cardBg = bStyles.cardFormContainer?.backgroundColor ?? themedWrapperBg ?? (isButtons ? "white" : "#EDEDFF");
3649
3708
  const cardInputBg = bStyles.cardInputBackground ?? appearanceVars?.colorBackground ?? "white";
3650
3709
  const hideBackButtonLabel = isEmptySlotContent(cardBackButtonContent);
@@ -4074,7 +4133,7 @@ function SplitCardFormInner({
4074
4133
  (0, import_shared5.isAVSFieldVisible)(avsConfig.postal_code, cc) && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: {
4075
4134
  flex: avsLayoutProp === "row" ? 1 : void 0,
4076
4135
  backgroundColor: cardInputBg,
4077
- border: `1px solid ${avsBorderColor(invalidAvsFields.zip)}`,
4136
+ border: `1px solid ${zipBorderColor}`,
4078
4137
  padding: "10px",
4079
4138
  ...avsLayoutProp === "row" && (0, import_shared5.isAVSFieldVisible)(avsConfig.country, cc) ? { borderRadius: "0", borderTopRightRadius: resolvedBorderRadius, borderBottomRightRadius: resolvedBorderRadius } : { borderRadius: resolvedBorderRadius }
4080
4139
  }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
@@ -4091,13 +4150,31 @@ function SplitCardFormInner({
4091
4150
  setZipCode(e.target.value);
4092
4151
  onZipChange?.(e.target.value);
4093
4152
  },
4153
+ onBlur: () => setZipTouched(true),
4094
4154
  disabled: isSubmitting,
4095
- required: true,
4155
+ required: postalCodeState.required,
4156
+ "aria-invalid": showPostcodeError || void 0,
4157
+ "aria-describedby": showPostcodeError ? "flopay-billing-postal-code-error" : void 0,
4096
4158
  "data-testid": "flopay-zip",
4097
4159
  style: inputFieldStyle()
4098
4160
  }
4099
4161
  ) })
4100
- ] })
4162
+ ] }),
4163
+ showPostcodeError && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4164
+ "div",
4165
+ {
4166
+ id: "flopay-billing-postal-code-error",
4167
+ role: "alert",
4168
+ "data-testid": "flopay-zip-error",
4169
+ style: {
4170
+ marginTop: "0.375rem",
4171
+ color: resolvedDangerColor,
4172
+ ...sharedInputTypography,
4173
+ fontSize: "0.75rem"
4174
+ },
4175
+ children: postalCodeState.empty ? `${(0, import_shared5.getPostalCodeLabel)(cc)} is required` : malformedPostcodeMessage(cc)
4176
+ }
4177
+ )
4101
4178
  ] });
4102
4179
  })() }),
4103
4180
  vaultActive && cardPreFormSlot && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { order: -2, width: "100%" }, children: cardPreFormSlot }),