Android SDK

Overview

The goal of the BluePOS Go SDK library is to provide an interface for communication between external third-party applications and the BluePOS Go application.

The SDK exposes:

  • An AIDL interface
  • Data classes required to send requests and receive responses

Requirements

  • The BluePOS Go application must be installed on the device
  • The BluePOS Go application must have the AIDL service implemented

Sample Application

This guide walks you through setting up the BluefinPOSGoSDK for Android, step by step. For a complete example of how the SDK components work together, see our sample application on GitHub.


Adding the Library

Add the BluePOS SDK .aar file to your project (e.g., in the libs/ folder).

Then include it in your build.gradle:

dependencies {
    implementation(files("libs/blueposgo-sdk-release.aar"))
}

SDK Imports

Once the SDK library is added to your project, here is the comprehensive list of what you need to import based on your needs.

import android.content.Intent
import android.util.Log

import com.bluefin.blueposgo.sdk.ACTION_EXTERNAL
import com.bluefin.blueposgo.sdk.BLUEPOS_GO_ACTIVITY
import com.bluefin.blueposgo.sdk.BLUEPOS_GO_PACKAGE
import com.bluefin.blueposgo.sdk.EXTRA_CAPTURE_REQUEST
import com.bluefin.blueposgo.sdk.EXTRA_CLEAR_DATA_REQUEST
import com.bluefin.blueposgo.sdk.EXTRA_COMMAND
import com.bluefin.blueposgo.sdk.EXTRA_CONNECT
import com.bluefin.blueposgo.sdk.EXTRA_DISCONNECT
import com.bluefin.blueposgo.sdk.EXTRA_FORGET
import com.bluefin.blueposgo.sdk.EXTRA_FULL_REFUND_REQUEST
import com.bluefin.blueposgo.sdk.EXTRA_INIT
import com.bluefin.blueposgo.sdk.EXTRA_PAYLOAD
import com.bluefin.blueposgo.sdk.EXTRA_PAYMENT_REQUEST
import com.bluefin.blueposgo.sdk.EXTRA_REBOOT
import com.bluefin.blueposgo.sdk.EXTRA_REFUND_REQUEST
import com.bluefin.blueposgo.sdk.EXTRA_TR_LIST_REQUEST
import com.bluefin.blueposgo.sdk.PAYMENT_SERVICE_ACTION
import com.bluefin.blueposgo.sdk.PaymentCallback
import com.bluefin.blueposgo.sdk.PaymentServiceAIDL
import com.bluefin.blueposgo.sdk.request.ClearDataRequest
import com.bluefin.blueposgo.sdk.request.FullRefundRequest
import com.bluefin.blueposgo.sdk.request.InitRequest
import com.bluefin.blueposgo.sdk.request.PaymentRequest
import com.bluefin.blueposgo.sdk.request.PostProcessRequest
import com.bluefin.blueposgo.sdk.response.ClearDataResponse
import com.bluefin.blueposgo.sdk.response.DeviceCommandResponse
import com.bluefin.blueposgo.sdk.response.InitResponse
import com.bluefin.blueposgo.sdk.response.PaymentResponse
import com.bluefin.testaidlgo.data.REFUND
import com.bluefin.testaidlgo.data.createCaptureRequest
import com.bluefin.testaidlgo.data.createClearDataRequest
import com.bluefin.testaidlgo.data.createFullRefundRequest
import com.bluefin.testaidlgo.data.createInitRequest
import com.bluefin.testaidlgo.data.createPaymentRequest
import com.bluefin.testaidlgo.data.createRefundRequest
import com.bluefin.testaidlgo.ui.MainScreen
import com.bluefin.testaidlgo.ui.events.MainScreenEvents
import com.bluefin.testaidlgo.ui.theme.TestAIDLTheme

Usage

🚧

Note

Note that all of the following examples should be put together within a single MainActivity class.


To communicate with BluePOS Go, bind to its AIDL service from your Activity.

In other words, the entire communication with BluePOS Go revolves around the MainActivity that binds to the AIDL service.

Essential MainActivity Setup

This activity is created to send a sample payment to BluePOS and get the result.

class MainActivity : ComponentActivity() {

    private var service: PaymentServiceAIDL? = null

