Recipe
- Web
- Android
- iOS
checkout.addEventListener('primer:bin-data-available', (event) => {
const { preferred, status } = event.detail;
if (preferred) {
document.getElementById('card-logo').src = `/images/${preferred.network.toLowerCase()}.svg`;
}
});
val controller = rememberCardFormController(checkout)
val state by controller.state.collectAsStateWithLifecycle()
val detected = state.networkSelection.selectedNetwork
if (detected != null) {
Text(text = detected.displayName)
}
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.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)
}
}
}
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
- Web
- Android
- iOS
- Listen for the
primer:bin-data-availableevent - Use
preferred.networkto get the card brand identifier - Update your UI with the appropriate logo or icon
- Create a
PrimerCardFormControllerusingrememberCardFormController() - Observe its
statewithcollectAsStateWithLifecycle() - Read
state.networkSelection.selectedNetworkfor the currently detected network - 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 |
- Mark the
PrimerCardFormSessioninjected into your slot closure as@ObservedObject - Read
session.state.selectedNetworkfor the network the form uses; it isniluntil a network is detected from the card number - Read
session.state.availableNetworksfor the detected networks yourorderedAllowedCardNetworksallows, in that order; more than one indicates a co-badged card - 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 |
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
- Web
- Android
- iOS
The
preferred object contains the detected card network and, when status is 'complete', additional issuer details:{
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;
}
See the Events Reference for the full
BinDataDetails type definition.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.@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?
}
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 yourorderedAllowedCardNetworksorder. It isnilwhen no detected network is allowed or no card number is entered.alternatives: [PrimerCardNetwork]lists the other detected networks in the same order.status: PrimerBinDataStatusis.partialwhile the network is detected locally from the card number. This happens with fewer than 8 digits, or when the remote lookup fails. It is.completeafter the remote BIN lookup.firstDigits: String?is set for.completeresults and isnilfor.partialresults.
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.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 |
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 for the authoritative iOS list.Variations
Show a loading indicator
Useprimer:bin-data-loading-change to indicate when BIN data is being fetched:
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
- Web
- Android
- iOS
For user-facing text, use
displayName instead of network:checkout.addEventListener('primer:bin-data-available', (event) => {
const { preferred } = event.detail;
const networkName = preferred?.displayName || 'Card';
document.getElementById('card-type-label').textContent = networkName;
});
val controller = rememberCardFormController(checkout)
val state by controller.state.collectAsStateWithLifecycle()
val networkName = state.networkSelection.selectedNetwork?.displayName ?: "Card"
Text(text = "Paying with $networkName")
let networkName = session.state.selectedNetwork?.displayName ?? "Card"
Text("Paying with \(networkName)")
Show/hide based on detection
- Web
- Android
- iOS
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';
}
});
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)
}
}
@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
- Web
- Android
- iOS
Some cards support multiple networks (e.g., Carte Bancaire / Visa). Use
alternatives to offer a network picker: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;
});
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) },
)
}
PrimerCardForm renders a dropdown selector for co-badged cards automatically. Use selectCardNetwork() only if building a fully custom card form.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:@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)
}
}
}
}
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
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
Build a custom card form
Step-by-step guide to building a fully custom card form
Events guide
Handle payment lifecycle events
Use BIN data for Click to Pay
Control Click to Pay based on BIN data