Recipe
- Web
- Android
- iOS
Copy
Ask AI
checkout.addEventListener('primer:ready', (event) => {
const primer = event.detail;
primer.onPaymentFailure = ({ error }) => {
console.error('Payment failed:', {
code: error.code,
message: error.message,
diagnosticsId: error.diagnosticsId, // Share with Primer support
});
};
});
Copy
Ask AI
PrimerCheckoutSheet(
checkout = checkout,
onEvent = { event ->
when (event) {
is PrimerCheckoutEvent.Failure -> {
val error = event.error
Log.e("Checkout", "Payment failed: ${error.description}")
Log.e("Checkout", "Error code: ${error.errorCode}")
Log.e("Checkout", "Diagnostics ID: ${error.diagnosticsId}")
Log.e("Checkout", "Recovery: ${error.recoverySuggestion}")
}
}
},
)
Copy
Ask AI
PrimerCheckout(
clientToken: clientToken,
onCompletion: { state in
if case .failure(let error) = state {
print("[Primer] Error ID: \(error.errorId)")
print("[Primer] Description: \(error.errorDescription ?? "N/A")")
print("[Primer] Diagnostics: \(error.diagnosticsId ?? "N/A")")
print("[Primer] Recovery: \(error.recoverySuggestion ?? "N/A")")
}
}
)
How it works
- Web
- Android
- iOS
- Listen for the
primer:readyevent to access the Primer SDK instance - Set the
onPaymentFailurecallback - Log the error details including
diagnosticsIdwhich helps Primer support investigate issues
- Use the
onEventcallback onPrimerCheckoutSheetorPrimerCheckoutHost - Handle
PrimerCheckoutEvent.Failureto access thePrimerError - Log the error details including
diagnosticsIdwhich helps Primer support investigate issues
PrimerError includes these properties:| Property | Type | Description |
|---|---|---|
errorId | String | Unique error identifier |
description | String | Human-readable error message |
errorCode | String? | Error code (e.g., "card_declined") |
diagnosticsId | String | Reference ID for Primer support |
recoverySuggestion | String? | Suggested recovery action for the user |
- Use the
onCompletioncallback or observecheckoutScope.statefor.failurecases - Access
PrimerErrorproperties for debugging details - Log the
diagnosticsIdwhich helps Primer support investigate issues
PrimerError includes these properties:| Property | Type | Description |
|---|---|---|
errorId | String | Unique error identifier |
errorDescription | String? | Human-readable error message |
diagnosticsId | String? | Reference ID for Primer support |
recoverySuggestion | String? | Suggested recovery action for the user |
The
diagnosticsId is a unique identifier for the error. When contacting Primer support, always include this ID to help them quickly locate and diagnose the issue.Variations
Send errors to logging service
- Web
- Android
- iOS
Copy
Ask AI
primer.onPaymentFailure = ({ error, paymentMethodType }) => {
// Send to your logging service (Sentry, LogRocket, etc.)
Sentry.captureException(new Error(error.message), {
extra: {
code: error.code,
diagnosticsId: error.diagnosticsId,
paymentMethod: paymentMethodType,
},
});
};
Copy
Ask AI
is PrimerCheckoutEvent.Failure -> {
val error = event.error
Sentry.captureException(Exception(error.description)) { scope ->
scope.setExtra("error_code", error.errorCode.orEmpty())
scope.setExtra("diagnostics_id", error.diagnosticsId)
}
}
Copy
Ask AI
if case .failure(let error) = state {
ErrorLogger.log(
errorId: error.errorId,
description: error.errorDescription ?? "Unknown error",
diagnosticsId: error.diagnosticsId ?? "N/A"
)
}
Custom error display
- Web
- Android
- iOS
Copy
Ask AI
const primerEvents = [
'primer:ready',
'primer:state-change',
'primer:methods-update',
'primer:payment-start',
'primer:payment-success',
'primer:payment-failure',
'primer:card-success',
'primer:card-error',
'primer:bin-data-available',
'primer:vault-methods-update',
];
primerEvents.forEach((eventName) => {
document.addEventListener(eventName, (event) => {
console.log(`[${eventName}]`, event.detail);
});
});
Override the error slot in
PrimerCheckoutSheet to display a custom error screen:Copy
Ask AI
PrimerCheckoutSheet(
checkout = checkout,
error = { error ->
Column(
modifier = Modifier
.fillMaxWidth()
.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Icon(
imageVector = Icons.Default.Warning,
contentDescription = null,
tint = MaterialTheme.colorScheme.error,
modifier = Modifier.size(48.dp),
)
Spacer(Modifier.height(16.dp))
Text(
text = "Something went wrong",
style = MaterialTheme.typography.titleLarge,
)
Spacer(Modifier.height(8.dp))
Text(
text = error.description,
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
if (error.recoverySuggestion != null) {
Spacer(Modifier.height(4.dp))
Text(
text = error.recoverySuggestion!!,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center,
)
}
Spacer(Modifier.height(24.dp))
Button(
onClick = { checkout.refresh() },
modifier = Modifier.fillMaxWidth(),
) {
Text("Try again")
}
Spacer(Modifier.height(8.dp))
OutlinedButton(
onClick = { checkout.dismiss() },
modifier = Modifier.fillMaxWidth(),
) {
Text("Use a different method")
}
}
},
onEvent = { event ->
when (event) {
is PrimerCheckoutEvent.Failure -> {
Log.e("Checkout", "Diagnostics: ${event.error.diagnosticsId}")
}
}
},
onDismiss = { },
)
Observe state changes and display errors inline:
Copy
Ask AI
PrimerCheckout(
clientToken: clientToken,
scope: { checkoutScope in
Task {
for await state in checkoutScope.state {
if case .failure(let error) = state {
print("[Primer] Error: \(error.errorDescription ?? "N/A")")
print("[Primer] Diagnostics: \(error.diagnosticsId ?? "N/A")")
}
}
}
}
)
Inline error handling
- Web
- Android
- iOS
Copy
Ask AI
checkout.addEventListener('primer:methods-update', (event) => {
const methods = event.detail.toArray();
console.table(methods.map((m) => ({ type: m.type, id: m.id })));
});
For
PrimerCheckoutHost, handle errors through the onEvent callback and display your own UI:Copy
Ask AI
@Composable
fun InlineCheckoutWithErrors(clientToken: String) {
val checkout = rememberPrimerCheckoutController(clientToken)
val state by checkout.state.collectAsStateWithLifecycle()
var paymentError by remember { mutableStateOf<PrimerError?>(null) }
when (state) {
is PrimerCheckoutState.Loading -> {
CircularProgressIndicator()
}
is PrimerCheckoutState.Ready -> {
PrimerCheckoutHost(
checkout = checkout,
onEvent = { event ->
when (event) {
is PrimerCheckoutEvent.Failure -> {
paymentError = event.error
}
is PrimerCheckoutEvent.Success -> {
paymentError = null
}
}
},
) {
val cardFormController = rememberCardFormController(checkout)
Column(Modifier.padding(16.dp)) {
PrimerCardForm(controller = cardFormController)
paymentError?.let { error ->
Spacer(Modifier.height(16.dp))
Card(
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.errorContainer,
),
modifier = Modifier.fillMaxWidth(),
) {
Row(
modifier = Modifier.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
imageVector = Icons.Default.Warning,
contentDescription = null,
tint = MaterialTheme.colorScheme.error,
)
Spacer(Modifier.width(12.dp))
Column(Modifier.weight(1f)) {
Text(
text = error.description,
style = MaterialTheme.typography.bodyMedium,
)
if (error.recoverySuggestion != null) {
Text(
text = error.recoverySuggestion!!,
style = MaterialTheme.typography.bodySmall,
)
}
}
}
}
}
}
}
}
}
}
Handle errors from
onCompletion and display card field validation errors:Copy
Ask AI
PrimerCheckout(
clientToken: clientToken,
scope: { checkoutScope in
if let cardScope: PrimerCardFormScope = checkoutScope.getPaymentMethodScope(PrimerCardFormScope.self) {
Task {
for await state in cardScope.state {
for fieldError in state.fieldErrors {
print("[Primer] Field \(fieldError.fieldType): \(fieldError.message)")
}
}
}
}
},
onCompletion: { state in
if case .failure(let error) = state {
print("[Primer] Payment error: \(error.errorDescription ?? "N/A")")
}
}
)
See also
Track payment in analytics
Send payment events to your analytics platform
Error handling
Handle payment failures and display error messages