> ## Documentation Index
> Fetch the complete documentation index at: https://primer.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Display detected card network

> Show the card brand logo as users type their card number.

Display the detected card network (Visa, Mastercard, etc.) as users enter their card number.

## Recipe

> [tab] Web
>
> ```javascript theme={"dark"}
> checkout.addEventListener('primer:bin-data-available', (event) => {
>   const { preferred, status } = event.detail;
>
>   if (preferred) {
>     document.getElementById('card-logo').src = `/images/${preferred.network.toLowerCase()}.svg`;
>   }
> });
> ```

> [tab] Android
>
> ```kotlin theme={"dark"}
> val controller = rememberCardFormController(checkout)
> val state by controller.state.collectAsStateWithLifecycle()
>
> val detected = state.networkSelection.selectedNetwork
>
> if (detected != null) {
>     Text(text = detected.displayName)
> }
> ```
>
> > [!NOTE]
> >
> > `PrimerCardForm` displays the card brand icon automatically in the card number field. Custom rendering is optional -- use `state.networkSelection` only if you need to display the network outside the card form.

> [tab] iOS
>
> ```swift theme={"dark"}
> struct CardFormWithNetwork: View {
>   var body: some View {
>     PrimerCardForm(cardDetails: { session in
>       CardDetailsWithNetwork(session: session)
>     })
>   }
> }
>
> struct CardDetailsWithNetwork: View {
>   @ObservedObject var session: PrimerCardFormSession
>
>   var body: some View {
>     VStack(alignment: .leading) {
>       if let network = session.state.selectedNetwork {
>         Text(network.displayName)
>           .font(.caption)
>           .padding(4)
>           .background(Color(.secondarySystemBackground))
>           .cornerRadius(4)
>       }
>       CardFormDefaults.cardDetails(session)
>     }
>   }
> }
> ```
>
> > [!NOTE]
> >
> > `PrimerCardForm` displays the card brand icon automatically in the card number field. Read `session.state.selectedNetwork` only if you need to display the network outside the card form. It holds the network the form uses, and it is `nil` until a network is detected from the card number. For a co-badged card, it is the first entry of `availableNetworks` until the user picks another one.

## How it works

> [tab] Web
>
> 1. Listen for the `primer:bin-data-available` event
> 2. Use `preferred.network` to get the card brand identifier
> 3. Update your UI with the appropriate logo or icon

> [tab] Android
>
> 1. Create a `PrimerCardFormController` using `rememberCardFormController()`
> 2. Observe its `state` with `collectAsStateWithLifecycle()`
> 3. Read `state.networkSelection.selectedNetwork` for the currently detected network
> 4. The state updates reactively as the user types -- no event listener needed
>
> | Property | Type | Description |
> | - | - | - |
> | `selectedNetwork` | `PrimerCardNetwork?` | Currently detected network, or `null` |
> | `availableNetworks` | `List<PrimerCardNetwork>` | All selectable networks (for co-badged cards) |
> | `isNetworkSelectable` | `Boolean` | `true` when the card has multiple networks |

> [tab] iOS
>
> 1. Mark the `PrimerCardFormSession` injected into your slot closure as `@ObservedObject`
> 2. Read `session.state.selectedNetwork` for the network the form uses; it is `nil` until a network is detected from the card number
> 3. Read `session.state.availableNetworks` for the detected networks your `orderedAllowedCardNetworks` allows, in that order; more than one indicates a co-badged card
> 4. The state updates reactively as the user types -- no event listener needed
>
> | Property | Type | Description |
> | - | - | - |
> | `availableNetworks` | `[PrimerCardNetwork]` | Detected networks that your `orderedAllowedCardNetworks` allows, in that order, or the first detected network if none is allowed. More than one indicates a co-badged card. |
> | `selectedNetwork` | `PrimerCardNetwork?` | Network the form uses: the detected network, or the user's pick for a co-badged card. `nil` until a network is detected |

