Zeal Communicator SDK
Hook into the Zeal payment flow on Android terminals — discounts, rewards, eReceipts, refunds — through the typed Kotlin Communicator SDK.
The Third-Party API is a service provided by Zeal to support a Standalone Payment Application on Android terminals during the payment flow. This guide specifies how a payment application integrates with the Standalone Zeal application.
How it works. The Third-Party API uses Android broadcast messages routed through manifest-driven receivers — there are no REST endpoints to call from outside the device.
Every card event identifies the card by its Card Fingerprint (loyalty_token), a unique card identifier
your payment app generates at card read. Read Card fingerprint before you start.
Installation
Step 1 · Add the Maven repository
Add the following Maven repository to dependencyResolutionManagement in settings.gradle:
dependencyResolutionManagement {
repositories {
maven {
url = uri("https://maven.pkg.github.com/zeal-io/Zeal-POS-App-Communicator-SDK")
credentials {
username = "*USERNAME*"
password = "*PASSWORD*"
}
}
}
}Step 2 · Add the dependency
dependencies {
// ...
implementation "com.zeal.zealmodule:zeal_communicator_sdk:1.2.28"
}Step 3 · Initialize the Zeal app
Call this method at the beginning of the app lifecycle, or before starting the payment flow, to initialize the Zeal app with your merchant data:
FlowHandler() .setTerminalInfo(context, "serialNumber", "terminalId", "merchantId")
Shared setup
Every action below uses the same preamble: import the SDK, create a FlowHandler, and pick a
context plus transaction type.
import com.zeal.zeal_communicator_sdk.* val flowHandler = FlowHandler() val context: Context = this // replace with your actual context val transactionType: TransactionTypes = TransactionTypes.Sale // or Void / Refund
Available transaction actions
digitalReceipt on FlowHandler with DigitalReceiptRequest.Transaction types
The TransactionTypes enum defines the available transaction kinds:
enum class TransactionTypes {
Sale,
Void,
Refund,
PreAuth,
Completion
}tran_status values
Every response carries a tran_status describing the redemption outcome:
tran_status {
FULLY_COVERED, // discount covers the entire transaction amount
NEW_AMOUNT, // discount partially covers the amount; remainder returned
SAME_AMOUNT, // no redemption occurred
ERROR, // error on Zeal or redemption side
CANCEL // skipped or chose not to redeem points
}Base response model
All response classes extend BaseResponse:
package com.zeal.zeal_communicator_sdk.communicationResponses
open class BaseResponse(
var third_party_request_type: String,
var tran_status: String,
var message: String
)Request and response models for each action are documented in the sale path and Refund and Reversal
sections below — AfterCardDetectedRequest, DigitalReceiptRequest, RefundTransactionRequest,
MarkAsReverseRequest, and their response counterparts.
Card fingerprint
loyalty_token carries the Card Fingerprint, also called the Unique Card Identifier. It is the value
that tells Zeal which card was used: your payment app sends it with every card event (afterCardDetected,
digitalReceipt and refund), and Zeal recognises a returning customer by it.
Your payment app produces the card fingerprint at card read, inside its own secure card processing, in one of two ways:
- Keyed hash — an HMAC-SHA-256 of the full PAN, with a key you hold.
- Vault lookup — a tokenization vault that returns the same identifier every time it sees the same card, so the fingerprint isn’t derived from the PAN itself.
The card number (PAN) is never sent to Zeal. The BIN (cardBin) and last four digits (masked_pan) you also send
are not used for identity.
The card fingerprint must meet three requirements:
If your payment app already produces a stable per-card hash for its own purposes, such as a PAN hash exposed by your terminal SDK, you can send that value as the card fingerprint, provided it meets all three requirements.
Zeal doesn’t prescribe how you generate it. It prescribes stability and irreversibility.
Can’t generate a card fingerprint that meets these requirements? Contact Zeal before you build the integration.
AfterCardDetected
Triggered after card entry and before card verification. Run this step first in the sale path, then continue with Digital Receipt.
Send an action
Build an AfterCardDetectedRequest, with the Card fingerprint in loyalty_token, and
subscribe on FlowHandler:
val request = AfterCardDetectedRequest( loyalty_token = "HhYDMww7aJgcdyJPU6QVWpFAZPNRhSzkqNpErHM3hikU", // card fingerprint, never the PAN currency_code = "818", decimal_shift = "2", amount = "100.00", phone_number = "", masked_pan = "1111", cardBin = "411111", par = "V0010013000000000000000000001" // optional ) flowHandler.afterCardDetected( context, transactionType, request ).subscribe({ response -> // AfterCardDetectedResponse — check tran_status, amount, transaction_id }) { throwable -> if (throwable is NotRegisteredFlowException) { // target application did not register an action for this step } else { // other exception } }
Request and response models
package com.zeal.zeal_communicator_sdk.communicationRequests class AfterCardDetectedRequest( var loyalty_token: String, // card fingerprint, never the card number var currency_code: String, var decimal_shift: String, // optional var amount: String, // in double format var phone_number: String = "", var masked_pan: String = "", // last 4 digits of card number var cardBin: String = "", // first 6 or 8 digits of card number var par: String? = null // optional — EMV Payment Account Reference )
package com.zeal.zeal_communicator_sdk.communicationResponses class AfterCardDetectedResponse( tran_status: String, // FULLY_COVERED, NEW_AMOUNT, SAME_AMOUNT, ERROR, CANCEL var transaction_id: String? = null, // redemption id needed to complete redemption at the end third_party_request_type: String, var decimal_shift: String, var amount: String, var currency_code: String ) : BaseResponse(third_party_request_type, tran_status)
Digital Receipt
Triggered after the transaction is approved and before the receipt is printed. Call this after
AfterCardDetected in the sale path. The wire action is eReceipt; the SDK
method and models use digitalReceipt / DigitalReceipt*. Send the same
Card fingerprint in loyalty_token as in AfterCardDetected.
Send an action
val request = DigitalReceiptRequest( currency_code = "818", decimal_shift = "2", amount = "100.00", entry_mode = "CHIP", card_brand = "VISA", card_type = "CREDIT", customer_receipt_data = "...", merchant_receipt_data = "...", loyalty_token = "HhYDMww7aJgcdyJPU6QVWpFAZPNRhSzkqNpErHM3hikU", // card fingerprint, never the PAN third_party_approval_code = "123456", third_party_tran_date = "20260422", third_party_tran_time = "143055", phone_number = "", par = "V0010013000000000000000000001" // optional ) flowHandler.digitalReceipt( context, transactionType, request ).subscribe({ response -> // DigitalReceiptResponse — payment_reference for later reverse calls }) { throwable -> if (throwable is NotRegisteredFlowException) { // target application did not register an action for this step } else { // other exception } }
Request and response models
package com.zeal.zeal_communicator_sdk.communicationRequests class DigitalReceiptRequest( var currency_code: String, var decimal_shift: String, var amount: String, var entry_mode: String, var card_brand: String, var card_type: String, var customer_receipt_data: String, var merchant_receipt_data: String, var loyalty_token: String, // card fingerprint, never the card number var third_party_approval_code: String, var third_party_tran_date: String, var third_party_tran_time: String, var phone_number: String = "", var par: String? = null // optional — EMV Payment Account Reference )
package com.zeal.zeal_communicator_sdk.communicationResponses
class DigitalReceiptResponse(
tran_status: String,
third_party_request_type: String,
var payment_reference: String? = null
) : BaseResponse(third_party_request_type, tran_status)Refund and Reversal
Refund and reverse are separate from the sale path. Use them when undoing benefits from a completed sale — not as the next step after Digital Receipt.
Refund
Triggered when a refund is performed. Mark the transaction as refunded and reverse benefits from
the original sale; link with an optional transaction_id or process standalone.
Send an action
val request = RefundTransactionRequest( transaction_id = "sale-txn-id", // optional — link to the original sale currency_code = "818", decimal_shift = "2", amount = "100.00", card_brand = "VISA", card_type = "CREDIT", loyalty_token = "HhYDMww7aJgcdyJPU6QVWpFAZPNRhSzkqNpErHM3hikU", // card fingerprint, never the PAN masked_pan = "1111", card_expiration_date = "1230", auth_code = "123456", par = "V0010013000000000000000000001" // optional ) flowHandler.refund( context, TransactionTypes.Refund, request ).subscribe({ response -> // RefundTransactionResponse }) { throwable -> if (throwable is NotRegisteredFlowException) { // target application did not register an action for this step } else { // other exception } }
Request and response models
package com.zeal.zeal_communicator_sdk.communicationRequests class RefundTransactionRequest( val transaction_id: String? = null, // original sale transaction id (optional) val currency_code: String, val decimal_shift: String, val amount: String, val card_brand: String, val card_type: String, val loyalty_token: String, // card fingerprint, never the card number val masked_pan: String? = null, val card_expiration_date: String? = null, val auth_code: String? = null, val par: String? = null // optional — EMV Payment Account Reference )
package com.zeal.zeal_communicator_sdk.communicationResponses class RefundTransactionResponse( tran_status: String, third_party_request_type: String, message: String = "" ) : BaseResponse(third_party_request_type, tran_status, message)
Reversal
Triggered when a completed sale must be reversed. Pass the payment_reference from
Digital Receipt to undo associated points, rewards, or vouchers.
Send an action
val request = MarkAsReverseRequest( payment_reference = "payment-ref-from-digital-receipt", reversal_payment_reference = "", // optional par = "V0010013000000000000000000001" // optional ) flowHandler.reverse( context, TransactionTypes.Void, request ).subscribe({ response -> // MarkAsReverseResponse }) { throwable -> if (throwable is NotRegisteredFlowException) { // target application did not register an action for this step } else { // other exception } }
Request and response models
package com.zeal.zeal_communicator_sdk.communicationRequests class MarkAsReverseRequest( var payment_reference: String, // original sale payment reference/id var reversal_payment_reference: String = "", // optional var par: String? = null // optional — EMV Payment Account Reference )
package com.zeal.zeal_communicator_sdk.communicationResponses class MarkAsReverseResponse( tran_status: String, third_party_request_type: String, message: String = "" ) : BaseResponse(third_party_request_type, tran_status, message)