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 the terminal PrimerCheckoutState
}
.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(_: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 billing-address setters behave the same way:
updateFirstName(), updateLastName(),
updateAddressLine1(), updateAddressLine2(), updateCity(), updateState(),
updatePostalCode() and updatePhoneNumber() all change what their field shows.updateCardNumber(), updateCvv(), updateExpiryDate() and updateCountryCode() set the value
used for payment but leave the visible field as the customer left it. For the card number and CVV
that is deliberate: the SDK never hands either back to your code, so it has nothing to write into
the field.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 the terminal PrimerCheckoutState
}
.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