> ## Documentation Index
> Fetch the complete documentation index at: https://walletconnect-pay-docs-wcagent-pay-docs-add-sofiusd-usat.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# AI Integration Prompt - Kotlin

> Comprehensive AI integration prompt for Kotlin/Android wallet developers integrating WalletConnect Pay via WalletKit.

# WalletConnect Pay Integration Guide for Kotlin/Android

This guide enables Android wallet developers to integrate WalletConnect Pay into applications that already have WalletKit configured.By following this guide, your wallet will be able to process crypto payment links, allowing users to pay merchants directly from your app.

## Before You Begin

### Study First, Then Implement

This document is a reference guide, not boilerplate code to copy-paste. Before implementing:

1. **Study your existing codebase** - Understand how URI handling, signing, and navigation work in your app
2. **Follow established patterns** - Match your app's architecture, naming conventions, and code style
3. **Adapt, don't copy** - The code examples show what to do, not necessarily how your specific app should do it

### Prerequisites

* WalletKit SDK integrated and initialized (`com.reown:walletkit`)
* Ethereum signing capability (EIP-712 typed data, personal\_sign)
* Kotlin Coroutines for async operations
* Understanding of your app's navigation and state management

### Dependencies

```kotlin theme={null}
// In your build.gradle.kts
dependencies {
    implementation("com.reown:walletkit:$walletKitVersion")

    // For EIP-712 signing (if not already included)
    implementation("org.web3j:core:4.9.8")
}
```

## Architecture Overview

### How WalletConnect Pay Works

WalletConnect Pay enables crypto payments through payment links. The flow works as follows:

```
Payment Link → Get Options → Select Option → [WebView Data Collection]* → Get Actions → Sign Actions → Confirm Payment
       ↓             ↓            ↓                    ↓                         ↓             ↓              ↓
   Detection    API call    User picks       Per-option IC (if needed)    Fetch actions  Wallet signs   Backend confirms
```

### WalletKit Integration

WalletKit automatically initializes WalletConnectPay during `WalletKit.initialize()`. The Pay functionality is exposed through the `WalletKit.Pay` object:

```kotlin theme={null}
object WalletKit {
    object Pay {
        suspend fun getPaymentOptions(paymentLink: String, accounts: List<String>): Result<PaymentOptionsResponse>
        suspend fun getRequiredPaymentActions(params: Params.RequiredPaymentActions): Result<List<RequiredAction>>
        suspend fun confirmPayment(params: Params.ConfirmPayment): Result<ConfirmPaymentResponse>
        fun isPaymentLink(uri: String): Boolean
    }
}
```

## Step 1: Payment Link Detection

### Using the Official Detection Function

WalletKit provides `isPaymentLink()` to identify payment URIs. Always use this function rather than custom URL parsing to ensure compatibility as the protocol evolves.

```kotlin theme={null}
import com.reown.walletkit.client.WalletKit

fun handleUri(uri: String) {
    when {
        WalletKit.Pay.isPaymentLink(uri) -> {
            // Handle as payment link
            navigateToPaymentFlow(uri)
        }
        // Handle other URI types (WalletConnect pairing, deep links, etc.)
        else -> handleOtherUri(uri)
    }
}
```

### Add Detection to All Entry Points

Payment link detection must be added wherever your app processes URIs:

1. **QR Code Scanner**

```kotlin theme={null}
fun onQrCodeScanned(scannedUri: String) {
    if (WalletKit.Pay.isPaymentLink(scannedUri)) {
        navigateToPaymentFlow(scannedUri)
    } else {
        // Handle as WalletConnect pairing or other URI
        handleWalletConnectUri(scannedUri)
    }
}
```

2. **Text Input / Paste**

```kotlin theme={null}
fun onUriPasted(pastedUri: String) {
    if (WalletKit.Pay.isPaymentLink(pastedUri)) {
        navigateToPaymentFlow(pastedUri)
    } else {
        handleGenericUri(pastedUri)
    }
}
```

3. **Deep Link Handler**

```kotlin theme={null}
// In your Activity or deep link handler
override fun onNewIntent(intent: Intent?) {
    super.onNewIntent(intent)
    intent?.data?.toString()?.let { uri ->
        if (WalletKit.Pay.isPaymentLink(uri)) {
            navigateToPaymentFlow(uri)
        } else {
            handleDeepLink(uri)
        }
    }
}
```

