Transaction module
Call flow
To develop app using the
transactionFlowController
, it is recommended that you have knowledge or experience in EMV transaction flow.
Setup
Before starting a transaction, you should create an instance and connect the app to the terminal by getControllerInstance()
and connectController()
.
The app shall be notified by onError
if there's any errors occurred.
abstract class ApolloTransactionFlow(private val context: Context) : TransactionFlowDelegate {
private var transactionFlowController: TransactionFlowController? = null
fun connect(): Boolean {
try {
if (transactionFlowController == null) {
// get instance
transactionFlowController = getControllerInstance(context, this)
// enable debug log display in console
SPDeviceController.enableDebugLog(true)
}
// establish the communication channel with secure module
transactionFlowController?.connectController()
} catch (ex: Exception) {
return false
}
return true
}
fun disconnect() {
transactionFlowController?.disconnectController()
transactionFlowController?.releaseControllerInstance()
}
override fun onError(errorType: ControllerError.Error?, message: String?) {
// app handle error here
}
}
EMV transaction flow
Start transaction
The app should put the amount, currency, check card mode and transaction sequence number (in case of refund or void) in the Hashtable
and call the startTransactionFlow
to start the EMV transaction.
fun startTransactionFlow(mode: BaseCardController.CheckCardMode, amount: String, amountCashback: String?, transactionSequenceNumber: String?, currency: Currency) {
val data = Hashtable<String, Any>()
// eg. BaseCardController.CheckCardMode.SWIPE_OR_INSERT_OR_TAP
data["checkCardMode"] = mode
// ISO 4217 currency code eg. 344 for HKD
data["currencyCode"] = currency.numericCode.toString()
// decimal string, eg. 2.00 for $2.00
data["amount"] = amount
data["transactionType"] = TransactionType.GOODS
// indicate the transaction sequence number for refund or void
if (transactionSequenceNumber != null) {
data["transactionNo"] = transactionSequenceNumber
}
transactionFlowController?.startTransactionFlow(data)
}
The card reader shall be turned on and start detecting any card in the selected CheckCardMode
. If no card interaction (tap, insert or swipe) is detected, the app shall receives onCardInteractionDetecting
.
override fun onCardInteractionDetecting(p0: BaseCardController.CheckCardMode?) {
// app handle the UI here
}
During the card detection phase, you can send abortDetection
to terminate the transaction. onTransactionStatusReceived
shall be returned with transaction status to the app.
fun abortDetection() {
transactionFlowController?.abortDetection()
}
override fun onTransactionStatusReceived(p0: TransactionResult?) {
// display transaction result
}
Application selection & read application data
If the card has more than one payment application, the app shall receive the onSelectAIDRequested
with an array of AIDs. The app should select one of the AID by selectAID()
.
override fun onSelectAIDRequested(p0: ArrayList<String>?) {
p0?.let {
val items = it.toTypedArray()
// display a dialog for user to select AID
MaterialAlertDialogBuilder(context)
.setTitle("Select Application")
.setItems(items) { dialog, item ->
transactionFlowController?.selectAID(item)
dialog.dismiss()
}
.setCancelable(false)
.show()
}
}
After selecting the AID, the terminal reads necessary data from the card.
Data authentication & processing restrictions
The terminal then proceeds to data authentication and restriction processing with the card. If either one failed or declined, the app shall receives onTransactionStatusReceived
with the status.
override fun onTransactionStatusReceived(p0: TransactionResult?) {
// display transaction result
}
Confirmation
After the previous step, the app shall receive onConfirmationRequested
. The app should prompt the user for confirmation and send back sendConfirmation(true)
to the terminal.
override fun onConfirmationRequested() {
transactionFlowController?.sendConfirmation(true)
}
Cardholder verification (PIN)
There are different cardholder verification method (CVM) in an EMV transaction, PIN is one of them.
If the transaction requires PIN entry, the app shall receives onPinEntryRequested
.
override fun onPinEntryRequested(p0: PinEntrySource?) {
// app display UI to prompt user to enter PIN
}
Terminal risk management
After cardholder verification, the transaction proceeds to the terminal risk management. This process takes place between the terminal and the card, if it fails, the app shall receive onTransactionStatusReceived
.
Terminal & card action analysis
During this phase, the terminal shall determine the next action based on the result of the previous processing steps.
Online processing & script processing
If the card decided to go online for authentication, the app shall receive onOnlineProcessingRequested
with the transaction data in a tag-length-value (TLV) format. The app should format the data in the acquirer/processor specified format e.g ISO8583, XML or JSON, and send to the acquirer/processor for authentication and wait for the response.
override fun onOnlineProcessRequested(p0: String?) {
// app handle online processing here and get the online reply TLV data
}
Once getting the response from the acquirer/processor, the app should send the response data back to the terminal by sendOnlineProcessingData(data)
.
var replyTlv: String = ""
// app return the online processing data to the terminal
transactionFlowController?.sendOnlineProcessingData(replyTlv)
In normal circumstances, the app shall receive the following response(s) from the acquirer/processor.
Tag | Parameter |
---|---|
8A | Authorization response code |
91 | Issuer authentication data |
71 | Issuer script template 1 |
72 | Issuer script template 2 |
Script processing
If there's any tag 71 or 72 in the responses from acquirer/processor, the terminal shall execute the script automatically once it receive the sendOnlineProcessingData()
from the app.
No response from processor
If the app cannot get any responses from the acquirer/processor due to any abnormal situation, the app SHOULD send a null
or an empty string to the terminal by sendOnlineProcessingData("")
to complete EMV transaction flow.
Refer to the EMV Book 3 - Application Specification, Annex A: Data Elements Dictionary for the details of the tags
Completion
After the previous steps, the app shall receive onBatchDataReceived
and onTransactionStatusReceived
with the final transaction result from the card.
The app should store the batch data and upload to the processor later for settlement if required.
override fun onBatchDataReceived(p0: String?) {
// store the batch data and upload to the processor later for settlement
}
override fun onTransactionStatusReceived(p0: TransactionResult?) {
// App handle UI display
}