    private val connection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
            service = PaymentServiceAIDL.Stub.asInterface(binder)
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            service = null
        }
    }

    override fun onStart() {
        super.onStart()

        val intent = Intent(PAYMENT_SERVICE_ACTION)
        intent.setPackage(BLUEPOS_GO_PACKAGE)

        bindService(intent, connection, BIND_AUTO_CREATE)
    }

    override fun onStop() {
        super.onStop()

        unbindService(connection)
        service = null
    }
}

After onServiceConnected , the onStart must be performed via Intent to bind the service.


BluePOS Go's payment work lives in PaymentServiceAIDL. Do not rely on that service to open BLUEPOS_GO_ACTIVITY itself.

Background activity starts are restricted according to app visibility, OS version, target SDK level, and any applicable exception. Those restrictions are not a single Android 14 rule. Use the supplied explicit activity handoff from a merchant screen that is already visible, and verify that handoff on the OS and target SDK combination you intend to ship.


That is why startThroughService does both steps.


paymentCallback

The next step is to set up the paymentCallback that receives the responses from BluePOS Go App. This is a set of callbacks that ensure the responses from the established PaymentServiceAIDL connection.

class MainActivity : ComponentActivity() {

    private var service: PaymentServiceAIDL? = null
    private var statusText by mutableStateOf("Ready")
    private var debugMode by mutableStateOf(true)
    private var transList by mutableStateOf(listOf<PaymentResponse?>())
    private var refundsList by mutableStateOf(listOf<PaymentResponse>())

    private val paymentCallback = object : PaymentCallback.Stub() {
        override fun onPaymentResult(response: PaymentResponse) {
            runOnUiThread {
                Log.d("Receive result", response.toString())
                statusText = formatPaymentResponse(response)
            }
        }

        override fun onClearDataResult(response: ClearDataResponse?) {
            runOnUiThread {
                Log.d("Receive result", response.toString())
                statusText = response?.let { formatClearDataResponse(it) } ?: "Unknown result"
            }
        }

        @Suppress("UNCHECKED_CAST")
        override fun onTransactionsListResponse(list: List<PaymentResponse?>?) {
            statusText = "List received"
            list?.let { lst ->
                transList = lst
                refundsList = lst.filter { it?.type == REFUND } as List<PaymentResponse>
            }
        }

        override fun onInitResult(response: InitResponse?) {
            runOnUiThread {
                statusText = response?.status ?: ""
            }
        }

        override fun onRebootResult(response: InitResponse?) {
            statusText = response?.status ?: ""
        }

        override fun onError(message: String?) {
            runOnUiThread {
                statusText = "Payment error: $message"
            }
        }

        override fun onDeviceCommandResult(response: DeviceCommandResponse?) {
            runOnUiThread {
                statusText = "Device command ${response?.command} result: ${response?.status}"
            }
        }
    }
}

startThroughService and startActivity


startThroughService sends a payment action to BluePOS Go and then opens the BluePOS Go screen by starting the activity via openBluePosGoPayment.
It checks that the service is connected. It sends the request, such as a payment, refund, or init, to the BluePOS Go service. If the service accepts it, the function opens the BluePOS Go app so the merchant can finish the action on the reader. The result comes back later through the callback.

If the service is missing, busy, or not initialized, it stops and shows an error instead of opening the app.

class MainActivity : ComponentActivity() {
    // ...    
    