> **Important:** The `isPaymentLink()` check must precede any generic HTTPS handlers to prevent payment links from accidentally opening in a browser.

## Step 2: Implement the Payment Flow

### Data Models

WalletKit exposes payment models under `Wallet.Model`:

```kotlin theme={null}
// Payment status enumeration
enum class PaymentStatus {
    REQUIRES_ACTION,  // Additional action needed
    PROCESSING,       // Payment being processed
    SUCCEEDED,        // Payment completed
    FAILED,           // Payment failed
    EXPIRED           // Payment link expired
}

// Payment information from merchant
data class PaymentInfo(
    val status: PaymentStatus,
    val amount: PaymentAmount,
    val expiresAt: Long,
    val merchant: MerchantInfo
)

// Payment option (token/chain combination)
data class PaymentOption(
    val id: String,
    val amount: PaymentAmount,
    val account: String,
    val estimatedTxs: Int?,
    val collectData: CollectDataAction?  // Per-option data collection (null if not required)
)

// Data collection action (for KYC/compliance via WebView)
data class CollectDataAction(
    val url: String,           // WebView URL for data collection
    val schema: String?        // JSON schema describing required fields
)

// Transaction result details (present when payment already completed)
data class PaymentResultInfo(
    val txId: String,          // Transaction ID
    val optionAmount: PaymentAmount  // Token amount details
)

// Signing action required from wallet
data class WalletRpcAction(
    val chainId: String,
    val method: String,  // "eth_signTypedData_v4" or "personal_sign"
    val params: String   // JSON array as string
)
```

### Payment Flow Implementation

#### 2.1 Get Payment Options

```kotlin theme={null}
import com.reown.walletkit.client.WalletKit
import com.reown.walletkit.client.Wallet

suspend fun getPaymentOptions(
    paymentLink: String,
    walletAddress: String
): Result<Wallet.Model.PaymentOptionsResponse> {
    // Format account as CAIP-10: "eip155:{chainId}:{address}"
    // Include all chains your wallet supports
    val accounts = listOf(
        "eip155:1:$walletAddress",     // Ethereum Mainnet
        "eip155:137:$walletAddress",   // Polygon
        "eip155:42161:$walletAddress", // Arbitrum
        "eip155:10:$walletAddress",    // Optimism
        "eip155:8453:$walletAddress"   // Base
    )

    return WalletKit.Pay.getPaymentOptions(
        paymentLink = paymentLink,
        accounts = accounts
    )
}
```

#### 2.2 Handle Data Collection via WebView (if required)

Some payments require user information (KYC/AML compliance). Data collection requirements are specified per payment option via `collectData`. Show all options first, then handle data collection after the user selects an option:

```kotlin theme={null}
fun processPaymentOptionsResponse(response: Wallet.Model.PaymentOptionsResponse) {
    if (response.options.isEmpty()) {
        showError("No payment options available")
        return
    }
    // Show all options — each option's collectData indicates if IC is needed
    showPaymentOptions(response.options, response.info)
}
```

When `selectedOption.collectData?.url` is present, display the URL in a WebView before proceeding with signing. The hosted form handles rendering, validation, and Terms & Conditions acceptance. The WebView communicates completion via JavaScript bridge messages (`IC_COMPLETE` / `IC_ERROR`).

The form URL accepts optional query parameters (append them with `buildFormUrl` before loading):

