Kiosk

Integrating External Apps with the Ecart Pay Kiosk

An external developer can integrate an Android app with the Ecartpay SK700 kiosk to charge a card and to use the printer, scanner, and camera, while keeping their own UI, login, and endpoints. The kiosk handles each request and returns the result to the app.

Your app and the kiosk are installed on the same SK700. Your app sends the request. The kiosk handles it and responds. Communication is an Android Intent with extras. There is no network call, socket, or pairing step.

SK700 (Android)
Your APK

Your UI, your login, your endpoints

UnifiedAPI.jar
Used to charge and print
Intent
Ecartpay Kiosk

Operates the hardware and runs the charge

(charge) over Nebula
Embedded IM30

The only device that reads the card

setResult
your Activity.onActivityResult(...)

Receives result and status callback

Two mechanisms are used:

OperationHow to invoke it
Charge, PrintWith the UnifiedAPI jar, through transApi.startTrans(...)
Scan, PhotoWith a dedicated Intent and startActivityForResult(...), with no library

The PAX Open SDK has no messages for reading a code or taking a photo. Its scan messages are payment methods, not a read operation. Scan and photo use a contract defined by the kiosk

The contract always returns a result, on success and on error.

⚠️

IMPORTANT

Charge and print require UnifiedAPI-2.00.00_20240730.jar, which you request from the Ecartpay team. The embedded IM30 must be linked to the merchant account before a charge can succeed. Camera permission is granted on the SK700 by the operator. Your app cannot grant it.

Prerequisites

  1. An SK700 with the Ecartpay Kiosk app installed (com.pax.ecartpay.kiosk).
  2. To charge: the embedded IM30 must be linked to the merchant account. If it is not, the charge returns The payment terminal has no account linked and the kiosk shows the linking QR. Request this from the Ecartpay team. It cannot be completed from your app.
  3. For the camera: the first time, the operator must grant the permission on the SK700. Your app cannot grant it.
  4. The UnifiedAPI-2.00.00_20240730.jar file, requested from the Ecartpay team. Place it in app/libs/ and declare it:
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
  1. Declare the three actions in your manifest. From targetSdk 30, Android filters which other apps yours can see. Without this, resolveActivity() returns null even when the kiosk is installed. startTrans checks that before launching, so it returns false with no obvious cause. The SK700 runs Android 10 (API 29), where this filter does not apply yet. A newer device does require it:
<queries>
    <intent>
        <action android:name="android.pax.payment.entry" />
    </intent>
    <intent>
        <action android:name="com.pax.ecartpay.kiosk.action.SCAN_CODE" />
    </intent>
    <intent>
        <action android:name="com.pax.ecartpay.kiosk.action.CAPTURE_PHOTO" />
    </intent>
</queries>
  1. Nothing else to declare. Your app does not need permissions for any of the four operations. The kiosk operates the hardware with its own permissions. minSdk 24 is enough.

Charge

private val transApi = TransAPIFactory.createTransAPI()
fun charge(amountInCents: Long) {
    val request = SaleMsg.Request().apply {
        amount = amountInCents              // 1500 = $15.00
        currencyCode = "MXN"                // optional, MXN by default
        tipAmount = 0
        packageName = "com.pax.ecartpay.kiosk"
    }
    // The context must be an Activity: the SDK uses it for startActivityForResult.
    if (!transApi.startTrans(this, request)) {
        // The kiosk is not installed, or the intent could not be resolved
    }
}

The response arrives in onActivityResult:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    val response = transApi.onResult(requestCode, resultCode, data) ?: return
    if (response.rspCode == SdkRspCode.SUCC) {
        // approved: response.authCode, refNo, cardNo, amount, voucherNo…
        val extras = response.extraBundle
        val customerVoucher = extras?.getString("clientVoucher")
        val merchantVoucher = extras?.getString("merchantVoucher")
        val orderId = extras?.getString("orderId")
    } else {
        // declined or interrupted: response.rspMsg carries the reason, in English
    }
}

rspCode == SUCC is the proof of success. No other field needs to be inspected.

Values returned in extraBundle

KeyContents
clientVoucherCustomer receipt, ready to print
merchantVoucherMerchant receipt
amountDisplayFormatted amount, for example $15.00 MXN
orderIdOrder identifier
enterModeHow the card was read
terminalCodeFailures only: the original terminal code
terminalMessageFailures only: the terminal text, in the language shown to its operator. Use it when you want a local operator to see the same message that appeared on the reader screen
Store the voucher if you will offer printing. That value is what you send to the kiosk in the next step.

Print

Send what you want to print, not a file path. Two forms are supported:

