> ## Documentation Index
> Fetch the complete documentation index at: https://primer.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Track payment in analytics

> Send payment events to your analytics platform.

Track payment success and failure events in your analytics platform (Google Analytics, Segment, etc.).

## Recipe

> [tab] Web
>
> ```javascript theme={"dark"}
> 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,
>   });
> });
> ```

> [tab] Android
>
> ```kotlin theme={"dark"}
> val state by checkout.state.collectAsStateWithLifecycle()
> LaunchedEffect(state) {
>     when (val s = state) {
>         is PrimerCheckoutState.Success -> {
>             val payment = s.checkoutData.payment
>             trackPaymentSuccess(payment.id, payment.orderId)
>         }
>         is PrimerCheckoutState.Failure -> {
>             val error = s.primerError
>             trackPaymentFailure(error.errorCode, error.diagnosticsId)
>         }
>         else -> Unit
>     }
> }
>
> PrimerCheckoutSheet(checkout = checkout)
> ```

> [tab] iOS
>
> ```swift theme={"dark"}
> PrimerCheckout(clientToken: clientToken) { state in
>   switch state {
>   case let .success(result):
>     Analytics.track("payment_success", properties: [
>       "payment_id": result.paymentId
>     ])
>   case let .failure(error, _):
>     Analytics.track("payment_failure", properties: [
>       "error_id": error.errorId,
>       "diagnostics_id": error.diagnosticsId
>     ])
>   default:
>     break
>   }
> }
> ```

## How it works

> [tab] Web
>
> 1. Listen for `primer:payment-success` and `primer:payment-failure` DOM events
> 2. Extract relevant data from `event.detail`
> 3. Send to your analytics platform with meaningful event names and properties

> [tab] Android
>
> 1. Observe `checkout.state` with `collectAsStateWithLifecycle()` and react in a `LaunchedEffect`
> 2. Map `PrimerCheckoutState` terminal cases to analytics events
> 3. Extract relevant data from each state and send to your analytics platform
>
> | Checkout State | Analytics Event | Key Properties |
> | - | - | - |
> | `Success` | `purchase_completed` | `payment_id`, `order_id` |
> | `Failure` | `purchase_failed` | `error_code`, `diagnostics_id` |

