Recipe
- Web
- Android
- iOS
checkout.addEventListener('primer:ready', (event) => {
const primer = event.detail;
const user = getAuthenticatedUser();
if (user?.fullName) {
primer.setCardholderName(user.fullName);
}
});
val checkout = rememberPrimerCheckoutController(clientToken)
val cardFormController = rememberCardFormController(checkout)
LaunchedEffect(cardFormController) {
cardFormController.updateCardholderName(userProfile.fullName)
}
@StateObject private var session = PrimerCheckoutSession(clientToken: clientToken)
var body: some View {
ScrollView {
PrimerCardForm()
}
.primerCheckoutSession(session) { state in
// Handle .success and .dismissed. After .ready, a .failure ends one attempt and the customer can retry.
}
.onChange(of: session.phase) { phase in
guard phase == .ready, let cardForm = session.cardForm else { return }
// The client session already carries the customer you attached on your backend.
let customer = session.clientSession?.customer
let name = [customer?.firstName, customer?.lastName]
.compactMap { $0 }
.joined(separator: " ")
if !name.isEmpty {
cardForm.updateCardholderName(name)
}
}
}
How it works
- Web
- Android
- iOS
- Listen for the
primer:readyevent to access the Primer SDK instance - Retrieve the user’s name from your authentication system or profile data
- Call
primer.setCardholderName()with the name value
When using
setCardholderName(), you don’t need to include the <primer-input-card-holder-name> component in your form if you don’t want users to edit the name.- Create a
PrimerCardFormControllerusingrememberCardFormController() - Call
updateCardholderName()with the user’s name inside aLaunchedEffect - The field will display the prefilled name and the user can edit it if needed
updateCardholderName() sets the field value programmatically. The customer can still edit the prefilled name. The same approach works for other fields — use updateCardNumber(), updateExpiryDate(), or updateCvv() as needed.- Hold a
PrimerCheckoutSessionas a@StateObjectand apply the.primerCheckoutSession(_:theme:onCompletion:)modifier - Once the session reaches
.ready, read the customer fromsession.clientSession— these are the details you attached when you created the client session, so there is nothing to pass in from your own screen - The
cardFormsub-session (aPrimerCardFormSession) is available at the same point — callupdateCardholderName()on it - The field shows the name, the customer can still edit it, and the form counts the field as filled, so the submit button is not held back
The other setters on
PrimerCardFormSession work the same way: updateCardNumber(),
updateExpiryDate(), updateCvv(), updateFirstName(), updateLastName(),
updateAddressLine1(), updateAddressLine2(), updateCity(), updateState(),
updatePostalCode(), updatePhoneNumber() and updateCountryCode(). Each one changes what its
field shows and counts the field as filled. updateCountryCode() takes a country code such as "GB". If the
SDK does not recognize the code, the country field stays as it was. A write that lands while the
customer is typing in a text field does not override their keystrokes.The values you read back are redacted: cardForm.state carries the card number masked to its BIN and
last four digits, and never carries the CVV.Variations
Pre-fill from shipping address
- Web
- Android
- iOS
checkout.addEventListener('primer:ready', (event) => {
const primer = event.detail;
// Get name from shipping form
const shippingName = document.getElementById('shipping-name').value;
if (shippingName) {
primer.setCardholderName(shippingName);
}
});
LaunchedEffect(cardFormController) {
cardFormController.updateCardholderName(shippingAddress.fullName)
}
.onChange(of: session.phase) { phase in
guard phase == .ready, let cardForm = session.cardForm else { return }
let shipping = session.clientSession?.customer?.shippingAddress
let name = [shipping?.firstName, shipping?.lastName]
.compactMap { $0 }
.joined(separator: " ")
if !name.isEmpty {
cardForm.updateCardholderName(name)
}
}
Pre-fill with editable field
If you want to pre-fill but still allow editing, include the cardholder name input and set an initial value:- Web
- Android
- iOS
checkout.addEventListener('primer:ready', (event) => {
const primer = event.detail;
const user = getAuthenticatedUser();
if (user?.fullName) {
// Pre-fill the value
primer.setCardholderName(user.fullName);
// The <primer-input-card-holder-name> component will show this value
// and users can still edit it
}
});
val state by checkout.state.collectAsStateWithLifecycle()
LaunchedEffect(state) {
when (state) {
is PrimerCheckoutState.Success -> { }
is PrimerCheckoutState.Failure -> { }
else -> Unit
}
}
PrimerCheckoutHost(
checkout = checkout,
) {
val cardFormController = rememberCardFormController(checkout)
LaunchedEffect(cardFormController) {
cardFormController.updateCardholderName(userProfile.fullName)
}
PrimerCardForm(
controller = cardFormController,
cardDetails = {
Column {
CardFormDefaults.CardholderField(cardFormController)
Spacer(Modifier.height(8.dp))
CardFormDefaults.CardNumberField(cardFormController)
Spacer(Modifier.height(8.dp))
Row(Modifier.fillMaxWidth()) {
CardFormDefaults.ExpiryField(
cardFormController,
Modifier.weight(1f),
)
Spacer(Modifier.width(8.dp))
CardFormDefaults.CvvField(
cardFormController,
Modifier.weight(1f),
)
}
}
},
)
}
The prefilled field is always editable. Recomposing the
cardDetails slot is how you decide where
the name sits — here it leads the form, so the customer sees what was filled in for them first:struct PrefilledCardFormScreen: View {
@StateObject private var session: PrimerCheckoutSession
init(clientToken: String) {
_session = StateObject(wrappedValue: PrimerCheckoutSession(clientToken: clientToken))
}
var body: some View {
ScrollView {
PrimerCardForm(cardDetails: { formSession in
VStack(spacing: 16) {
CardFormDefaults.cardholderName(formSession)
CardFormDefaults.cardNumber(formSession)
HStack(spacing: 12) {
CardFormDefaults.expiryDate(formSession)
CardFormDefaults.cvv(formSession)
}
CardFormDefaults.cardNetwork(formSession)
}
.padding()
})
}
.primerCheckoutSession(session) { state in
// Handle .success and .dismissed. After .ready, a .failure ends one attempt and the customer can retry.
}
.onChange(of: session.phase) { phase in
guard phase == .ready, let cardForm = session.cardForm else { return }
let customer = session.clientSession?.customer
let name = [customer?.firstName, customer?.lastName]
.compactMap { $0 }
.joined(separator: " ")
if !name.isEmpty {
cardForm.updateCardholderName(name)
}
}
}
}
When you recompose the individual card fields, add
CardFormDefaults.cardNetwork(formSession) so co-badged cards still get a network selector — the built-in one is suppressed once you replace the default cardDetails section.See also
Build a custom card form
Step-by-step guide to building a fully custom card form
SDK options
All available SDK configuration options