fun printReceipt(voucher: String) {
    val request = PrintBitmapMsg.Request().apply {
        bitmap = voucher                          // the voucher exactly as it arrived from the charge
        packageName = "com.pax.ecartpay.kiosk"
    }
    transApi.startTrans(this, request)
}
What you send in bitmapWhat the kiosk does
The clientVoucher or merchantVoucher from a chargeRenders it and prints it. Do not modify it. The kiosk resolves the format and the paper width
A Base64 imagePrints it as received
Plain text is not supported yet. Content that has neither voucher tags nor Base64 is returned as an invalid payload. If your use case needs plain text, request it from the Ecartpay team.
The response travels the same way as a charge: rspCode == SUCC when the paper comes out. Otherwise rspMsg says whether there is no printer or the content was not valid.
❗️

Keep the size in mind. Everything travels inside the Intent, and Android limits the transaction to about 1 MB. A receipt is tens of KB. A full camera image does not fit.

Scan a Code

No library is required. The kiosk opens the laser scanner and returns what it reads:

private val SCAN_REQUEST = 200
fun scan() {
    val scan = Intent("com.pax.ecartpay.kiosk.action.SCAN_CODE")
    if (packageManager.resolveActivity(scan, 0) == null) {
        // the kiosk is not installed
        return
    }
    startActivityForResult(scan, SCAN_REQUEST)
}
// in onActivityResult, when requestCode == SCAN_REQUEST
val scanned = data?.getStringExtra("com.pax.ecartpay.kiosk.extra.RESULT")
val error = data?.getStringExtra("com.pax.ecartpay.kiosk.extra.ERROR")
when {
    resultCode == RESULT_OK && scanned != null -> { /* use the contents */ }
    else -> { /* error explains why: no scanner, or nothing was read */ }
}

Possible reasons are This terminal has no scanner and No code was read.
It always responds, even when it reads nothing. After 7 seconds it returns RESULT_CANCELED with the reason. Your app never stays waiting.

Capture a Photo

The photo does not travel in the Intent. It does not fit. The kiosk stores it and gives you a content:// URI you can read, plus a thumbnail for an immediate preview.

private val CAMERA_REQUEST = 300
fun capture() {
    val capture = Intent("com.pax.ecartpay.kiosk.action.CAPTURE_PHOTO")
    startActivityForResult(capture, CAMERA_REQUEST)
}
// in onActivityResult, when requestCode == CAMERA_REQUEST
val uri = data?.getParcelableExtra<Uri>("com.pax.ecartpay.kiosk.extra.PHOTO_URI")
val thumbnail = data?.getParcelableExtra<Bitmap>("com.pax.ecartpay.kiosk.extra.THUMBNAIL")
val error = data?.getStringExtra("com.pax.ecartpay.kiosk.extra.ERROR")
if (resultCode == RESULT_OK && uri != null) {
    imageView.setImageBitmap(thumbnail)                        // immediate preview
    val photo = contentResolver.openInputStream(uri)?.use {    // the full photo
        BitmapFactory.decodeStream(it)
    }
}

Possible reasons are Camera permission was denied, The photo could not be taken, and The photo could not be stored.

❗️

The read permission lasts only as long as your app's task. If you store the URI and open it again the next day, you receive a SecurityException. The file lives in the kiosk until the next capture, which replaces it. If you need to keep the photo, copy it when you receive it.

Response Codes

For charge and print, read response.rspCode:

CoderspMsg
SUCC (0)OK
ERR_TRANS_CANCEL (-2)Cancelled on the payment terminal
ERR_TRANS_TIMEOUT (-1)The payment terminal did not answer in time
ERR_CONNECT_MPOS (-4)The payment terminal is not connected
ERR_TRANS_FAIL (-3)The payment terminal has no account linked, The payment terminal has no internet connection, or The charge was declined
On print, ERR_TRANS_FAIL can also carry Printer error: <code>, Payload is neither a voucher nor Base64, or Operation not supported.
When the decline originates on the payment terminal, its original text — in the language its operator sees — travels separately in the terminalMessage extra, together with its code in terminalCode.

Contract Rules

  • Every call returns a result. No operation stays silent. If something fails, the reason arrives. If startTrans returns false, the intent was not resolved. That is almost always because the kiosk is not installed, or because the manifest is missing <queries>.
  • The kiosk can be closed. The call starts it on its own. You do not need to open it or leave it in the background.
  • The charge is performed by the IM30, not by the SK700. That is why some error messages say "the payment terminal": they refer to the embedded reader.
  • The kiosk renders the voucher. Send it unchanged. If you reformat it, the receipt prints incorrectly.
  • Always set packageName on Open SDK messages. That makes the kiosk handle the request instead of another payment app installed on the device.

What to Request from the Ecartpay Team

  1. The UnifiedAPI-2.00.00_20240730.jar file.
  2. The SK700 IM30 linked to the merchant account that will receive the charges.
  3. The Ecartpay Kiosk app installed on the device.

Did this page help you?