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

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

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 after the transaction is approved 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
}

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