> [tab] iOS
>
> 1. Pass an `onCompletion` closure to `PrimerCheckout` (or to the `.primerCheckoutSession(_:theme:onCompletion:)` modifier when composing views inline). It receives `.failure` once for each failed attempt, while the checkout stays open for the shopper to retry. It then receives `.success` or `.dismissed` exactly once.
> 2. Map `PrimerCheckoutState` cases to analytics events
> 3. 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, checkoutData)` | `payment_failure` | `error_id`, `diagnostics_id`, plus `payment_id` and `order_id` from `checkoutData?.payment` |
> | `.dismissed` | `checkout_dismissed` | — |
>
> If no payment was created before the failure, `checkoutData` is `nil`. This happens for initialization, tokenization and connectivity failures.
>
> `.initializing` and `.ready` are never delivered. With `PrimerCheckout`, a failed initialization arrives as `.failure`. The error screen then offers a retry. With the inline modifier, a failed initialization is the last outcome delivered.
>
> `.failure` can arrive more than once in one checkout, one per failed attempt. A `payment_failure` count is therefore per attempt, not per checkout.

## Variations

### Google Analytics 4 / Firebase Analytics

> [tab] Web
>
> ```javascript theme={"dark"}
> 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,
>   });
> });
> ```

> [tab] Android
>
> Add the Firebase Analytics dependency:
>
> ```kotlin theme={"dark"}
> // build.gradle.kts
> implementation(platform("com.google.firebase:firebase-bom:33.0.0"))
> implementation("com.google.firebase:firebase-analytics")
> ```
>
> Create an analytics tracker:
>
> ```kotlin theme={"dark"}
> 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)
>         }
>     }
> }
> ```
>
> Wire it into your checkout:
>
> ```kotlin theme={"dark"}
> @Composable
> fun CheckoutScreen(
>     clientToken: String,
>     analytics: CheckoutAnalytics,
> ) {
>     val checkout = rememberPrimerCheckoutController(clientToken)
>     val state by checkout.state.collectAsStateWithLifecycle()
>
>     LaunchedEffect(state) {
>         when (val s = state) {
>             is PrimerCheckoutState.Success -> {
>                 val payment = s.checkoutData.payment
>                 analytics.trackPaymentSuccess(payment.id, payment.orderId)
>             }
>             is PrimerCheckoutState.Failure -> {
>                 analytics.trackPaymentFailure(
>                     s.primerError.errorCode,
>                     s.primerError.diagnosticsId,
>                 )
>             }
>             else -> Unit
>         }
>     }
>
>     when (state) {
>         is PrimerCheckoutState.Loading -> CircularProgressIndicator()
>         is PrimerCheckoutState.Ready -> {
>             PrimerCheckoutSheet(checkout = checkout)
>         }
>     }
> }
> ```

> [tab] iOS
>
> Add the Firebase Analytics dependency via SPM or CocoaPods, then create a tracker:
>
> ```swift theme={"dark"}
> class CheckoutAnalytics {
>   let firebaseAnalytics: Analytics.Type = Analytics.self
>
>   func trackState(_ state: PrimerCheckoutState) {
>     switch state {
>     case let .success(result):
>       firebaseAnalytics.logEvent("purchase_completed", parameters: [
>         "payment_id": result.paymentId
>       ])
>     case let .failure(error, _):
>       firebaseAnalytics.logEvent("purchase_failed", parameters: [
>         "error_id": error.errorId,
>         "diagnostics_id": error.diagnosticsId
>       ])
>     default:
>       break
>     }
>   }
> }
> ```
>
> > [!NOTE]
> >
> > `PaymentResult.amount` and `currencyCode` are optional and are `nil` for card and saved-method payments. Take the purchase value and currency from your own order.
>
> Wire it into your checkout. `PrimerCheckout` calls `onCompletion` with `.failure` for each failed attempt, then once with `.success` or `.dismissed`:
>
> ```swift theme={"dark"}
> struct CheckoutView: View {
>   let clientToken: String
>   let analytics = CheckoutAnalytics()
>
>   var body: some View {
>     PrimerCheckout(clientToken: clientToken) { state in
>       analytics.trackState(state)
>     }
>   }
> }
> ```

### Segment / Reusable analytics wrapper

> [tab] Web
>
> ```javascript theme={"dark"}
> 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,
>   });
> });
> ```

> [tab] Android
>
> For apps that use multiple analytics providers, create an interface that decouples checkout tracking from any specific SDK:
>
> ```kotlin theme={"dark"}
> 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) }
>     }
> }
> ```
>
> Usage:
>
> ```kotlin theme={"dark"}
> fun handleState(state: PrimerCheckoutState, tracker: CheckoutTracker) {
>     when (state) {
>         is PrimerCheckoutState.Success -> {
>             val payment = state.checkoutData.payment
>             tracker.onPaymentSuccess(payment.id, payment.orderId)
>         }
>         is PrimerCheckoutState.Failure -> {
>             tracker.onPaymentFailure(state.primerError.errorCode, state.primerError.diagnosticsId)
>         }
>         else -> Unit
>     }
> }
> ```

> [tab] iOS
>
> For apps with multiple analytics providers, create a protocol that decouples checkout tracking:
>
> ```swift theme={"dark"}
> 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) }
>   }
> }
> ```
>
> Usage:
>
> ```swift theme={"dark"}
> func handleState(_ state: PrimerCheckoutState, tracker: CheckoutTracker) {
>   switch state {
>   case let .success(result):
>     tracker.onPaymentSuccess(paymentId: result.paymentId)
>   case let .failure(error, _):
>     tracker.onPaymentFailure(errorId: error.errorId)
>   default:
>     break
>   }
> }
> ```

### Track payment method selection

> [tab] Web
>
> ```javascript theme={"dark"}
> document.addEventListener('primer:payment-start', (event) => {
>   const { paymentMethodType } = event.detail;
>
>   analytics.track('Payment Method Selected', {
>     method: paymentMethodType,
>   });
> });
> ```

> [tab] Android
>
> > [!NOTE]
> >
> > Payment method selection tracking is not available through `PrimerCheckoutState`. The SDK manages payment method selection internally within its UI components.

> [tab] iOS
>
> `PrimerPaymentMethods` passes each row's `CheckoutPaymentMethod` (with `type` and `name`) and an `onSelect` action to its `method` slot. Wrap `PaymentMethodsDefaults.method` and track the tap before calling `onSelect`. `PrimerPaymentMethods` is a composable view, so place it inside `.primerCheckoutSession(_:theme:onCompletion:)`. The managed `PrimerCheckout` modal has no selection callback.
>
> ```swift theme={"dark"}
> struct TrackedPaymentMethods: View {
>   var body: some View {
>     PrimerPaymentMethods(method: { method, onSelect in
>       PaymentMethodsDefaults.method(method) {
>         Analytics.track("payment_method_selected", properties: [
>           "type": method.type,
>           "name": method.name
>         ])
>         onSelect()
>       }
>     })
>   }
> }
> ```

## See also

> [card] [Log errors for debugging](https://primer.io/docs/checkout/primer-checkout/guides-and-recipes/log-errors-debugging.md)
>
> Capture and surface errors during development

> [card] [Events guide](https://primer.io/docs/checkout/primer-checkout/configuration/events.md)
>
> Handle payment lifecycle events


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.