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
The SDK is served from Zeal’s private Maven repository. Zeal sends you a Maven username and token once your access is approved. The token does not expire; it stays valid until Zeal revokes it.
Keep the credentials out of your project and Git. Put them in ~/.gradle/gradle.properties on each
developer machine:
zealMavenUser=YOUR_USERNAME zealMavenToken=YOUR_TOKEN
On CI, supply them as the ZEAL_MAVEN_USER and ZEAL_MAVEN_TOKEN environment variables instead.
Then add the repository to dependencyResolutionManagement in your settings file:
import org.gradle.authentication.http.BasicAuthentication dependencyResolutionManagement { repositories { google() mavenCentral() exclusiveContent { forRepository { maven { name = "ZealPrivateMaven" url = uri("https://onetimesecret.zeal-members.com/maven/") credentials { username = providers.gradleProperty("zealMavenUser") .orElse(providers.environmentVariable("ZEAL_MAVEN_USER")).get() password = providers.gradleProperty("zealMavenToken") .orElse(providers.environmentVariable("ZEAL_MAVEN_TOKEN")).get() } authentication { create<BasicAuthentication>("basic") } } } filter { includeGroup("com.zeal.zealmodule") } } } }
Why BasicAuthentication? It makes Gradle send your credentials with the first request
instead of waiting for a 401 challenge, which saves a round trip for every file it downloads.
A 401 Unauthorized during resolution means the username or token is wrong or has been revoked.
exclusiveContent makes Gradle look up only com.zeal.zealmodule in the private repository, so your
token is never sent to other repositories and public dependencies still come from Google and Maven Central.
Already installed from GitHub Packages? The versions you already use keep resolving from there, so nothing breaks. To switch to the private repository, replace only the repository block above; the dependency line stays exactly the same.
Step 2 · Add the dependency
dependencies {
// ...
implementation "com.zeal.zealmodule:zeal_communicator_sdk:1.2.32"
}Use an exact version. Dynamic versions such as 1.2.+ or latest.release do not resolve, because
the repository publishes fixed, immutable versions only.
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
Declined, // Digital Receipt sent with status = Declined
Cancelled // Digital Receipt sent with status = Cancelled
}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 once the payment result is known 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 status = DigitalReceiptStatus.Success // optional — Success, Declined or Cancelled ) 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 var status: DigitalReceiptStatus? = null // optional — payment outcome ) enum class DigitalReceiptStatus { Success, Declined, Cancelled }
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)Transaction status
Since 1.2.32, status tells Zeal how the payment ended. It is optional.
tran_status is Declined.tran_status is Cancelled.Leave status as null and Zeal decides from third_party_approval_code (00 or 000 means
approved), as in earlier versions.
Receipt example
A $15.00 sale where Zeal applies a $1.00 reward. AfterCardDetected returns
NEW_AMOUNT with the new total, the payment app charges that total, and then sends
Digital Receipt. Each numbered line on the cardholder copy comes from a field
in those two calls.
and reward your customers
DigitalReceiptRequest.third_party_tran_date / third_party_tran_timeDigitalReceiptRequest.card_brand · AfterCardDetectedRequest.masked_panAfterCardDetectedRequest.amount = "15.00"amount − response amount, when tran_status is NEW_AMOUNTAfterCardDetectedResponse.amount = "14.00", sent again as DigitalReceiptRequest.amountSend the full printed text to Zeal in customer_receipt_data.
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)