Recipe
- Web
- Android
- iOS
Copy
Ask AI
document.addEventListener('primer:payment-success', (event) => {
const { paymentSummary, paymentMethodType } = event.detail;
analytics.track('Payment Completed', {
paymentId: paymentSummary.id,
orderId: paymentSummary.orderId,
method: paymentMethodType,
last4: paymentSummary.paymentMethodData?.last4Digits,
});
});
document.addEventListener('primer:payment-failure', (event) => {
const { error, paymentMethodType } = event.detail;
analytics.track('Payment Failed', {
errorCode: error.code,
method: paymentMethodType,
diagnosticsId: error.diagnosticsId,
});
});
Copy
Ask AI
PrimerCheckoutSheet(
checkout = checkout,
onEvent = { event ->
when (event) {
is PrimerCheckoutEvent.Success -> {
val payment = event.checkoutData.payment
trackPaymentSuccess(payment.id, payment.orderId)
}
is PrimerCheckoutEvent.Failure -> {
val error = event.error
trackPaymentFailure(error.errorCode, error.diagnosticsId)
}
}
},
)
Copy
Ask AI
PrimerCheckout(
clientToken: clientToken,
scope: { checkoutScope in
Task {
for await state in checkoutScope.state {
switch state {
case .success(let result):
Analytics.track("payment_success", properties: [
"payment_id": result.payment?.id ?? ""
])
case .failure(let error):
Analytics.track("payment_failure", properties: [
"error_id": error.errorId
])
default:
break
}
}
}
}
)
How it works
- Web
- Android
- iOS
- Listen for
primer:payment-successandprimer:payment-failureDOM events - Extract relevant data from
event.detail - Send to your analytics platform with meaningful event names and properties
- Use the
onEventcallback onPrimerCheckoutSheetorPrimerCheckoutHost - Map
PrimerCheckoutEventtypes to analytics events - Extract relevant data from each event and send to your analytics platform
| Checkout Event | Analytics Event | Key Properties |
|---|---|---|
Success | purchase_completed | payment_id, order_id |
Failure | purchase_failed | error_code, diagnostics_id |
- Use the
scopeclosure to observecheckoutScope.stateviafor await - Map
PrimerCheckoutStatecases to analytics events - Extract relevant data from each state and send to your analytics platform
| Checkout State | Analytics Event | Key Properties |
|---|---|---|
.success(result) | payment_success | payment_id |
.failure(error) | payment_failure | error_id |
.ready(amount, currency) | checkout_ready | amount, currency |
.dismissed | checkout_dismissed | — |
Variations
Google Analytics 4 / Firebase Analytics
- Web
- Android
- iOS
Copy
Ask AI
document.addEventListener('primer:payment-success', (event) => {
const { paymentSummary, paymentMethodType } = event.detail;
gtag('event', 'purchase', {
transaction_id: paymentSummary.orderId,
value: paymentSummary.amount / 100, // Convert from cents
currency: paymentSummary.currencyCode,
payment_type: paymentMethodType,
});
});
Add the Firebase Analytics dependency:Create an analytics tracker:Wire it into your checkout:
Copy
Ask AI
// build.gradle.kts
implementation(platform("com.google.firebase:firebase-bom:33.0.0"))
implementation("com.google.firebase:firebase-analytics")
Copy
Ask AI
class CheckoutAnalytics(
private val firebaseAnalytics: FirebaseAnalytics,
) {
fun trackPaymentSuccess(paymentId: String, orderId: String?) {
firebaseAnalytics.logEvent("purchase_completed") {
param("payment_id", paymentId)
param("order_id", orderId.orEmpty())
}
}
fun trackPaymentFailure(errorCode: String?, diagnosticsId: String) {
firebaseAnalytics.logEvent("purchase_failed") {
param("error_code", errorCode.orEmpty())
param("diagnostics_id", diagnosticsId)
}
}
fun trackPaymentMethodSelected(methodType: String) {
firebaseAnalytics.logEvent("payment_method_selected") {
param("payment_method_type", methodType)
}
}
}
Copy
Ask AI
@Composable
fun CheckoutScreen(
clientToken: String,
analytics: CheckoutAnalytics,
) {
val checkout = rememberPrimerCheckoutController(clientToken)
val state by checkout.state.collectAsStateWithLifecycle()
when (state) {
is PrimerCheckoutState.Loading -> CircularProgressIndicator()
is PrimerCheckoutState.Ready -> {
PrimerCheckoutSheet(
checkout = checkout,
onEvent = { event ->
when (event) {
is PrimerCheckoutEvent.Success -> {
val payment = event.checkoutData.payment
analytics.trackPaymentSuccess(payment.id, payment.orderId)
}
is PrimerCheckoutEvent.Failure -> {
analytics.trackPaymentFailure(
event.error.errorCode,
event.error.diagnosticsId,
)
}
}
},
)
}
}
}
Add the Firebase Analytics dependency via SPM or CocoaPods, then create a tracker:Wire it into your checkout:
Copy
Ask AI
class CheckoutAnalytics {
let firebaseAnalytics: Analytics.Type = Analytics.self
func trackState(_ state: PrimerCheckoutState) {
switch state {
case .ready(let totalAmount, let currencyCode):
firebaseAnalytics.logEvent("checkout_ready", parameters: [
"amount": totalAmount,
"currency": currencyCode
])
case .success(let result):
firebaseAnalytics.logEvent("purchase_completed", parameters: [
"payment_id": result.payment?.id ?? ""
])
case .failure(let error):
firebaseAnalytics.logEvent("purchase_failed", parameters: [
"error_id": error.errorId
])
default:
break
}
}
}
Copy
Ask AI
let analytics = CheckoutAnalytics()
PrimerCheckout(
clientToken: clientToken,
scope: { checkoutScope in
Task {
for await state in checkoutScope.state {
analytics.trackState(state)
}
}
}
)
Segment / Reusable analytics wrapper
- Web
- Android
- iOS
Copy
Ask AI
document.addEventListener('primer:payment-success', (event) => {
const { paymentSummary, paymentMethodType } = event.detail;
analytics.track('Order Completed', {
order_id: paymentSummary.orderId,
total: paymentSummary.amount / 100,
currency: paymentSummary.currencyCode,
payment_method: paymentMethodType,
});
});
For apps that use multiple analytics providers, create an interface that decouples checkout tracking from any specific SDK:Usage:
Copy
Ask AI
interface CheckoutTracker {
fun onPaymentSuccess(paymentId: String, orderId: String?)
fun onPaymentFailure(errorCode: String?, diagnosticsId: String)
fun onPaymentMethodSelected(methodType: String)
}
class CompositeCheckoutTracker(
private val trackers: List<CheckoutTracker>,
) : CheckoutTracker {
override fun onPaymentSuccess(paymentId: String, orderId: String?) {
trackers.forEach { it.onPaymentSuccess(paymentId, orderId) }
}
override fun onPaymentFailure(errorCode: String?, diagnosticsId: String) {
trackers.forEach { it.onPaymentFailure(errorCode, diagnosticsId) }
}
override fun onPaymentMethodSelected(methodType: String) {
trackers.forEach { it.onPaymentMethodSelected(methodType) }
}
}
Copy
Ask AI
fun handleEvent(event: PrimerCheckoutEvent, tracker: CheckoutTracker) {
when (event) {
is PrimerCheckoutEvent.Success -> {
val payment = event.checkoutData.payment
tracker.onPaymentSuccess(payment.id, payment.orderId)
}
is PrimerCheckoutEvent.Failure -> {
tracker.onPaymentFailure(event.error.errorCode, event.error.diagnosticsId)
}
}
}
For apps with multiple analytics providers, create a protocol that decouples checkout tracking:Usage:
Copy
Ask AI
protocol CheckoutTracker {
func onPaymentSuccess(paymentId: String)
func onPaymentFailure(errorId: String)
}
class CompositeCheckoutTracker: CheckoutTracker {
let trackers: [CheckoutTracker]
init(trackers: [CheckoutTracker]) { self.trackers = trackers }
func onPaymentSuccess(paymentId: String) {
trackers.forEach { $0.onPaymentSuccess(paymentId: paymentId) }
}
func onPaymentFailure(errorId: String) {
trackers.forEach { $0.onPaymentFailure(errorId: errorId) }
}
}
Copy
Ask AI
func handleState(_ state: PrimerCheckoutState, tracker: CheckoutTracker) {
switch state {
case .success(let result):
tracker.onPaymentSuccess(paymentId: result.payment?.id ?? "")
case .failure(let error):
tracker.onPaymentFailure(errorId: error.errorId)
default:
break
}
}
Track payment method selection
- Web
- Android
- iOS
Copy
Ask AI
document.addEventListener('primer:payment-start', (event) => {
const { paymentMethodType } = event.detail;
analytics.track('Payment Method Selected', {
method: paymentMethodType,
});
});
Payment method selection tracking is not available through
PrimerCheckoutEvent. The SDK manages payment method selection internally within its UI components.Copy
Ask AI
Task {
for await state in checkoutScope.paymentMethodSelection.state {
if let selected = state.selectedPaymentMethod {
Analytics.track("payment_method_selected", properties: [
"type": selected.type,
"name": selected.name
])
}
}
}
See also
Log errors for debugging
Capture and surface errors during development
Events guide
Handle payment lifecycle events