Nearby Payment
This guide describes the nearby payment flow where a Customer device discovers multiple Business devices in range via BLE and can accept payments from any of them.
Prerequisites
- SDK initialized and configured. See: Android VipasoSDK
- Business user and Customer user are authenticated on their respective devices
Flow overview
- Customer starts listening for nearby payment events via BLE (ambient discovery)
- Business sends a payment request via BLE to all potential payers in range
- Information is exchanged between Business and Customer devices once connected
- The payment request is shown to the Customer, who can accept or cancel
- If approved, the Customer sends an acceptance back to the Business device
- Payment execution and finalization occur with the backend on both sides
Offline scenarios:
- If one side is offline, the online side handles the payment and shares the result
- If both are offline, payments require pre-authorization on the Customer instrument (experimental)
1) Business — Send nearby payment request
The Business device broadcasts a payment request to all potential payers in range. The call returns when the first Customer accepts it.
Note: The payment remains active until it reaches a final state (successful, cancelled, or failed), after which it is automatically terminated.
import io.vipaso.vipaso.sdkApi.payment.business.response.VipasoPaymentResult
val result: VipasoPaymentResult = Vipaso.payment.business.sendNearbyPaymentRequest(
amount = "12.00",
currency = "USD",
paymentReference = null // optional
)
when (result) {
is VipasoPaymentResult.PaymentSuccessful -> {
val id = result.paymentId
val amount = result.amount
val tip = result.tip
val currency = result.currency
// Handle success - payment automatically terminates
}
is VipasoPaymentResult.PaymentCancelled -> {
val id = result.paymentId
// Handle cancellation - payment automatically terminates
}
is VipasoPaymentResult.PaymentFailed -> {
val id = result.paymentId
val error = result.error
// Handle failure - payment automatically terminates
}
}
Possible VipasoPaymentResult outcomes:
-
PaymentSuccessful(paymentId: UUID, amount: String, tip: String, currency: String)- The payment was approved by both Business and Customer.
- Example amounts:
"100.00"for amount,"0.00"for tip.
-
PaymentCancelled(paymentId: UUID)- The payment was cancelled on the Business device.
-
PaymentFailed(paymentId: UUID, error: Throwable)- There was an unrecoverable error on the Business side.
2) Customer — Listen for nearby payments and accept
The Customer device listens for incoming payment requests from any nearby Business terminal via BLE and accepts or declines.
Note: This starts a payment that remains active until it reaches a final state. To receive another payment, call the receive method again.
import io.vipaso.vipaso.sdkApi.payment.customer.response.VipasoCustomerPaymentEvent
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
val nearbyPaymentsFlow: Flow<VipasoCustomerPaymentEvent> = Vipaso.payment.customer.receiveNearbyPaymentRequests()
scope.launch {
nearbyPaymentsFlow.collect { event ->
when (event) {
is VipasoCustomerPaymentEvent.PaymentRequestReceived -> {
val paymentId = event.paymentId
val amount = event.amount
val currency = event.currency
val businessUserId = event.businessUserID
val operatorId = event.operatorId
val businessUserName = event.businessUserName
// You can accept or decline the payment request
}
is VipasoCustomerPaymentEvent.PaymentSuccessful -> {
// Payment was successful, show confirmation
// Payment automatically terminates after this event
}
is VipasoCustomerPaymentEvent.PaymentCancelled -> {
// Payment was cancelled
// Payment automatically terminates after this event
}
is VipasoCustomerPaymentEvent.AnotherUserPaid -> {
// Another user paid instead
// Payment automatically terminates after this event
}
is VipasoCustomerPaymentEvent.PaymentFailed -> {
// Payment failed, show error message
// Payment automatically terminates after this event
}
}
}
}
Accept Payment
When an incoming payment request arrives, users can decide if they accept it. If the user accepts, send the acceptance to the Business device:
// Request: Accept a payment from a nearby payment request
val acceptance = VipasoPaymentAcceptanceRequest(
paymentId = UUID.fromString("payment-id-from-request"),
amount = "50.00",
tip = "5.00", // optional
currency = "USD",
instrumentId = "selected-instrument-id",
isDelegatedInstrument = true // set to true if instrument is delegated
)
Vipaso.payment.customer.acceptPayment(acceptance)
// The payment result will be delivered through the receiveNearbyPaymentRequests() flow
// as PaymentSuccessful, PaymentFailed, or other events
Note: The acceptance method call is the same for both delegated and non-delegated instruments. Only the isDelegatedInstrument parameter changes.
Cancel Payment (Business)
The Business can cancel the current payment, which terminates the payment on both Business and Customer sides:
// Cancel current payment and automatically terminate payment on both sides
Vipaso.payment.business.cancelPayment()
Important: When business cancels a payment, all connected customers are notified that the payment is cancelled and cannot pay it anymore and their payments are automatically terminated.
Cancel Payment (Customer)
If the Customer does not accept a payment, they can cancel the operation:
// Request: Reject or cancel a payment operation
Vipaso.payment.customer.cancelPayment("payment_id")
// This gracefully cancels the payment and automatically terminates the payment
// The payment state will be updated in the nearby payment flow
Important: When you cancel a payment, the customer will not receive this payment request from the business side anymore.
3) Termination
To manually terminate the payment at any time (e.g., user navigates away, app lifecycle events):
// Manually terminate payment (idempotent - safe to call anytime)
Vipaso.payment.customer.terminatePayment()
Best Practice: Always call terminatePayment() in cleanup blocks (e.g., in finally blocks or onDestroy(), onCleared(), etc.). The method is idempotent and won't cause issues if the payment is already terminated or being terminated