Skip to main content

User flow

  1. Cashier selects “Pay with Crypto” on the POS.
  2. POS shows a WalletConnect QR code with the payment intent.
  3. Customer scans the QR code with their wallet.
  4. Wallet displays the payment amount and the customer approves.
  5. The transaction is broadcasted by the wallet.
  6. POS shows confirmation when payment is successful.

SDK Methods

The WalletConnect POS SDK exposes methods and callbacks to create smooth payment flows.
The methods here are shown in isolation, for a full guide on how to use them follow the step by step guide at the bottom.

Initialization

To initialize the SDK, call the init method with your API credentials:
PosClient.init(
    apiKey = "YOUR_MERCHANT_API_KEY",
    merchantId = "YOUR_MERCHANT_ID",
    deviceId = "unique_device_identifier"
)
Where the initialization parameters are:
ParameterTypeDescription
apiKeyStringYour WalletConnect Pay merchant API key
merchantIdStringYour merchant identifier
deviceIdStringUnique identifier for this POS device
All parameters are required and cannot be blank. The SDK will throw IllegalStateException if validation fails.

Payment Intent

A payment intent specifies the amount to be paid and the currency. Currently, only USD is supported.
PosClient.createPaymentIntent(
    amount = Pos.Amount(
        unit = "iso4217/USD", // ISO 4217 currency format
        value = "1000"        // Amount in minor units (cents)
    ),
    referenceId = "ORDER-12345" // Your internal order reference
)
Where the Pos.Amount has the following fields:
ParameterTypeDescription
unitStringCurrency in ISO 4217 format: "iso4217/{CURRENCY}" (e.g., "iso4217/USD", "iso4217/EUR")
valueStringAmount in minor units (e.g., "1000" = $10.00 USD)
referenceIdStringMerchant’s reference ID for this payment (can be empty)
What it does under the hood:
  • Creates the payment intent on the server
  • Generates the connection URL and QR code
  • Sends the connection proposal to the wallet
  • Awaits the connection result
  • Builds and sends the transaction to the wallet
  • Awaits the transaction result from the wallet
  • Polls the transaction status until completion

Payment intent life cycle

The WalletConnect POS SDK emits events through a delegate that allow you to adapt the POS UI depending on the status of the payment.
sealed interface PaymentEvent {
    // Payment created, QR code ready
    data class PaymentCreated(
        val uri: URI,         // URI for QR code generation
        val amount: Amount,   // Payment amount
        val paymentId: String // Unique payment identifier
    ) : PaymentEvent

    // Customer scanned QR and initiated payment
    data object PaymentRequested : PaymentEvent

    // Payment is being processed on-chain
    data object PaymentProcessing : PaymentEvent

    // Payment completed successfully
    data class PaymentSuccess(
        val paymentId: String
    ) : PaymentEvent

    // Payment failed
    sealed interface PaymentError : PaymentEvent {
        data class CreatePaymentFailed(val message: String) : PaymentError
        data class PaymentFailed(val message: String) : PaymentError
        data class PaymentNotFound(val message: String) : PaymentError
        data class PaymentExpired(val message: String) : PaymentError
        data class InvalidPaymentRequest(val message: String) : PaymentError
        data class Undefined(val message: String) : PaymentError
    }
}

fun interface POSDelegate {
    fun onEvent(event: Pos.PaymentEvent)
}

PosClient.setDelegate(POSDelegate)
Event Flow: PaymentCreated → PaymentRequested → PaymentProcessing → PaymentSuccess ↘ PaymentError

How to Integrate the POS SDK

Below are the steps to integrate the Kotlin POS SDK into your project.

Prerequisites

To use the POS SDK you will need the following:
  • API Key and Merchant ID from WalletConnect Pay
  • Latest SDK (WalletConnect Kotlin): com.walletconnect:pos:0.0.1
  • Requirements: Android min SDK 23

Integration Steps

1

Add the Kotlin POS SDK dependencies

First, add the dependencies to your project’s build.gradle.kts file as shown below:
/app/build.gradle.kts
dependencies {
    implementation("com.walletconnect:pos:0.0.1")
}
2

Initialize the POS client

Initialize the POS client in your Application class:
class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()

        PosClient.init(
            apiKey = "YOUR_MERCHANT_API_KEY",
            merchantId = "YOUR_MERCHANT_ID",
            deviceId = "unique_device_identifier"
        )
    }
}
3

Set up the delegate

It’s important that you register the delegate before triggering a payment, or you risk missing early events like PaymentCreated.Create a delegate to receive payment events:
object MyPaymentDelegate : POSDelegate {
    private val scope = CoroutineScope(
        SupervisorJob() + Dispatchers.IO
    )
    private val _paymentEvents = MutableSharedFlow<Pos.PaymentEvent>()
    val paymentEvents = _paymentEvents.asSharedFlow()