    private fun startThroughService(
        command: String,
        request: Any? = null
    ) {
        if (service == null) {
            statusText = UNKNOWN_COMMAND
            return
        }

        try {
            var serviceResult: Boolean? = false
            service?.debugMode = debugMode
            if (service?.isInitialized != true && command != EXTRA_INIT) {
                statusText = getString(R.string.not_initialized)
                return
            }
            when (command) {
                EXTRA_PAYMENT_REQUEST -> request?.let {
                    serviceResult = service?.payment(request as PaymentRequest, paymentCallback)
                } ?: { statusText = UNKNOWN_COMMAND }
                EXTRA_CLEAR_DATA_REQUEST -> request?.let {
                    serviceResult = service?.clearDataRead(request as ClearDataRequest, paymentCallback)
                } ?: { statusText = UNKNOWN_COMMAND }
                EXTRA_FULL_REFUND_REQUEST -> request?.let {
                    serviceResult = service?.fullRefund(request as FullRefundRequest, paymentCallback)
                } ?: { statusText = UNKNOWN_COMMAND }
                EXTRA_REFUND_REQUEST -> request?.let {
                    serviceResult = service?.refund(request as PostProcessRequest, paymentCallback)
                } ?: { statusText = UNKNOWN_COMMAND }
                EXTRA_CAPTURE_REQUEST -> request?.let {
                    serviceResult = service?.capture(request as PostProcessRequest, paymentCallback)
                } ?: { statusText = UNKNOWN_COMMAND }
                EXTRA_INIT -> request?.let {
                    serviceResult = service?.init(request as InitRequest, paymentCallback)
                } ?: { statusText = UNKNOWN_COMMAND }
                EXTRA_TR_LIST_REQUEST ->
                    serviceResult = service?.getTransactionsListResponse(paymentCallback)

                EXTRA_REBOOT -> serviceResult = service?.reboot(paymentCallback)

                EXTRA_CONNECT -> serviceResult = service?.connectDevice(paymentCallback)

                EXTRA_DISCONNECT -> serviceResult = service?.disconnectDevice(paymentCallback)

                EXTRA_FORGET -> serviceResult = service?.forgetDevice(paymentCallback)

                else -> statusText = UNKNOWN_COMMAND
            }
            if (serviceResult == true) {
                openBluePosGoPayment(command, request?.let { it as Parcelable })
                statusText = "Payment started. Waiting for callback..."
            } else
                statusText = "Service is busy or not started"
        } catch (e: RemoteException) {
            statusText = "PaymentService error: ${e.message}"
        } catch (_: ActivityNotFoundException) {
            statusText = "BluePOS Go activity was not found"
        }
    }
    
    private fun openBluePosGoPayment(
        command: String,
        request: Parcelable?
    ) {
        val intent = Intent(ACTION_EXTERNAL)
            .setClassName(BLUEPOS_GO_PACKAGE, BLUEPOS_GO_ACTIVITY)
            .putExtra(EXTRA_COMMAND, command)
            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)

        request?.let {
            intent.putExtra(EXTRA_PAYLOAD, request)
        }

        startActivity(intent)
    }
}

Initialize BluePOS Go

Initialize the BluePOS Go application before attempting a payment.

val request = createInitRequest()
startThroughService(EXTRA_INIT, request)

Making Payment

private fun processAction(
    event: MainScreenEvents,
    updateStatus: (String) -> Unit,
    updateChecked: (Boolean) -> Unit
) {
  // ...
  val request = createPaymentRequest(event.amount)
  startThroughService(EXTRA_PAYMENT_REQUEST, request)
  // ...
}

How It Works

  1. Bind to the BluePOS AIDL service using an explicit intent.
  2. Obtain an instance of PaymentServiceAIDL.
  3. Call service methods to:
    • Process payments
    • Perform refunds and authorizations
    • Retrieve transactions
  4. Create intent and call activity
  5. Handle result in callback, passed to service

API Authentication and Building BasicToken

The sample below is taken from the sample application. It shows how to authenticate with the API before you build a payment request.

To build your basic token for your SDK instance, please create your respective API key under your PayConex account ID and use the following code to generate the basic authentication. For more details, refer to API Authentication | Basic Authentication.

The example below also demonstrates all the payment helpers you will need in practice in order to process sale, auth, refunds, captures, and other operations.

🚧

Scope Definitions

Depending on the operation you are running on your device, make sure to add the corresponding scope permissions to your API key being used.

For all the scope permissions, please refer to API Key Management | Scope Definitions.

import android.util.Base64
import com.bluefin.blueposgo.sdk.request.ClearDataRequest
import com.bluefin.blueposgo.sdk.request.FullRefundRequest
import com.bluefin.blueposgo.sdk.request.InitRequest
import com.bluefin.blueposgo.sdk.request.PaymentRequest
import com.bluefin.blueposgo.sdk.request.PostProcessRequest
import com.bluefin.blueposgo.sdk.request.PostProcessType
import com.bluefin.testaidlgo.BuildConfig
import java.math.RoundingMode

const val REFUND = "refund"
const val AUTHORIZED = "AUTHORIZED"

private data class PaymentConfig(
    val accountId: String,
    val apiKey: String,
    val apiSecret: String,
    val environment: String
)

