Online Payment

Arguably the most important feature of the SDK is to execute payments between the PoS and Pay apps over BLE. In the online payment use case, in order to make a successful payment, you will need:

The APIs are a bit different on Android and iOS, but the idea is the same. When you enter the desired amount and select Pay:

  • The PoS side creates a new payment instance on the backend and starts advertising its ID over BLE
  • The Pay side is scanning via BLE, receives the payment request and can react to the PoS side
  • Pay then executes the payment request on the backend
  • Lastly both sides poll the payment details from the backend and can react to success or error states separately

Let's check each step in more detail.

1. Pay: Start Scanning

The flow has two steps on iOS. You need to connect to the PoS side via BLE and register as a payment receiver to be notified about any payment requests coming from PoS.

On Android you start bluetooth scan and receive a completable payment response that includes data on payment and a call to notify server on payment result.

vipasoPay.connectBLE { [weak self] result in
    switch result {
    case .success(let response):
        // Success: connected, start listening for payments after the connection succeeds
        vipasoPay.setupBLEPaymentReceiver { result in
            switch result {
            case .success(let response):
                // Success: response wraps the payment identifier
            case .failure(let error):
                // Error: BLE error
            }
        }
    case .failure(let error):
        // Error: connection error
    }
}
val response = vipasoPay.startBLE()
if (response.data != null) {
  // Success: requested payment's id will be in the wrapped response data
  // Also inform payment that payment was accepted/rejected
  response.data.onComplete(true)
} else {
  // Error: message is in response.error?.message
}

Example screenshots from our development application:

2. PoS: Start Payment

When the merchant requests a payment, two things will happen:

1. Getting a new payment ID from the backend

You need to set the expected payment details and the SDK will return a payment ID.

let request = InitiatePaymentRequest(
    amount: "10.00",
    currency: "EUR"
)
vipasoPOS.initiatePayment(request: request) { result in
    switch result {
    case .success(let response):
        // Success: payment ID is in response.paymentID
    case .failure(let error):
        // Error: error message detailing the issue
    }
}
val request = InitiatePaymentRequest(amount = amount, currency = currency)
return vipasoWrapper.vipasoPos.initiatePayment(request).paymentId

If you debug your networking calls, you should see the following traffic:

POST /paga/payment HTTP/1.1
Content-Type: application/json
{
  "currency": "EUR",
  "amount": "5.8"
}
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
{
  "paymentId": "01936d84-42c5-747f-8c5c-f374588cdbf3"
}

2. Requesting payment execution via BLE

You can now advertise the payment ID so any Pay app who is close enough can receive the payment request.

let paymentRequest = PaymentRequest(paymentID: paymentID)
vipasoPOS.initiateBLEPayment(request: paymentRequest) { result in
    switch result {
    case .success(let response):
        print("success: \(response)")
    case .failure(let error):
        print("error: \(error.localizedDescription)")
    }
}
val request: PaymentRequest = PaymentRequest("paymentId")

val response = vipasoPos.initiateBLEPayment(request)
if (response.data != null) {
    print(response)
} else {
    print(response.error?.message)
}

Example screenshots from our development application:

3. Pay: Executing Payment

When the payment ID arrived successfully on the Pay side, you can fetch its details and display a screen to the user where they can select the instrument they want to pay with and an optional tip.

After they accept, you can use the SDK to execute the payment.

let request = ExecutePaymentRequest(
    paymentID: paymentID,
    instrumentID: instrumentID,
    invoiceAmendments: nil,
    lang: .en
)
vipasoPay.executePayment(request: request) { result in
    switch result {
    case .success(let response):
        // Success: response.payment contains all details
    case .failure(let error):
        // Error: any server or generic error
    }
}
val request = ExecutePaymentRequest(
    paymentID: "paymentId",
    instrumentID: "instrumentID",
    lang: "EN",
    confirmationTimestamp: "timeStamp now"
)
val response = vipasoPay.executePayment(request)
if (response.data != null) {
    // Success: response.payment contains all details
} else {
    // Error: any server or generic error
}

If you debug your networking calls, you should see the following traffic:

PATCH /paga/payment HTTP/1.1
Content-Type: application/json
{
  "confirmationTimestamp": "2024-11-27T12:09:00.152Z",
  "instrumentId": "01936D85-F1CA-788B-0000-8DC7AD80BE92",
  "tip": "0.00",
  "paymentId": "01936d84-42c5-747f-8c5c-f374588cdbf3"
}
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
{
  "paymentId": "01936D86-3C08-75F0-9197-6A361F05A28F",
  "amount": "5.8000",
  "currency": "EUR",
  "tip": "0.0000",
  "pagaStatus": "EXECUTED",
  "unifiedStatus": "COMPLETED",
  ...
}

Example screenshots from our development application:

4. Pay/PoS: Polling Payment details

Since payment execution is asynchronous and can take some time, we recommend polling the payment details every two seconds:

Once the status of the payment is "final", you can update your screen accordingly. Possible states are detailed in the VipasoPayment model's unified status property:

"INITIATED"     // Payment execution started
"IN_PROGRESS"   // Payment execution ongoing
"COMPLETED"     // Payment execution finished successfully
"FAILED"        // Payment execution failed
"CANCELLED"     // Payment execution was cancelled

If you debug your networking calls, you should see the following traffic:

GET /paga/payment/01936D84-42C5-747F-8C5C-F374588CDBF3 HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
{
  "paymentId": "01936d84-42c5-747f-8c5c-f374588cdbf3",
  "amount": "5.8000",
  "currency": "EUR",
  "tip": "0.0000",
  "pagaStatus": "EXECUTED",
  "unifiedStatus": "COMPLETED",
  ...
}

Example screenshots from our development application: