AVS (Address Verification)
Configurable Address Verification with per-field, per-country rules. Capture country, ZIP, street address, city, and state to improve auth rates.
AVS (Address Verification)
AVS validates the buyer's billing address against the address on file with their card issuer. When enabled, the SDK collects address fields, forwards them to Stripe as billing_details, and Stripe runs AVS checks automatically. Stricter address checks improve authorization rates and reduce rebill failures.
enableAVS accepts either a boolean (the legacy default) or an AVSFieldConfig object that controls which fields are shown — globally or per country.
Quick Start
The simplest form — country dropdown + postal code, shown for every buyer:
Configurable AVS
Pass an object to control each field independently. Each field accepts:
true— always visiblefalseor omitted — hiddenstring[]— visible only when the buyer's country matches one of these ISO 3166-1 alpha-2 codes
Country codes are normalized (case- and whitespace-insensitive), so ['us', ' ca '] behaves the same as ['US', 'CA'].
Available fields
| Key | Field |
|---|---|
country | Country dropdown (ISO 3166-1 alpha-2) |
postal_code | ZIP / postal code |
address_line_1 | Street address |
address_line_2 | Apt, suite, unit (optional even when visible) |
city | City / town |
state | State / province / region — dropdown for US (50 states) and CA (13 provinces), free text otherwise |
All visible fields are required at submit time, except address_line_2, which is always optional.
Backwards Compatibility
The boolean form keeps working exactly as before:
| Prop value | Behavior |
|---|---|
enableAVS={false} (or omitted) | No AVS fields |
enableAVS={true} | Country + postal code (the legacy default) |
enableAVS={{ … }} | Custom per-field config |
Visible-Only Data Flow
Hidden fields are never sent to Stripe or to your backend, even if React state was populated earlier (for example, the buyer typed a city, then changed the country to one where city is hidden). Each send site — billing_details, the /process request, and 3DS retries — gates every field through isAVSFieldVisible(config.field, currentCountry).
This means:
- The data you see in Stripe matches the fields the buyer actually saw.
- Country-scoped configs cannot leak data from one country into another.
- Switching country at the form mid-edit clears the now-hidden fields from outgoing payloads automatically.
Where the Address Goes
| Destination | Fields |
|---|---|
Stripe PaymentMethod.billing_details.address | line1, line2, city, state, country, postal_code |
Stripe Customer.address | same six |
Stripe PaymentIntent.shipping.address | same six |
Backend /process request accountData | addressLine1, addressLine2, city, state, country, zip |
Database checkout_session row | user_address_line_1, user_address_line_2, user_address_city, user_address_state, user_address_country, user_address_zip |
3DS Retries Preserve All Fields
When Stripe demands a 3DS challenge after tokenization, the SDK includes the full billing_details object — country, postal code, line1, line2, city, state — on the retry confirmation. The PaymentMethod minted during authentication therefore carries the same address the buyer entered before the challenge.
Pre-fill from GEO/IP
Pass any address fields the partner already knows about the buyer (typically resolved via a GEO/IP lookup or pulled from the partner's user profile) in the session's account object, and the SDK pre-fills the matching AVS inputs:
FloPayCheckout automatically forwards session.customer.{country, city, state} from the create-session response into the matching AVS inputs.
Not auto-forwarded (the buyer always types these themselves):
zip/postal_codeaddressLine1/addressLine2
SplitCardForm accepts zip, addressLine1, addressLine2 props for direct consumers, but FloPayCheckout does not pre-populate them. This matches an earlier product decision to avoid steering buyers toward an address Stripe AVS may then reject.
If no country is provided, the country dropdown defaults to US. The buyer can edit any prefilled value before submitting.
Note: Pre-filling does not bypass AVS validation. Stripe still runs the address check against the issuing bank — the prefill just saves the buyer typing.
State Derivation from Postal Code (US / CA)
When the form collects address_line_1 and postal_code but hides the state input, the SDK derives a state / province code from the buyer's postal code and sends it to Stripe billing_details.address.state anyway. This gives Stripe Radar a richer AVS signal without adding another required input.
Trigger conditions (all must hold):
address_line_1is visiblepostal_codeis visiblestateis not visible- Country is
USorCA
When triggered, the SDK calls getStateFromPostalCode(country, postalCode), attaches the result to billing_details.address.state (and to the /process accountData), and the backend persists it onto the session row via an atomic UPDATE … WHERE user_address_state IS NULL. Values the partner already provided at session creation are never overwritten.
Resolution rules:
- US: 5-digit ZIP → 2-letter USPS state code via the 3-digit Sectional Center Facility prefix table.
- CA: A1A 1A1 → 2-letter ISO 3166-2:CA province code via Forward Sortation Area first letter.
Xresolves toNT(shared withNU). - Other countries: returns
null— caller skips state derivation.
City derivation from postal code is not currently supported — Canadian postal codes don't map to a single city, and the US ZIP→city dataset is too large to bundle. If that becomes a priority we'll evaluate a CDN-loaded dataset or a backend-side lookup.
Postal Code Labels
The label adapts automatically based on the selected country:
| Country | Label |
|---|---|
US | ZIP Code |
GB, AU, NZ | Postcode |
CA | Postal Code |
IE | Eircode |
| All others | Postal Code |
The label updates in real time when the buyer changes country.
State / Province Dropdowns
For US and CA buyers, the SDK renders a dropdown populated from US_STATES and CA_PROVINCES. For all other countries, the field falls back to a free-text input labelled by getStateLabel(country) ("State", "Province", "County", "State / Territory", or "State / Province / Region").
Field Layout
AVS fields can be arranged side-by-side (default) or stacked:
When the address fields are visible (address_line_1, address_line_2, city, state), they always render on their own rows above country/postal — buttons-layout still uses the row/column choice for the country and postal pair.
Theming
AVS fields inherit from the card input styles (cardInputBackground, cardInputColor, cardInputBorder). Each AVS field also has a dedicated style slot you can override:
The built-in dark theme preset already includes appropriate AVS field styles.
Testing & Selectors
E2E tests can target AVS fields via stable data-testid attributes:
| Field | data-testid |
|---|---|
| Country dropdown | flopay-country |
| Postal / ZIP | flopay-zip |
| Street address | flopay-address-line1 |
| Apt / suite | flopay-address-line2 |
| City | flopay-city |
| State / province | flopay-state |
Props Reference
FloPayCheckout / SplitCardForm
| Prop | Type | Default | Description |
|---|---|---|---|
enableAVS | boolean | AVSFieldConfig | false | Enable AVS. true shows country + postal; an object enables per-field, per-country rules |
avsLayout | 'row' | 'column' | 'row' | Layout for the country/postal pair |
country | string | 'US' | Pre-filled country code (ISO 3166-1 alpha-2) |
zip | string | — | Pre-filled postal code |
onCountryChange | (country: string) => void | — | Fires when country changes |
onZipChange | (zip: string) => void | — | Fires when postal code changes |
ButtonsLayoutStyles (AVS slots)
| Property | Description |
|---|---|
countrySelect | Country dropdown container |
zipInput | ZIP / postcode input container |
addressLine1Input | Street address input |
addressLine2Input | Apt / suite input |
cityInput | City input |
stateInput | State dropdown or text input |
Stripe Radar Configuration
After enabling AVS in the SDK, configure Stripe Radar to act on the results:
- Go to Stripe Dashboard → Radar → Rules
- Add rules based on the AVS check outcome:
Block if ::addressPostalCodeCheck:: = 'fail'— block when ZIP doesn't matchBlock if ::addressLine1Check:: = 'fail'— block when street address doesn't match (only meaningful whenaddress_line_1is collected)Review if ::addressPostalCodeCheck:: = 'unavailable'— review when the issuer doesn't support AVS
AVS coverage varies by issuer and country. Some banks return unavailable for legitimate cards. Blocking on unavailable may reject good payments from regions with limited AVS support.
Shared Helpers
Analytics
Every checkout records which AVS fields were actually shown (resolved against the buyer's country). See the Checkout Analytics guide for the full schema and how to populate analytics when sessions are created server-side.
Next Steps
- FloPayCheckout API Reference — full props table
- Theming — customize card field appearance
- 3D Secure — how 3DS authentication preserves AVS data
- Checkout Analytics — measuring AVS impact on auth and conversion