private fun requirePaymentConfig(): PaymentConfig {
    val config = PaymentConfig(
        accountId = BuildConfig.BLUEPOS_ACCOUNT_ID,
        apiKey = BuildConfig.BLUEPOS_API_KEY,
        apiSecret = BuildConfig.BLUEPOS_API_SECRET,
        environment = BuildConfig.BLUEPOS_ENVIRONMENT
    )
    val missing = listOf(
        "BLUEPOS_ACCOUNT_ID" to config.accountId,
        "BLUEPOS_API_KEY" to config.apiKey,
        "BLUEPOS_API_SECRET" to config.apiSecret,
        "BLUEPOS_ENVIRONMENT" to config.environment
    ).filter { (_, value) -> value.isBlank() }
        .joinToString { (name, _) -> name }

    check(missing.isEmpty()) {
        "Missing payment configuration: $missing. Add values to payment.properties, Gradle properties, or environment variables."
    }

    return config
}

private fun buildBasicToken(apiKey: String, apiSecret: String): String {
    val credentials = "$apiKey:$apiSecret"
    val encoded = Base64.encodeToString(
        credentials.toByteArray(Charsets.UTF_8),
        Base64.NO_WRAP
    )
    return "Basic $encoded"
}

fun createPaymentRequest(amount: Long, tip: Long): PaymentRequest {
    val config = requirePaymentConfig()

    return PaymentRequest(
        amount = amount.toDouble() / 100,
        tip = tip.toDouble() / 100,
        type = "sale",
        notes = "External application", // not really needed
        accountId = config.accountId,
        basicToken = buildBasicToken(config.apiKey, config.apiSecret),
        environment = config.environment
    )
}

fun createRefundRequest(transactionId: String, amount: Long): PostProcessRequest {
    return PostProcessRequest(
        amount = amount.toDouble() / 100,
        transactionId = transactionId,
        type = PostProcessType.REFUND
    )
}

fun createCaptureRequest(transactionId: String, amount: Long): PostProcessRequest {
    return PostProcessRequest(
        amount = amount.toDouble() / 100,
        transactionId = transactionId,
        type = PostProcessType.CAPTURE
    )
}

fun createClearDataRequest(amount: Long): ClearDataRequest {
    return ClearDataRequest(
        amount = amount.toDouble() / 100,
        timeOut = 60
    )
}
fun createFullRefundRequest(id: String): FullRefundRequest {
    return FullRefundRequest(
        transactionId = id
    )
}


fun createInitRequest(): InitRequest {
    val config = requirePaymentConfig()

    return InitRequest(
        accountId = config.accountId,
        basicToken = buildBasicToken(config.apiKey, config.apiSecret),
        environment = config.environment
    )
}

fun prepareAmountNumber(amountText: String): Long = amountText
    .toBigDecimalOrNull()
    ?.setScale(2, RoundingMode.DOWN)
    ?.movePointRight(2)
    ?.toLong() ?: 0

All of the device operations are based on the EXTRA_* enumerations based on the operation.

Initialize BluePOS Go

Initialize before attempting a payment.

val request = createInitRequest()
startThroughService(EXTRA_INIT, request)


Sale With Tip

  val request = createPaymentRequest(event.amount, event.tip)
  startThroughService(EXTRA_PAYMENT_REQUEST, request)

Authorization

val request = createPaymentRequest(event.amount, event.tip)
request.type = "auth"
startThroughService(EXTRA_PAYMENT_REQUEST, request)

Save Card

val request = createPaymentRequest(0, 0)
request.type = "save"
startThroughService(EXTRA_PAYMENT_REQUEST, request)

Clear Data


val request = createClearDataRequest(event.amount)
startThroughService(EXTRA_CLEAR_DATA_REQUEST, request)

Capture Transaction

val request = createCaptureRequest(event.transactionId, event.amount)
startThroughService(EXTRA_CAPTURE_REQUEST, request)

Partial Refund

val request = createRefundRequest(event.transactionId, event.amount)
startThroughService(EXTRA_REFUND_REQUEST, request)


Full Refund

val request = createFullRefundRequest(event.id)
startThroughService(EXTRA_FULL_REFUND_REQUEST, request)

Did this page help you?