* `prefill` — base64url-encoded JSON of known user fields. Keys must match the `required` fields from `collectData.schema` (e.g. `fullName`, `dob`, `pobAddress`).
* `theme` — `light` or `dark`, to match your wallet's color mode.
* `themeVariables` — a base64url string exported from the [WalletConnect Pay Dashboard](https://dashboard.walletconnect.com) that overrides design tokens (font, font size, some colors, button/input border radius). Append it verbatim.

> **Important:** When using the WebView approach, do **not** pass `collectedData` to `confirmPayment()`. The WebView submits data directly to the backend.

#### WebView Implementation

```kotlin theme={null}
import android.webkit.JavascriptInterface
import android.webkit.WebView
import android.webkit.WebViewClient
import android.webkit.WebResourceRequest
import android.content.Intent
import android.net.Uri
import android.util.Base64
import androidx.compose.runtime.Composable
import androidx.compose.ui.viewinterop.AndroidView
import org.json.JSONObject

@Composable
fun PayDataCollectionWebView(
    url: String,
    onComplete: () -> Unit,
    onError: (String) -> Unit
) {
    AndroidView(factory = { context ->
        WebView(context).apply {
            settings.javaScriptEnabled = true
            settings.domStorageEnabled = true
            settings.allowFileAccess = false

            addJavascriptInterface(
                object {
                    @JavascriptInterface
                    fun onDataCollectionComplete(json: String) {
                        val message = JSONObject(json)
                        when (message.optString("type")) {
                            "IC_COMPLETE" -> onComplete()
                            "IC_ERROR" -> onError(
                                message.optString("error", "Unknown error")
                            )
                        }
                    }
                },
                "AndroidWallet"
            )

            webViewClient = object : WebViewClient() {
                override fun shouldOverrideUrlLoading(
                    view: WebView?,
                    request: WebResourceRequest?
                ): Boolean {
                    val requestUrl = request?.url?.toString() ?: return false
                    if (!requestUrl.contains("pay.walletconnect.com")) {
                        context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(requestUrl)))
                        return true
                    }
                    return false
                }
            }

            loadUrl(url)
        }
    })
}

fun buildFormUrl(
    baseUrl: String,
    prefillData: Map<String, String> = emptyMap(),
    theme: String? = null,            // "light" or "dark"
    themeVariables: String? = null    // base64url string exported from the Pay Dashboard
): String {
    val builder = Uri.parse(baseUrl).buildUpon()
    if (prefillData.isNotEmpty()) {
        val prefill = Base64.encodeToString(
            JSONObject(prefillData).toString().toByteArray(),
            Base64.NO_WRAP or Base64.URL_SAFE or Base64.NO_PADDING
        )
        builder.appendQueryParameter("prefill", prefill)
    }
    theme?.let { builder.appendQueryParameter("theme", it) }
    themeVariables?.let { builder.appendQueryParameter("themeVariables", it) }
    return builder.build().toString()
}
```

#### 2.3 Get Required Payment Actions

After user selects a payment option, get the signing actions:

```kotlin theme={null}
suspend fun getRequiredActions(
    paymentId: String,
    optionId: String
): Result<List<Wallet.Model.RequiredAction>> {
    return WalletKit.Pay.getRequiredPaymentActions(
        Wallet.Params.RequiredPaymentActions(
            paymentId = paymentId,
            optionId = optionId
        )
    )
}
```

#### 2.4 Sign the Actions

Sign each `WalletRpcAction` with the wallet's private key:

```kotlin theme={null}
import org.json.JSONArray
import org.web3j.crypto.ECKeyPair
import org.web3j.crypto.Sign
import org.web3j.crypto.StructuredDataEncoder

fun signWalletRpcAction(
    action: Wallet.Model.WalletRpcAction,
    privateKey: ByteArray,
    walletAddress: String
): String {
    return when (action.method) {
        "eth_signTypedData_v4" -> signTypedDataV4(action.params, privateKey, walletAddress)
        "personal_sign" -> personalSign(action.params, privateKey)
        else -> throw UnsupportedOperationException("Unsupported method: ${action.method}")
    }
}

/**
 * Sign EIP-712 typed data.
 * The params are a JSON array: [address, typedDataJson]
 */
fun signTypedDataV4(
    params: String,
    privateKey: ByteArray,
    walletAddress: String
): String {
    val paramsArray = JSONArray(params)
    val requestedAddress = paramsArray.getString(0)
    val typedData = paramsArray.getString(1)

    // Verify address matches
    require(requestedAddress.equals(walletAddress, ignoreCase = true)) {
        "Requested address does not match wallet address"
    }

    // Hash the typed data using StructuredDataEncoder
    val encoder = StructuredDataEncoder(typedData)
    val hash = encoder.hashStructuredData()

    // Sign the hash
    val keyPair = ECKeyPair.create(privateKey)
    val signatureData = Sign.signMessage(hash, keyPair, false)

    // Encode signature as hex string
    val r = signatureData.r.toHexString()
    val s = signatureData.s.toHexString()
    val v = (signatureData.v[0].toInt() and 0xff).toString(16).padStart(2, '0')

    return "0x$r$s$v".lowercase()
}

/**
 * Sign a personal message.
 * The params are a JSON array: [messageHex, address]
 */
fun personalSign(
    params: String,
    privateKey: ByteArray
): String {
    val paramsArray = JSONArray(params)
    val messageHex = paramsArray.getString(0)

    // Remove "0x" prefix and decode hex to bytes
    val messageBytes = messageHex.removePrefix("0x").hexToByteArray()

    // Prefix with Ethereum signed message header
    val prefix = "\u0019Ethereum Signed Message:\n${messageBytes.size}"
    val prefixedMessage = prefix.toByteArray() + messageBytes

    // Hash and sign
    val hash = org.web3j.crypto.Hash.sha3(prefixedMessage)
    val keyPair = ECKeyPair.create(privateKey)
    val signatureData = Sign.signMessage(hash, keyPair, false)

    val r = signatureData.r.toHexString()
    val s = signatureData.s.toHexString()
    val v = (signatureData.v[0].toInt() and 0xff).toString(16).padStart(2, '0')

    return "0x$r$s$v".lowercase()
}

// Extension function for hex conversion
fun ByteArray.toHexString(): String = joinToString("") { "%02x".format(it) }
fun String.hexToByteArray(): ByteArray = chunked(2).map { it.toInt(16).toByte() }.toByteArray()
```

> **Critical:** Pass raw JSON strings directly to signing APIs. Do not parse and reconstruct the JSON data, as this can cause transformation issues leading to signature verification failures.

#### 2.5 Confirm Payment

Submit the signatures to confirm the payment (data collection, if any, was already submitted by the WebView):

```kotlin theme={null}
suspend fun confirmPayment(
    paymentId: String,
    optionId: String,
    signatures: List<String>
): Result<Wallet.Model.ConfirmPaymentResponse> {
    return WalletKit.Pay.confirmPayment(
        Wallet.Params.ConfirmPayment(
            paymentId = paymentId,
            optionId = optionId,
            signatures = signatures
        )
    )
}

// Handle the response
fun handleConfirmationResult(response: Wallet.Model.ConfirmPaymentResponse) {
    when (response.status) {
        Wallet.Model.PaymentStatus.SUCCEEDED -> {
            showSuccessScreen("Payment completed successfully!")
        }
        Wallet.Model.PaymentStatus.PROCESSING -> {
            // Payment is being processed asynchronously
            showSuccessScreen("Payment is being processed...")
            // Optionally poll for updates using response.pollInMs
        }
        Wallet.Model.PaymentStatus.FAILED -> {
            showErrorScreen("Payment failed. Please try again.")
        }
        Wallet.Model.PaymentStatus.EXPIRED -> {
            showErrorScreen("Payment link has expired.")
        }
        Wallet.Model.PaymentStatus.REQUIRES_ACTION -> {
            // Additional action needed (rare case)
            showErrorScreen("Additional action required.")
        }
    }
}
```

## Step 3: State Management

### Recommended State Machine

Implement a state machine to manage the payment flow:

```kotlin theme={null}
sealed class PaymentUiState {
    data object Loading : PaymentUiState()

    data class Options(
        val paymentInfo: Wallet.Model.PaymentInfo?,
        val options: List<Wallet.Model.PaymentOption>
    ) : PaymentUiState()

    data class WebViewDataCollection(
        val url: String
    ) : PaymentUiState()

    data class Processing(
        val message: String,
        val paymentInfo: Wallet.Model.PaymentInfo? = null
    ) : PaymentUiState()

    data class Success(
        val message: String,
        val paymentInfo: Wallet.Model.PaymentInfo? = null
    ) : PaymentUiState()

    data class Error(val message: String) : PaymentUiState()
}
```

### ViewModel Implementation

```kotlin theme={null}
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch

class PaymentViewModel(
    private val walletRepository: WalletRepository
) : ViewModel() {

    private val _uiState = MutableStateFlow<PaymentUiState>(PaymentUiState.Loading)
    val uiState: StateFlow<PaymentUiState> = _uiState.asStateFlow()

    private var currentPaymentId: String? = null
    private var selectedOptionId: String? = null
    private var pendingActions: List<Wallet.Model.RequiredAction.WalletRpc> = emptyList()
    private var storedPaymentInfo: Wallet.Model.PaymentInfo? = null
    private var storedOptions: List<Wallet.Model.PaymentOption> = emptyList()

    fun loadPaymentOptions(paymentLink: String) {
        viewModelScope.launch {
            _uiState.value = PaymentUiState.Loading

            val accounts = walletRepository.getAccounts() // CAIP-10 format

            WalletKit.Pay.getPaymentOptions(paymentLink, accounts)
                .onSuccess { response ->
                    currentPaymentId = response.paymentId
                    storedPaymentInfo = response.info
                    storedOptions = response.options

                    if (response.options.isEmpty()) {
                        _uiState.value = PaymentUiState.Error("No payment options available")
                    } else {
                        _uiState.value = PaymentUiState.Options(
                            paymentInfo = response.info,
                            options = response.options
                        )
                    }
                }
                .onFailure { error ->
                    _uiState.value = PaymentUiState.Error(error.message ?: "Failed to load payment options")
                }
        }
    }

    fun selectPaymentOption(optionId: String) {
        val paymentId = currentPaymentId ?: return
        selectedOptionId = optionId

        // Check if the selected option requires data collection
        val selectedOption = storedOptions.find { it.id == optionId }
        selectedOption?.collectData?.url?.let { url ->
            // Show WebView for data collection before proceeding
            _uiState.value = PaymentUiState.WebViewDataCollection(url = url)
            return
        }

        // No data collection needed — proceed directly to signing
        proceedWithPaymentExecution(paymentId, optionId)
    }

    private fun proceedWithPaymentExecution(paymentId: String, optionId: String) {
        viewModelScope.launch {
            _uiState.value = PaymentUiState.Processing("Preparing payment...")

            WalletKit.Pay.getRequiredPaymentActions(
                Wallet.Params.RequiredPaymentActions(paymentId, optionId)
            )
                .onSuccess { actions ->
                    pendingActions = actions.filterIsInstance<Wallet.Model.RequiredAction.WalletRpc>()
                    executePayment()
                }
                .onFailure { error ->
                    _uiState.value = PaymentUiState.Error(error.message ?: "Failed to get payment actions")
                }
        }
    }

    private suspend fun executePayment() {
        val paymentId = currentPaymentId ?: return
        val optionId = selectedOptionId ?: return

        _uiState.value = PaymentUiState.Processing(
            message = "Confirming payment...",
            paymentInfo = storedPaymentInfo
        )

        try {
            // Sign all pending actions
            val signatures = pendingActions.map { action ->
                walletRepository.signWalletRpcAction(action.action)
            }

            // Confirm payment (no collectedData - WebView handles submission)
            WalletKit.Pay.confirmPayment(
                Wallet.Params.ConfirmPayment(
                    paymentId = paymentId,
                    optionId = optionId,
                    signatures = signatures
                )
            )
                .onSuccess { response ->
                    when (response.status) {
                        Wallet.Model.PaymentStatus.SUCCEEDED,
                        Wallet.Model.PaymentStatus.PROCESSING -> {
                            _uiState.value = PaymentUiState.Success(
                                message = if (response.status == Wallet.Model.PaymentStatus.SUCCEEDED)
                                    "Payment completed!" else "Payment processing...",
                                paymentInfo = storedPaymentInfo
                            )
                        }
                        else -> {
                            _uiState.value = PaymentUiState.Error("Payment ${response.status.name.lowercase()}")
                        }
                    }
                }
                .onFailure { error ->
                    _uiState.value = PaymentUiState.Error(error.message ?: "Payment confirmation failed")
                }
        } catch (e: Exception) {
            _uiState.value = PaymentUiState.Error(e.message ?: "An error occurred")
        }
    }

    fun onWebViewComplete() {
        // WebView data collection completed, proceed with payment execution for selected option
        val paymentId = currentPaymentId ?: return
        val optionId = selectedOptionId ?: return
        proceedWithPaymentExecution(paymentId, optionId)
    }

    fun onWebViewError(error: String) {
        _uiState.value = PaymentUiState.Error(error)
    }

    fun cancel() {
        currentPaymentId = null
        selectedOptionId = null
        pendingActions = emptyList()
        _uiState.value = PaymentUiState.Loading
    }
}
```

## Step 4: UI Components

### Required Screens

1. **Loading Screen** - Show while fetching payment options
2. **Options Screen** - List of payment options (tokens/chains). Show an "Info required" badge on options that have `collectData != null`
3. **Data Collection Screen** - WebView for KYC/compliance data (shown after selecting an option that requires IC)
4. **Processing Screen** - Show while signing and confirming
5. **Success Screen** - Payment completed successfully
6. **Error Screen** - Handle failures gracefully

### Jetpack Compose Example

```kotlin theme={null}
@Composable
fun PaymentScreen(
    viewModel: PaymentViewModel = viewModel(),
    onDismiss: () -> Unit
) {
    val uiState by viewModel.uiState.collectAsState()

    when (val state = uiState) {
        is PaymentUiState.Loading -> LoadingContent()

        is PaymentUiState.Options -> OptionsContent(
            paymentInfo = state.paymentInfo,
            options = state.options,
            onSelectOption = { option -> viewModel.selectPaymentOption(option.id) },
            onCancel = { viewModel.cancel(); onDismiss() }
        )

        is PaymentUiState.WebViewDataCollection -> PayDataCollectionWebView(
            url = state.url,
            onComplete = { viewModel.onWebViewComplete() },
            onError = { error -> viewModel.onWebViewError(error) }
        )

        is PaymentUiState.Processing -> ProcessingContent(
            message = state.message,
            paymentInfo = state.paymentInfo
        )

        is PaymentUiState.Success -> SuccessContent(
            message = state.message,
            paymentInfo = state.paymentInfo,
            onDone = { viewModel.cancel(); onDismiss() }
        )

        is PaymentUiState.Error -> ErrorContent(
            message = state.message,
            onRetry = { /* implement retry logic */ },
            onDismiss = { viewModel.cancel(); onDismiss() }
        )
    }
}
```

## Utility Functions

### Format Payment Amount

```kotlin theme={null}
fun formatPaymentAmount(amount: Wallet.Model.PaymentAmount): String {
    val value = amount.value.toBigDecimalOrNull() ?: return "${amount.value} ${amount.unit}"
    val decimals = amount.display?.decimals ?: 18

    val divisor = BigDecimal.TEN.pow(decimals)
    val formattedValue = value.divide(divisor, decimals, RoundingMode.HALF_UP)
        .stripTrailingZeros()
        .toPlainString()

    val symbol = amount.display?.assetSymbol ?: amount.unit
    return "$formattedValue $symbol"
}
```

### Format Expiry Time

```kotlin theme={null}
fun formatExpiryTime(expiresAtMillis: Long): String {
    val now = System.currentTimeMillis()
    val remainingMillis = expiresAtMillis - now

    if (remainingMillis <= 0) return "Expired"

    val minutes = TimeUnit.MILLISECONDS.toMinutes(remainingMillis)
    val seconds = TimeUnit.MILLISECONDS.toSeconds(remainingMillis) % 60

    return when {
        minutes > 60 -> "${minutes / 60}h ${minutes % 60}m remaining"
        minutes > 0 -> "${minutes}m ${seconds}s remaining"
        else -> "${seconds}s remaining"
    }
}
```

### Validate Date Input

```kotlin theme={null}
fun validateDateInput(input: String): Boolean {
    // Expected format: YYYY-MM-DD
    val regex = Regex("""^\d{4}-\d{2}-\d{2}$""")
    if (!regex.matches(input)) return false

    return try {
        LocalDate.parse(input)
        true
    } catch (e: Exception) {
        false
    }
}
```

## Error Handling

### Common Errors

WalletKit exposes typed errors for payment operations:

```kotlin theme={null}
sealed class PaymentError : Exception() {
    data class InvalidPaymentLink(override val message: String) : PaymentError()
    data class PaymentExpired(override val message: String) : PaymentError()
    data class PaymentNotFound(override val message: String) : PaymentError()
    data class InvalidRequest(override val message: String) : PaymentError()
    data class ComplianceFailed(override val message: String) : PaymentError()
    data class NetworkError(override val message: String) : PaymentError()
    data class InternalError(override val message: String) : PaymentError()
}
```

### Error Handling Strategy

```kotlin theme={null}
fun handlePaymentError(error: Throwable): String {
    return when (error) {
        is PaymentError.InvalidPaymentLink -> "Invalid payment link format"
        is PaymentError.PaymentExpired -> "This payment link has expired"
        is PaymentError.PaymentNotFound -> "Payment not found"
        is PaymentError.ComplianceFailed -> "Unable to process payment due to compliance requirements"
        is PaymentError.NetworkError -> "Network error. Please check your connection"
        else -> error.message ?: "An unexpected error occurred"
    }
}
```

## Troubleshooting

### Payment Options Empty

**Symptom:** `getPaymentOptions` returns an empty options list.

**Causes:**

* Wallet address not supported by merchant
* Chain not supported for this payment
* Payment link already used or expired

**Solution:**

* Verify CAIP-10 account format: `eip155:{chainId}:{address}`
* Include all chains your wallet supports
* Check if payment link is still valid

### Signature Verification Failed

**Symptom:** `confirmPayment` fails with signature verification error.

**Causes:**

* Incorrect EIP-712 signing implementation
* JSON transformation altering the typed data
* Wrong address used for signing

**Solution:**

* Use `StructuredDataEncoder` for EIP-712 hashing
* Pass raw JSON strings to signing without parsing/reconstruction
* Verify the signing address matches the requested address in params

### Wrong Chain

**Symptom:** Transaction fails or signing produces unexpected results.

**Causes:**

* Not using the chainId from `WalletRpcAction`
* Signing with wrong network context

**Solution:**

* Always use `action.chainId` to determine which network to sign for
* Ensure your wallet's signing context matches the action's chain

### Payment Status Polling

For `PROCESSING` status, implement polling:

```kotlin theme={null}
suspend fun pollPaymentStatus(
    paymentId: String,
    optionId: String,
    pollIntervalMs: Long
) {
    delay(pollIntervalMs)
    // Re-call getPaymentOptions or implement a status check endpoint
    // Continue polling until status is final (SUCCEEDED, FAILED, EXPIRED)
}
```

## Complete Flow Example

```kotlin theme={null}
class PaymentFlowExample(
    private val walletRepository: WalletRepository
) {
    suspend fun processPayment(paymentLink: String): Result<Unit> = runCatching {
        // 1. Get accounts in CAIP-10 format
        val accounts = walletRepository.getAccountsAsCaip10()

        // 2. Get payment options
        val optionsResponse = WalletKit.Pay.getPaymentOptions(paymentLink, accounts)
            .getOrThrow()

        val paymentId = optionsResponse.paymentId

        // 3. Let user select payment option
        val selectedOption = showOptionsAndGetSelection(optionsResponse.options)

        // 4. Handle data collection via WebView if required for selected option
        selectedOption.collectData?.url?.let { url ->
            showDataCollectionWebView(url)
            // Wait for IC_COMPLETE before proceeding
        }

        // 5. Get required signing actions
        val actions = WalletKit.Pay.getRequiredPaymentActions(
            Wallet.Params.RequiredPaymentActions(paymentId, selectedOption.id)
        ).getOrThrow()

        // 6. Sign all actions
        val signatures = actions
            .filterIsInstance<Wallet.Model.RequiredAction.WalletRpc>()
            .map { walletRepository.signWalletRpcAction(it.action) }

        // 7. Confirm payment
        val confirmResponse = WalletKit.Pay.confirmPayment(
            Wallet.Params.ConfirmPayment(
                paymentId = paymentId,
                optionId = selectedOption.id,
                signatures = signatures
            )
        ).getOrThrow()

        // 8. Handle result
        when (confirmResponse.status) {
            Wallet.Model.PaymentStatus.SUCCEEDED -> { /* Success! */ }
            Wallet.Model.PaymentStatus.PROCESSING -> {
                // Optionally poll for final status
                confirmResponse.pollInMs?.let { delay(it) }
            }
            else -> throw Exception("Payment ${confirmResponse.status}")
        }
    }
}
```

## Reference Implementation

For a complete working example, see the sample wallet implementation:

* `sample/wallet/src/main/kotlin/com/reown/sample/wallet/ui/routes/dialog_routes/payment/PaymentViewModel.kt`
* `sample/wallet/src/main/kotlin/com/reown/sample/wallet/ui/routes/dialog_routes/payment/PaymentRoute.kt`

## Additional Resources

* [WalletKit Documentation](https://docs.reown.com/walletkit/kotlin/overview)
* [WalletConnect Pay Overview](https://docs.reown.com/pay/overview)
* [EIP-712 Specification](https://eips.ethereum.org/EIPS/eip-712)
* [CAIP-10 Account ID Specification](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-10.md)
