ZealDeveloper Hub Beta
EN ES
App-to-App Integration · Android

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

afterAmountEnteraction
Triggered before card detection. The third-party app can handle the transaction with alternative methods other than credit/debit cards.
afterCardDetectedaction
Triggered after card entry and before card verification. Provides total amount, currency, and the first 6 digits of the PAN — useful for discounts or custom validation.
eReceiptaction
Triggered once the payment result is known and before the receipt is printed. Use digitalReceipt on FlowHandler with DigitalReceiptRequest.
afterTransactionaction
Triggered after the receipt is processed and before returning to the idle screen. Use for surveys, phone-number capture, or loyalty enrolment.
reverseaction
Triggered when a completed sale must be reversed. Mark the original transaction as reversed and undo any associated points, rewards, or vouchers.
refundaction
Triggered when a refund is performed. Mark the transaction as refunded and reverse any benefits granted by the original sale; can be linked to a previous sale or processed standalone.

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:

1 · Stablerequirement
The same physical card always produces the same fingerprint across purchases, terminals and merchants.
2 · Irreversiblerequirement
Nobody without your key or vault can recover the PAN from it. The key or vault stays with you and is never shared with Zeal.
3 · Fixed for the integrationrequirement
Keep the same key or vault for the life of the integration. Changing it changes every fingerprint and orphans every enrolled card.

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.

Successstatus
Zeal finishes the redemption and continues the normal receipt flow.
Declinedstatus
Zeal cancels the redemption and records the transaction as declined. No Zeal screen is shown; the response tran_status is Declined.
Cancelledstatus
Zeal cancels the redemption and records the transaction as cancelled. No Zeal screen is shown; the response 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.

1
Transaction date and time
DigitalReceiptRequest.third_party_tran_date / third_party_tran_time
2
Card brand and last 4 digits
DigitalReceiptRequest.card_brand · AfterCardDetectedRequest.masked_pan
3
Original amount
AfterCardDetectedRequest.amount = "15.00"
4
Zeal reward
Request amount − response amount, when tran_status is NEW_AMOUNT
5
Charged total
AfterCardDetectedResponse.amount = "14.00", sent again as DigitalReceiptRequest.amount

Send 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)