The `preferred` field contains the recommended card network based on `orderedAllowedCardNetworks`. It is `undefined` when no network is detected (e.g., empty input or insufficient digits).

## The preferred object

> [tab] Web
>
> The `preferred` object contains the detected card network and, when `status` is `'complete'`, additional issuer details:
>
> ```typescript theme={"dark"}
> {
>   displayName: string;  // Human-readable name (e.g., "Visa", "American Express")
>   network: string;      // Backend identifier (e.g., "VISA", "AMEX")
>   allowed: boolean;     // Whether the network is allowed per orderedAllowedCardNetworks
>   // Available when status is 'complete':
>   issuerCountryCode?: string;
>   issuerName?: string;
>   accountFundingType?: string;
> }
> ```
>
> > [!NOTE]
> >
> > See the [Events Reference](https://primer.io/docs/sdk/primer-checkout-web/events-reference.md#bindatadetails) for the full `BinDataDetails` type definition.

> [tab] Android
>
> ```kotlin theme={"dark"}
> data class PrimerCardNetwork(
>     val network: CardNetwork.Type,
>     val displayName: String,
>     val allowed: Boolean,
> )
> ```
>
> `selectedNetwork` is `null` when no card network is detected. Android uses the `CardNetwork.Type` enum with a `displayName` property.

> [tab] iOS
>
> ```swift theme={"dark"}
> @objc
> public final class PrimerCardNetwork: NSObject {
>     public let displayName: String                    // Human-readable name (e.g. "Visa")
>     public let network: CardNetwork                   // String-backed enum (.visa, .masterCard, ...)
>     public var allowed: Bool { get }                  // In the client session's orderedAllowedCardNetworks
>     public var allowsUserSelection: Bool { get }      // false for networks that auto-route (EFTPOS)
>
>     // Issuer details: set only on `binData` entries, `nil` elsewhere
>     public let issuerCountryCode: String?
>     public let issuerName: String?
>     public let accountFundingType: String?
>     public let prepaidReloadableIndicator: String?
>     public let productUsageType: String?
>     public let productCode: String?
>     public let productName: String?
>     public let issuerCurrencyCode: String?
>     public let regionalRestriction: String?
>     public let accountNumberType: String?
> }
> ```
>
> The network the form uses comes from `selectedNetwork`; it is `nil` until a network is detected from the card number. `availableNetworks` lists the detected networks that your `orderedAllowedCardNetworks` allows, in that order (more than one for a co-badged card). If none is allowed, it holds only the first detected network. It is empty when no card network is detected. To also see detected networks that are not allowed, read `binData.alternatives`. iOS exposes `PrimerCardNetwork` with a `displayName` string property and a `network` (`CardNetwork`) enum whose raw value matches the backend identifier (e.g. `"VISA"`).
>
> For issuer details, read `session.state.binData`. It is a `PrimerBinData?` that is `nil` until the first detection, with four properties:
>
> * `preferred: PrimerCardNetwork?` is the first allowed network in your `orderedAllowedCardNetworks` order. It is `nil` when no detected network is allowed or no card number is entered.
> * `alternatives: [PrimerCardNetwork]` lists the other detected networks in the same order.
> * `status: PrimerBinDataStatus` is `.partial` while the network is detected locally from the card number. This happens with fewer than 8 digits, or when the remote lookup fails. It is `.complete` after the remote BIN lookup.
> * `firstDigits: String?` is set for `.complete` results and is `nil` for `.partial` results.
>
> The `PrimerCardNetwork` entries in `preferred` and `alternatives` carry the issuer details as optional properties. These are `issuerCountryCode`, `issuerName`, `accountFundingType`, `prepaidReloadableIndicator`, `productUsageType`, `productCode`, `productName`, `issuerCurrencyCode`, `regionalRestriction` and `accountNumberType`. They are `nil` for `.partial` results and for any value the lookup does not return. The entries in `availableNetworks` and `selectedNetwork` never carry issuer details.
>
> ```swift theme={"dark"}
> if let bin = session.state.binData, bin.status == .complete {
>   Text(bin.preferred?.issuerName ?? "")
> }
> ```

## Supported card networks

| `network` | Web / Android `displayName` | iOS `displayName` |
| - | - | - |
| `VISA` | Visa | Visa |
| `MASTERCARD` | Mastercard | Mastercard |
| `AMEX` | American Express | American Express |
| `DISCOVER` | Discover | Discover |
| `DINERS_CLUB` | Diners Club | Diners |
| `JCB` | JCB | JCB |
| `MAESTRO` | Maestro | Maestro |
| `UNIONPAY` | UnionPay | UnionPay |
| `ELO` | Elo | Elo |
| `HIPER` | Hiper | Hiper |
| `HIPERCARD` | Hipercard | Hiper |
| `MIR` | Mir | Mir |
| `CARTES_BANCAIRES` | Cartes Bancaires | Cartes Bancaires |
| `BANCONTACT` | — | Bancontact |
| `DANKORT` | Dankort | — (not in iOS `CardNetwork`) |
| `EFTPOS` | Eftpos | EFTPOS |
| `OTHER` | Other | Unknown |

> [!NOTE]
>
> On Android, these are values of the `CardNetwork.Type` enum, each with a `displayName` property. On iOS, the `network` raw values map to cases of the `CardNetwork` enum; a few display names and the supported set differ (iOS has no `DANKORT` and adds `BANCONTACT`). See [PrimerCardNetwork](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/common/primer-card-network.md) for the authoritative iOS list.

## Variations

### Show a loading indicator

Use `primer:bin-data-loading-change` to indicate when BIN data is being fetched:

```javascript theme={"dark"}
checkout.addEventListener('primer:bin-data-loading-change', (event) => {
  const { loading } = event.detail;
  document.getElementById('card-logo').style.opacity = loading ? '0.5' : '1';
});
```

### Display card network name

> [tab] Web
>
> For user-facing text, use `displayName` instead of `network`:
>
> ```javascript theme={"dark"}
> checkout.addEventListener('primer:bin-data-available', (event) => {
>   const { preferred } = event.detail;
>
>   const networkName = preferred?.displayName || 'Card';
>   document.getElementById('card-type-label').textContent = networkName;
> });
> ```

> [tab] Android
>
> ```kotlin theme={"dark"}
> val controller = rememberCardFormController(checkout)
> val state by controller.state.collectAsStateWithLifecycle()
>
> val networkName = state.networkSelection.selectedNetwork?.displayName ?: "Card"
> Text(text = "Paying with $networkName")
> ```

> [tab] iOS
>
> ```swift theme={"dark"}
> let networkName = session.state.selectedNetwork?.displayName ?? "Card"
> Text("Paying with \(networkName)")
> ```

### Show/hide based on detection

> [tab] Web
>
> ```javascript theme={"dark"}
> const cardLogo = document.getElementById('card-logo');
>
> checkout.addEventListener('primer:bin-data-available', (event) => {
>   const { preferred } = event.detail;
>
>   if (preferred) {
>     cardLogo.src = `/images/${preferred.network.toLowerCase()}.svg`;
>     cardLogo.style.display = 'block';
>   } else {
>     cardLogo.style.display = 'none';
>   }
> });
> ```

> [tab] Android
>
> ```kotlin theme={"dark"}
> val controller = rememberCardFormController(checkout)
> val state by controller.state.collectAsStateWithLifecycle()
>
> val detected = state.networkSelection.selectedNetwork
>
> AnimatedVisibility(visible = detected != null) {
>     if (detected != null) {
>         Text(text = detected.displayName)
>     }
> }
> ```

> [tab] iOS
>
> ```swift theme={"dark"}
> @ObservedObject var session: PrimerCardFormSession
>
> var body: some View {
>   VStack {
>     if let network = session.state.selectedNetwork {
>       Text(network.displayName)
>         .transition(.opacity)
>     }
>     CardFormDefaults.cardNumber(session)
>   }
>   .animation(.default, value: session.state.selectedNetwork?.network)
> }
> ```

### Co-badge network selector

> [tab] Web
>
> Some cards support multiple networks (e.g., Carte Bancaire / Visa). Use `alternatives` to offer a network picker:
>
> ```javascript theme={"dark"}
> checkout.addEventListener('primer:bin-data-available', (event) => {
>   const { preferred, alternatives } = event.detail;
>   const cobrandEl = document.getElementById('cobrand-selector');
>
>   if (preferred) {
>     document.getElementById('card-logo').src = `/images/${preferred.network.toLowerCase()}.svg`;
>   }
>
>   const selectableNetworks = [preferred, ...alternatives].filter(n => n?.allowed);
>   cobrandEl.hidden = selectableNetworks.length <= 1;
> });
> ```

> [tab] Android
>
> ```kotlin theme={"dark"}
> val controller = rememberCardFormController(checkout)
> val state by controller.state.collectAsStateWithLifecycle()
> val networkSelection = state.networkSelection
>
> if (networkSelection.isNetworkSelectable && networkSelection.availableNetworks.size > 1) {
>     NetworkSelector(
>         networks = networkSelection.availableNetworks,
>         selected = networkSelection.selectedNetwork,
>         onSelect = { network -> controller.selectCardNetwork(network) },
>     )
> }
> ```
>
> > [!NOTE]
> >
> > `PrimerCardForm` renders a dropdown selector for co-badged cards automatically. Use `selectCardNetwork()` only if building a fully custom card form.

> [tab] iOS
>
> When a card supports multiple networks, `session.state.availableNetworks` contains all options. Render your own picker and commit the choice with `session.selectCardNetwork(_:)`. After that call, `session.state.selectedNetwork` holds the user's pick:
>
> ```swift theme={"dark"}
> @ObservedObject var session: PrimerCardFormSession
>
> var body: some View {
>   let networks = session.state.availableNetworks
>   if networks.count > 1 {
>     HStack {
>       ForEach(networks, id: \.network) { network in
>         Button(network.displayName) {
>           session.selectCardNetwork(network)
>         }
>         .buttonStyle(.bordered)
>         .disabled(!network.allowed)
>       }
>     }
>   }
> }
> ```
>
> > [!NOTE]
> >
> > `PrimerCardForm` renders a network selector for co-badged cards automatically, inside the card number field. It is a dropdown by default. With `PrimerCardPaymentOptions(networkSelectorStyle: .inline)`, it shows inline badges instead. When a detected network such as EFTPOS does not allow selection, it shows the badges of all detected networks and offers no choice. `CardFormDefaults.cardNumber(session)` keeps this selector when you recompose the fields, so do not add `CardFormDefaults.cardNetwork(session)` next to it. Call `selectCardNetwork(_:)` only when you build your own picker.

### Use icon font or CSS classes

```javascript theme={"dark"}
checkout.addEventListener('primer:bin-data-available', (event) => {
  const { preferred } = event.detail;
  const iconEl = document.getElementById('card-icon');

  if (preferred) {
    iconEl.classList.add(`card-${preferred.network.toLowerCase()}`);
  }
});
```

## See also

> [card] [Build a custom card form](https://primer.io/docs/checkout/primer-checkout/guides-and-recipes/build-custom-card-form.md)
>
> Step-by-step guide to building a fully custom card form

> [card] [Events guide](https://primer.io/docs/checkout/primer-checkout/configuration/events.md)
>
> Handle payment lifecycle events

> [card] [Use BIN data for Click to Pay](https://primer.io/docs/checkout/primer-checkout/guides-and-recipes/use-bin-data-to-control-click-to-pay.md)
>
> Control Click to Pay based on BIN data


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.