    override fun onEvent(event: Pos.PaymentEvent) {
        scope.launch {
            _paymentEvents.emit(event)
        }
    }
}
Register the delegate after initialization:
PosClient.setDelegate(MyPaymentDelegate)
4

Initiate the payment

Now, you can create a payment intent and initiate the payment:
PosClient.createPaymentIntent(
    amount = Pos.Amount(
        unit = "iso4217/USD",
        value = "1000" // $10.00 USD in cents
    ),
    referenceId = "ORDER-12345"
)
5

Render QR code and handle events

Handle the PaymentCreated event to display the QR code. You can use the uri from the event to generate a QR code. On successful connection, you’ll receive PaymentRequested which you can use to show a loading spinner indicating that the payment request is being processed.Projects need to create their own UI.Example event handling in a ViewModel:
class PaymentViewModel : ViewModel() {
    init {
        viewModelScope.launch {
            MyPaymentDelegate.paymentEvents.collect { event ->
                handlePaymentEvent(event)
            }
        }
    }

    private fun handlePaymentEvent(event: Pos.PaymentEvent) {
        when (event) {
            is Pos.PaymentEvent.PaymentCreated -> {
                // Display QR code using event.uri
                // Show amount: event.amount.format() → "10.00 USD"
                // Store payment ID: event.paymentId
            }
            is Pos.PaymentEvent.PaymentRequested -> {
                // Customer scanned QR - update UI to show "Processing..."
            }
            is Pos.PaymentEvent.PaymentProcessing -> {
                // Transaction submitted on-chain - show "Confirming..."
            }
            is Pos.PaymentEvent.PaymentSuccess -> {
                // Payment complete! Show success screen
            }
            is Pos.PaymentEvent.PaymentError -> {
                handleError(event)
            }
        }
    }
}
6

Observe payment status

The SDK automatically polls for payment status and emits events through the delegate:
  • PaymentRequested → Customer scanned QR and initiated payment
  • PaymentProcessing → Transaction submitted on-chain
  • PaymentSuccess → Payment confirmed and completed
  • PaymentError → Payment failed (see Error Handling below)
Depending on the different status, it’s recommended to adapt the UI to show feedback to the user.

Error Handling

Handle payment errors by checking the PaymentError event type:
private fun handleError(error: Pos.PaymentEvent.PaymentError) {
    val message = when (error) {
        is Pos.PaymentEvent.PaymentError.CreatePaymentFailed ->
            "Failed to create payment: ${error.message}"
        is Pos.PaymentEvent.PaymentError.PaymentFailed ->
            "Payment failed: ${error.message}"
        is Pos.PaymentEvent.PaymentError.PaymentNotFound ->
            "Payment not found: ${error.message}"
        is Pos.PaymentEvent.PaymentError.PaymentExpired ->
            "Payment expired - please try again"
        is Pos.PaymentEvent.PaymentError.InvalidPaymentRequest ->
            "Invalid request: ${error.message}"
        is Pos.PaymentEvent.PaymentError.Undefined ->
            "Unexpected error: ${error.message}"
    }
    showErrorToUser(message)
}

Additional Methods

Cancel an Active Payment

Call when user cancels the payment flow:
PosClient.cancelPayment()
This stops any active polling and allows starting a new payment.

Check Payment Status

For checking status without polling (one-off check):
viewModelScope.launch {
    val status = PosClient.checkPaymentStatus(paymentId)
    when (status) {
        is Pos.PaymentEvent.PaymentSuccess -> {
            /* Payment successful */
        }
        is Pos.PaymentEvent.PaymentProcessing -> {
            /* Payment processing */
        }
        is Pos.PaymentEvent.PaymentError -> {
            /* Handle error */
        }
    }
}

Amount Formatting

The Pos.Amount class provides a formatting helper:
val amount = Pos.Amount(unit = "iso4217/USD", value = "1050")
amount.format() // Returns: "10.50 USD"

Shutdown SDK

Call when SDK is no longer needed (e.g., app termination):
PosClient.shutdown()

API Reference Summary

MethodDescription
PosClient.init(apiKey, merchantId, deviceId)Initialize the SDK (required before any other calls)
PosClient.setDelegate(delegate)Set delegate for receiving payment events
PosClient.createPaymentIntent(amount, referenceId)Create payment and start polling
PosClient.cancelPayment()Cancel active payment/polling
PosClient.checkPaymentStatus(paymentId)One-off status check (suspend function)
PosClient.shutdown()Release all SDK resources

Important Notes

  1. Initialize once - Call init() only once, typically in Application.onCreate()
  2. Set delegate early - Set delegate before creating payments to avoid missing events
  3. Minor units - Amount value is in minor units (cents for USD, not dollars)
  4. Cancel before new payment - Call cancelPayment() if user exits payment flow
  5. Thread safety - Events are delivered on IO dispatcher; update UI on main thread