Recipe
- Web
- Android
- iOS
checkout.addEventListener('primer:ready', (event) => {
const primer = event.detail;
primer.onPaymentSuccess = ({ payment, paymentMethodType }) => {
const message = `Payment of ${payment.amount} completed via ${paymentMethodType}`;
showSuccessModal(message);
};
});
val state by checkout.state.collectAsStateWithLifecycle()
LaunchedEffect(state) {
when (val s = state) {
is PrimerCheckoutState.Success -> {
val payment = s.checkoutData.payment
Log.d("Checkout", "Payment completed: ${payment.id}")
navController.navigate("confirmation/${payment.id}") {
popUpTo("checkout") { inclusive = true }
}
}
is PrimerCheckoutState.Failure -> {
val error = s.primerError
Log.e("Checkout", "Failed: ${error.description}")
Log.e("Checkout", "Diagnostics: ${error.diagnosticsId}")
}
else -> Unit
}
}
PrimerCheckoutSheet(checkout = checkout)
struct CheckoutView: View {
let clientToken: String
@State private var showSuccess = false
var body: some View {
ZStack {
PrimerCheckout(
clientToken: clientToken,
primerSettings: PrimerSettings(uiOptions: PrimerUIOptions(isSuccessScreenEnabled: false)),
onCompletion: { state in
if case .success = state {
withAnimation { showSuccess = true }
}
}
)
if showSuccess {
SuccessOverlay()
.transition(.opacity)
}
}
}
}
How it works
- Web
- Android
- iOS
- Listen for the
primer:readyevent to access the Primer SDK instance - Set the
onPaymentSuccesscallback - Access
paymentdetails andpaymentMethodTypefrom the callback parameters - Display your custom UI (modal, toast, inline message, etc.)
- Observe
checkout.statewithcollectAsStateWithLifecycle()and react in aLaunchedEffect - Handle
PrimerCheckoutState.Successto accessPrimerCheckoutDatawith payment details - Navigate to a confirmation screen or update your order UI
| Property | Type | Description |
|---|---|---|
payment.id | String | The Primer payment ID |
payment.orderId | String? | Your order ID |
When using
PrimerCheckoutSheet, the SDK shows a default success screen that auto-dismisses after 3 seconds. The Success state emits immediately — you do not need to wait for the dismiss. Navigate as soon as you observe the state.- Turn off the SDK success screen with
PrimerUIOptions(isSuccessScreenEnabled: false) - Use the
onCompletioncallback onPrimerCheckout - Match on
.successto detect payment completion (bind it withcase .success(let result)to readresult.paymentId) - Update your SwiftUI state to show a custom success view
By default,
PrimerCheckout shows its own success screen for 3 seconds and delivers .success to onCompletion when that screen closes. With isSuccessScreenEnabled: false, the SDK shows nothing and delivers .success as soon as the payment completes.Variations
Custom success with payment details
- Web
- Android
- iOS
primer.onPaymentSuccess = ({ payment }) => {
document.getElementById('success-container').innerHTML = `
<h2>Thank you for your order!</h2>
<p>Order ID: ${payment.orderId}</p>
<p>Amount: ${formatCurrency(payment.amount, payment.currencyCode)}</p>
`;
};
Display payment details from the
Success event:val state by checkout.state.collectAsStateWithLifecycle()
LaunchedEffect(state) {
when (val s = state) {
is PrimerCheckoutState.Success -> {
val payment = s.checkoutData.payment
showSuccessScreen(
orderId = payment.orderId,
paymentId = payment.id,
)
}
is PrimerCheckoutState.Failure -> {
Log.e("Checkout", "Failed: ${s.primerError.diagnosticsId}")
}
else -> Unit
}
}
PrimerCheckoutSheet(checkout = checkout)
Display payment details from the success result:
PrimerCheckout(
clientToken: clientToken,
primerSettings: PrimerSettings(uiOptions: PrimerUIOptions(isSuccessScreenEnabled: false)),
onCompletion: { state in
switch state {
case .success(let result):
showSuccessScreen(paymentId: result.paymentId)
case let .failure(error, _):
print("Failed [\(error.errorId)]: \(error.diagnosticsId)")
default:
break
}
}
)
Handle checkout dismissal
- Web
- Android
- iOS
primer.onPaymentSuccess = () => {
const successEl = document.getElementById('success-message');
successEl.classList.add('visible');
// Hide checkout form
document.querySelector('primer-checkout').style.display = 'none';
};
Use the
onDismiss callback to detect when the checkout sheet is closed:PrimerCheckoutSheet(
checkout = checkout,
onDismiss = {
navController.popBackStack()
},
)
onDismiss fires after the sheet closes. If you already navigated away when observing PrimerCheckoutState.Success, guard against double navigation.Handle the
.dismissed case to detect when the shopper closes the checkout. The payment method list shows a close button when dismissalMechanism includes .closeButton. Tapping it ends the checkout with .dismissed. When other methods are available, the close button on a payment method screen, such as the card form, returns the shopper to the list. In that case, .dismissed does not arrive. With a single method, that close button ends the checkout with .dismissed. If you present PrimerCheckout in your own sheet, a swipe-down does not reach onCompletion, so use your sheet’s onDismiss for that.PrimerCheckout(
clientToken: clientToken,
primerSettings: PrimerSettings(
uiOptions: PrimerUIOptions(
dismissalMechanism: [.closeButton]
)
),
onCompletion: { state in
switch state {
case .success:
withAnimation { showSuccess = true }
case .dismissed:
// The shopper closed the checkout
break
default:
break
}
}
)
See also
Redirect after payment
Navigate to a confirmation page after payment
Error handling
Handle payment failures and display error messages