Online Payment

Vipaso Pay SDK allows you to execute payments between POS and Pay apps over BLE. To make a successful payment, you will need: - A logged-in POS user (merchant) - A logged-in Pay user (customer) with at least one valid payment instrument

Overview

The flow is the following:

  • The Pay Side starts receiving payments via BLE and listens for payment related events.
  • The POS Side creates a new payment instance and starts requesting payments via BLE. Upon connection, it exchanges information with the Pay Side.
  • When the initial information exchange is complete, the POS Side sends a payment request which can be either accepted or cancelled by the payment.
  • Upon acceptance, the Pay Side sends the acceptance to the POS Side.
  • Lastly, both sides execute the payment with the backend and can react to success or error states separately.
    • If any of the sides are offline then the online side sends the result to the offline side and they finish the payment according to the results.
    • If both side are offline (no internet connection), then please check out Offline Payment Use Case for details.

Let's check each step in more detail.

Payment Setup

Pay Side: Receiving Payments

The first step for the Pay app is to start receiving payments via BLE and listen for incoming payment requests. The SDK will handle the BLE communication and connection establishment in the background.

// Start listening for BLE payments
try {
    vipasoPay.payment.listenForBlePayments().collect { paymentState ->
        when (paymentState) {
            is VipasoWalletPaymentState.ReceivedPaymentRequest -> {
                // Payment request received from a POS
                val paymentRequest = paymentState.paymentRequest
                // Display payment details to the user
                handlePaymentRequest(paymentRequest)
            }
            is VipasoWalletPaymentState.PaymentSuccessful -> {
                // Payment was successful
                handleSuccessfulPayment(paymentState.paymentId)
            }
            is VipasoWalletPaymentState.PaymentCancelled -> {
                // Payment was cancelled by either side
                handleCancelledPayment(paymentState.paymentId)
            }
            is VipasoWalletPaymentState.AnotherUserPaid -> {
                // Payment was paid by another wallet
                handleOtherUserPaid(paymentState.paymentId)
            }
            is VipasoWalletPaymentState.PaymentFailed -> {
                // Payment failed
                handleFailedPayment(paymentState.paymentId, paymentState.error)
            }
        }
    }
} catch (e: Throwable) {
    // Handle errors when starting BLE advertisement
    Log.e("BlePayment", "Error listening for BLE payments: ${e.message}")
}

Example screenshots from our development application:

POS Side: Requesting Payments

In this step, the POS side create a payment request with merchant details, amount, and currency. The SDK will send the request to nearby Pay devices (via BLE) which are ready to receive payments.

// Request a payment via BLE
try {
    val paymentResult = vipasoPos.payment.requestPaymentViaBle(
        amount = "25.50",
        currency = "USD",
        merchantId = "merchant-123",
        operatorId = "operator-456"
    )
    
    // Process the payment result based on its type
    when (paymentResult) {
        is VipasoPosPaymentResult.PaymentSuccessful -> {
            // Payment was successfully processed
            val amount = paymentResult.amount
            val tip = paymentResult.tip 
            val currency = paymentResult.currency
            val paymentId = paymentResult.paymentId
            
            // Update UI with successful payment
            displayPaymentSuccess(amount, tip, currency, paymentId)
        }
        is VipasoPosPaymentResult.PaymentCancelled -> {
            // Payment was cancelled by the operator or payer
            val paymentId = paymentResult.paymentId
            
            // Update UI to show payment was cancelled
            displayPaymentCancelled(paymentId)
        }
        is VipasoPosPaymentResult.PaymentFailed -> {
            // Payment processing failed
            val paymentId = paymentResult.paymentId
            val error = paymentResult.error
            
            // Handle payment failure, display error message
            displayPaymentError(paymentId, error)
        }
    }
} catch (e: Exception) {
    // Handle exceptions during payment initialization
    Log.e("BlePayment", "Failed to initiate payment", e)
}

Example screenshots from our development application:

Payment Processing

Pay Side: Accepting the Payment

After the user has reviewed the payment details and selected a payment instrument, they can accept and execute the payment:

// Create a payment response with the selected instrument
try {
    // First fetch available payment instruments
    val instruments = vipasoPay.instrument.fetchInstruments()
    
    // Let the user select an instrument or use the default
    val selectedInstrument = instruments.instruments.first { it.isDefault }
    
    // Accept the payment with the selected instrument
    vipasoPay.payment.acceptPayment(
        paymentId = paymentId,
        amount = amount,
        instrumentId = selectedInstrument.id,
        tip = tipAmount, // Optional tip amount
        currency = currency
    )
    
    // The payment state will be updated through the listenForBlePayments flow
} catch (e: Throwable) {
    // Handle errors accepting the payment
    Log.e("BlePayment", "Error accepting payment: ${e.message}")
}

Example screenshots from our development application:

Cancelling the Payment

Pay Side Cancellation:

The Pay user can reject or cancel a received payment request. When this happens, this Pay user will not be able to pay this specific payment again.

// Cancel the payment
try {
    vipasoPay.payment.cancelPayment(paymentId)
    // The payment state will be updated through the listenForBlePayments flow
} catch (e: Throwable) {
    // Handle errors cancelling the payment
    Log.e("BlePayment", "Error cancelling payment: ${e.message}")
}

POS Side Cancellation:

With this call the POS side notifies all the connected Pay devices that it the Payment is cancelled so they cannot pay it anymore.

// Cancel payment on POS side
vipasoPos.payment.cancelPayment(paymentId)
// No callback needed as this is a fire and forget call

Stop Receiving Payments

When you no longer need to receive payments, you can stop the payment reception:

// Stop payment reception
try {
    vipasoPay.payment.stopBle()
    // BLE advertisement stopped successfully
} catch (e: Throwable) {
    // Handle errors stopping BLE advertisement
    Log.e("BlePayment", "Error stopping BLE: ${e.message}")
}