EMV
Initial EMV
This variable isinitail before start emv.
open class EmvHelper(...) {
//Initail EMV Option
private val emvOption: EMVOption = EMVOption.create()
//Initail Card Option
private val cardOption: CardOption = CardOption.create()
//Initail EMV from DeviceHelper
private val emv: UEMV? = DeviceHelper.me().emv;
//Initail Pinpad
private val pinpad: UPinpad? = DeviceHelper.me()
.getPinpad(
// Call KAPID position
KAPId(0, 0),
// Choose Key system
KeySystem.KS_FIXED_KEY,
// Device name
DeviceName.IPP
).also {
// If initialize pinpad choose it
pinpad = it
}
private var lastCardRecord: CardRecord? = null
private var wholeTrkId = 0
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Initial Card Operation
This function is initial emv can read type of card from setting.
open class EmvHelper(...) {
...
private fun initCardOption() {
// Get data from Realms database
val dataSetting = getDataSetting()
//Set Whole data track 1
setTrkIdWithWholeData(false, TrackID.TRK1)
//Set Whole data track 2
setTrkIdWithWholeData(false, TrackID.TRK2)
//Set Whole data track 3
setTrkIdWithWholeData(false, TrackID.TRK2)
//Set card option to support IC (Contact) card by boolean
cardOption.supportICCard(<Boolean>)
//Set card option to support Magnetic card by boolean
cardOption.supportMagCard(<Boolean>)
//Set card option to support RF (Contactless) card by boolean
cardOption.supportRFCard(<Boolean>)
//Set card option to support All RF (Contactless, Suica etc.) card by boolean
cardOption.supportAllRFCardTypes(<Boolean>)
//Set card option to support RF (Contactless) card by boolean
cardOption.rfDeviceName(RFDeviceName.INNER)
//Set card option to check card of track by boolean
cardOption.trackCheckEnabled(<Boolean>)
}
// Set whole data by track id
private fun setTrkIdWithWholeData(isSlted: Boolean, trkId: Int) {
wholeTrkId = if (isSlted) {
wholeTrkId or trkId
} else {
wholeTrkId and trkId.inv()
}
cardOption.trkIdWithWholeData(wholeTrkId)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Pinpad
This function is initial pinpad before use card holder verification.
open class EMVHelper(...) {
...
private fun openPinpad() {
try {
// Call pinpad to initial open
pinpad!!.open()
} catch (e: Exception) {
...
}
}
private fun closePinpad() {
try {
// Call pinpad to close pinpad
pinpad!!.close()
} catch (e: Exception) {
...
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Kernel ID
This function isget kernel version.
open class EMVHelper(...) {
private fun getKernelVersion() {
try {
// Initai version to string value
val version = StringValue()
// get kernel version and return data
val ret = emv!!.getKernelVersion(version)
if (ret == EMVError.SUCCESS) {
...
} else {
...
}
} catch (e: RemoteException) {
...
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Checksum
This functionchecksum with flag.
open class EMVHelper(...) {
...
private fun getCheckSum() {
try {
val flag = 0xA2
val checkSum = StringValue()
val ret = emv!!.getCheckSum(flag, checkSum)
if (ret == EMVError.SUCCESS) {
...
} else {
...
}
} catch (e: RemoteException) {
...
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Halt
This function use with RFCard to retry tap again.
open class EMVHelper(...) {
private fun halt() {
try {
emv!!.halt()
} catch (e: java.lang.Exception) {
...
}
}
}
2
3
4
5
6
7
8
9
Start/Stop EMV
This function is start and stop emv.
open class EMVHelper(...) {
...
fun startEMV() {
try {
// Call function
initCardOption()
getKernelVersion()
getCheckSum()
// Start EMV with handler and emv options
emv!!.startEMV(emvOption.toBundle(), emvEventHandler)
// open pinpad initial when EMV start
openPinpad()
} catch (e: Exception) {
...
}
}
fun stopEMV() {
try {
emv!!.stopEMV()
emv!!.stopSearch()
emv!!.stopProcess()
closePinpad()
} catch (e: Exception) {
...
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
EMV Handler
This function is handle step of emv and automatic in flow of emv.
open class EMVHelper(...) {
private var emvEventHandler: EMVEventHandler = object : EMVEventHandler.Stub() {
// Initial EMV process
override fun onInitEMV() {
doInitEMV()
}
// Wait card operation (Magnetic, Contact and Contacless)
override fun onWaitCard(flag: Int) {
doWaitCard(flag)
}
override fun onCardChecked(cardType: Int) {
// Only happen when use startProcess()
...
}
// Select AID from contact and contacless
override fun onAppSelect(reSelect: Boolean, list: List<CandidateAID>) {
doAppSelect(reSelect, list)
}
// Choose One AID from contact and contacless
override fun onFinalSelect(finalData: FinalData) {
doFinalSelect(finalData)
}
// Read card record from contact and contacless
override fun onReadRecord(cardRecord: CardRecord?) {
lastCardRecord = cardRecord
doReadRecord(cardRecord)
}
// Verify card from contact and contacless
override fun onCardHolderVerify(cvmMethod: CVMMethod) {
doCardHolderVerify(cvmMethod)
}
// Build message data send to host from contact and contacless
override fun onOnlineProcess(transData: TransData) {
doOnlineProcess(transData)
}
// End process
override fun onEndProcess(result: Int, transData: TransData) {
doEndProcess(result, transData)
}
// Offline pin case
override fun onVerifyOfflinePin(
flag: Int,
random: ByteArray,
caPublicKey: CAPublicKey,
offlinePinVerifyResult: OfflinePinVerifyResult,
) {
...
}
override fun onObtainData(ins: Int, data: ByteArray) {
...
}
// Final data sendout
override fun onSendOut(ins: Int, data: ByteArray) {
doSendOut(ins, data)
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
onInitEMV
This function is call external function in class EMVHelper is doInitEMV() and doInitEMV() call manageAID() again
fun doInitEMV() {
//call manageAID function
manageAID()
}
private fun manageAID() {
// Initial AID list
val aids = arrayOf(
"A000000677",
"A000000677010101",
"A000000003",
"A000000065",
"A000000004"
)
// Loop for add aid to emv.manageAID
for (aid in aids) {
emv!!.manageAID(ActionFlag.ADD, aid, true)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
onWaitCard
Use when search card
private fun doWaitCard(flag: Int) {
// Stop search card when have a background process
emv!!.stopSearch()
when (flag) {
// Normal flag
WaitCardFlag.NORMAL -> searchCard {
respondCard()
}
// Show card again flag
WaitCardFlag.ISS_SCRIPT_UPDATE, WaitCardFlag.SHOW_CARD_AGAIN -> searchRFCard {
respondCard()
}
// Excute CDCVM flag
WaitCardFlag.EXECUTE_CDCVM -> {
emv.halt()
searchRFCard { respondCard() }
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Search Card
private fun searchCard(next: Runnable) {
try {
// Initial Listener
val cardHandler = object : SearchCardListener.Stub() {
// Overide function Swiped (Magnetic)
override fun onCardSwiped(track: Bundle?) {
// First stop EMV
stopEMV()
// Pass data to magnetic track
val checkSuccess = getMagneticTrack(track)
// If true continue
if (checkSuccess) {
isSwipeSuccess()
}
}
// Overide function Card insert (Contact)
override fun onCardInsert() {
// If listener is this condition go to next process
next.run()
}
// Overide function Card pass (Contactless)
override fun onCardPass(p0: Int) {
// If listener is this condition go to next process
next.run()
}
// Overide function timeout
override fun onTimeout() {
// If timeout, stop EMV
stopEMV()
}
// Overide function error
override fun onError(p0: Int, p1: String?) {
// If timeout, stop EMV
stopEMV()
// If timeout, stop search card
emv!!.stopSearch()
// If p0 equal 1 or 2 start EMV again
if (p0 == 1 || p0 == 2) {
startEMV()
}
}
}
// Start search card
emv?.searchCard(
cardOption.toBundle(),
60,
cardHandler
)
} catch (e: Exception) {
...
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
Select AID
This function use to select AID for read record data on Contact card and Contactless card
private fun checkPriority(aid: String): Boolean {
return when (aid) {
"a000000677" -> {
true
}
else -> {
false
}
}
}
private fun doAppSelect(reSelect: Boolean, candList: List<CandidateAID>) {
// Check list of AID
if (candList.size > 1) {
// Add aid to show on popup screen
aidOriginalList!!.add(candList)
// Count match priority AID
var countCheckPriority = 0
// Initial Bytearray to push aid
var aidPriority: ByteArray = byteArrayOf()
// Start loop by canList
for (candAid in candList) {
// Check priority of AID
if (checkPriority(candAid.aid.toHex().subSequence(0, 10).toString())) {
// If match count add 1
countCheckPriority += 1
// Add aid match priority to bytearray
aidPriority = candAid.aid
}
// Add aid to list
aidList!!.add(String(candAid.appLabel))
}
// Check count AID and aidPriority not empty
if (countCheckPriority > 0 && aidPriority.isNotEmpty()) {
// Call respondAID function
respondAID(aidPriority)
} else {
// Show aid popup to user select
seletAIDPopup!!.value = true
}
} else {
// If aid list of size equal 1 call respondAID function
respondAID(candList[0].aid)
}
}
// Select aid from user selected
fun selectedAID(select: Int, aidOriginalList: MutableList<List<CandidateAID>>) {
// Call respondAID function
respondAID(aidOriginalList[0][select].aid)
}
// This is function to call response event aid select to emv kernel
private fun respondAID(aid: ByteArray?) {
// Show process ui
doProcess!!.value = true
try {
// Build 9F06 tag from selected aid
val tmAid = TLV.fromData(EMVTag.EMV_TAG_TM_AID, aid)
// Return tlv to emv kernel
emv!!.respondEvent(tmAid.toString())
} catch (e: Exception) {
...
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
Final select
TIP
- Before use this function please read
《UEMV develop guide》before develop - In Thailand aid
a000000677kernel is 7 like PBOC (Public Bank Of China)
This function isbuild tlv message from user selected aid.
private fun doFinalSelect(finalData: FinalData) {
val tlvList = StringBuilder()
val pid = finalData.pid
var kernelID = finalData.kernelID.toInt()
// Check AID
when (finalData.aid.toHex()) {
// If aid is equal a000000677
"a000000677010101" -> {
// Add tlv
tlvList
.append("...")
// Set kernel id
kernelID = 7
}
else -> {
// Condition by kernel id
when (kernelID) {
// ID = 0 is normal emv
KernelID.EMV ->
tlvList.append("...")
}
// ID = 7 is Public Bank Of China
KernelID.PBOC -> {
// if suport PBOC Ecash,see transaction parameters of PBOC Ecash in《UEMV develop guide》.
// If support qPBOC, see transaction parameters of QuickPass in《UEMV develop guide》.
// For reference only below
tlvList.append("...")
}
// ID = 3 is VISA Paywave contactless
KernelID.VISA -> {
// Parameter settings, see transaction parameters of PAYWAVE in《UEMV develop guide》.
tlvList
.append("...")
}
// ID = 2 is Mastercard Paypass contactless
KernelID.MASTER ->
// Parameter settings, see transaction parameters of PAYPASS in《UEMV develop guide》.
tlvList
.append("...")
// ID = 4 is AMEX
KernelID.AMEX -> {
tlvList.append("...")
}
// ID = 6 is Discover
KernelID.DISCOVER -> {
tlvList.append("...")
}
// ID = 5 is JCB
KernelID.JCB -> {
tlvList.append("...")
}
else -> {
...
// And more please read kerbel id
}
}
}
}
// Send tlv to emv kernel
emv?.setTLVList(kernelID, tlvList.toString());
val emvTLVList = emv?.respondEvent(null)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
Kernel ID
Interface of Kernel ID
public interface KernelID {
int EMV = 0;
int EMVCTLess = 1;
int MASTER = 2;
int VISA = 3;
int AMEX = 4;
int JCB = 5;
int DISCOVER = 6;
int PBOC = 7;
int PURE = 11;
int MIR = 12;
int RUPAY = 13;
int PAGO = 14;
int MB = 15;
int NSICC = 218;
int DEFINE = 222;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Read Record
This function is reqad record from card.
private fun doReadRecord(record: CardRecord?) {
try {
// Get CAPK public index from record
val capkIndex = record?.pubKIndex?.let { byteArrayOf(it) }
if (capkIndex != null) {
...
}
// Get CAPK from database search by aid and index
val listPubKey = getCAPK(
BytesUtil.bytes2HexString(record?.aid).subSequence(0, 10).toString(),
capkIndex!!.toHex()
)
// If CAPK on database not null
if (listPubKey != null) {
// Please read CAPK list on internet
// Initial CAPK
val capKey = CAPublicKey()
// Initail index
capKey.index = record!!.pubKIndex
// Initail rid (A0000000XX)
capKey.rid = listPubKey.rid!!.decodeHex()
// Initail Exponent
capKey.exp = listPubKey.exponent!!.decodeHex()
// Initail modulus
capKey.mod = listPubKey.mod!!.decodeHex()
// Initail hash flag
capKey.hashFlag = 0x00.toByte()
// Initail expire date of capk
capKey.expDate = listPubKey.exp!!.decodeHex()
// Initail sha1
capKey.hash = listPubKey.sha1!!.decodeHex()
// Return capk to emv kernel
val ret = emv!!.setCAPubKey(capKey)
}
//
val cardData = selectCardData(BytesUtil.bytes2HexString(record.pan))
val handler = CoroutineExceptionHandler { _, exception ->
hasError!!.value = true
description!!.value = exception.message.toString()
}
if (cardData != null) {
// Find card control
val cardControl =
getCardControl(cardData.card_control_record_index!!, transactionType!!)
if (cardControl != null && jsonData != null) {
if (cardControl.checkAllow(transactionType)!!) {
jsonData.put("operation", "contact")
jsonData.put(
"card_exp",
BytesUtil.bytes2HexString(record.expiry).substring(2, 6)
)
// Set card data
jsonData.put("card_exp", BytesUtil.bytes2HexString(record.expiry).substring(2, 6))
jsonData.put("card_number", BytesUtil.bytes2HexString(record.pan))
jsonData.put("card_record_index", cardData.card_record_index!!)
jsonData.put("card_label", cardData.card_label)
jsonData.put("card_scheme_type", cardData.card_scheme_type)
jsonData.put("pan_masking", cardControl.pan_masking)
jsonData.put("host_record_index", cardData.host_record_index!!)
val host = selectHost(cardData.host_record_index!!)
Log.v("TEST", "host: $host")
if (host != null) {
// Set host data
jsonData.put("tid", host.terminal_id)
jsonData.put("mid", host.merchant_id)
jsonData.put("nii", host.nii)
jsonData.put("stan", host.stan)
jsonData.put("ip_address1", host.ip_address1)
jsonData.put("port1", host.port1)
jsonData.put("host_define_type", host.host_define_type)
jsonData.put("host_label", host.host_label_name)
jsonData.put("batch_number", host.last_batch_number)
jsonData.put("invoice", getTraceInvoice())
}
} else {
...
}
} else {
...
}
} else {
...
}
emv!!.respondEvent(null)
} catch (e: Exception) {
...
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
CAPK
CAPK (CA Public Key Index)open in new window
Card Holder verification
This function is verify card with pin or other method.
private fun doCardHolderVerify(cvm: CVMMethod) {
try {
// Initial Bundle
val param = Bundle()
// Put pinpad limit to buddle
param.putByteArray(PinpadData.PIN_LIMIT, byteArrayOf(0, 4, 5, 6, 7, 8, 9, 10, 11, 12))
// Initail listener
val listener: OnPinEntryListener = object : OnPinEntryListener.Stub() {
// When input pinpad
override fun onInput(arg0: Int, arg1: Int) {
...
}
// When confirm on pinpad
override fun onConfirm(arg0: ByteArray?, arg1: Boolean) {
return respondCVMResult(1.toByte())
}
// When cancle on pinpad
override fun onCancel() {
return respondCVMResult(0.toByte())
}
// When error from pinpad
override fun onError(error: Int) {
return respondCVMResult(2.toByte())
}
}
val scope = MainScope()
// CVM case
when (cvm.cvm.toInt()) {
// When offline pin
EMV_CVMFLAG_OFFLINEPIN -> pinpad!!.startOfflinePinEntry(param, listener)
// When online pin
EMV_CVMFLAG_ONLINEPIN -> {
param.putByteArray(PinpadData.PAN_BLOCK, lastCardRecord!!.pan)
scope.launch {
pinpad!!.startPinEntry(10, param, listener)
}
}
else -> {
respondCVMResult(1.toByte())
}
}
} catch (e: Exception) {
...
}
}
private fun respondCVMResult(result: Byte) {
try {
// Build tlv and response data to emv kernel
val chvStatus = TLV.fromData(EMVTag.DEF_TAG_CHV_STATUS, byteArrayOf(result))
val ret = emv!!.respondEvent(chvStatus.toString())
} catch (e: Exception) {
...
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
Online process
This function is send message to making transaction with bank host.
@Throws(RemoteException::class)
open fun doOnlineProcess(transData: TransData) {
val goOnline = isGoingOnline()
if (goOnline != "") {
// Build tlv from response message
val tlvList = StringBuilder()
.append("..")
// Send tlv to emv kernel
val ret = emv!!.respondEvent(tlvList.toString())
}
}
private fun isGoingOnline(): String {
return try {
// Build ISO8583 Message
val dataISO8583 = buildISO(jsonData!!)
// Go online to host wait to update
...
// Return data from host by string
""
} catch (e: Exception) {
""
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
End process
This function is call when emv process is ended.
private fun doEndProcess(result: Int, transData: TransData) {
val strTransData = BytesUtil.bytes2HexString(transData.tlvData)
if (result != EMVError.SUCCESS) {
// If error
...
} else {
// If not error
..,
}
}
2
3
4
5
6
7
8
9
10
Send out
private fun doSendOut(ins: Int, data: ByteArray) {
Log.d("test_sendout", "=> onSendOut | track2 = $ins " + data.toHex())
when (ins) {
KernelINS.DISPLAY -> // DisplayMsg: MsgID(1 byte) + Currency(1 byte)+ DataLen(1 byte) + Data(30 bytes)
if (data[0].toInt() == MessageID.ICC_ACCOUNT) {
val len: Int = data[2].toInt()
val account = BytesUtil.subBytes(data, 1 + 1 + 1, len)
val accTLVList = TLVList.fromBinary(account)
val track2 = BytesUtil.bytes2HexString(accTLVList.getTLV("57").bytesValue)
Log.d("test_sendout", "=> onSendOut | track2 = $track2")
}
KernelINS.DBLOG -> {
var i = data.size - 1
while (i >= 0) {
if (data[i].toInt() == 0x00) {
data[i] = 0x20
}
i--
}
Log.d("DBLOG", String(data))
}
// RF is halt call function again
KernelINS.CLOSE_RF -> {
Log.d(
"test_sendout",
"=>=> onSendOut: Notify the application to halt contactless module"
)
halt()
}
else -> {
Log.d(
"test_sendout",
"=> onSendOut: instruction is 0x" + Integer.toHexString(ins) + ", data is " + BytesUtil.bytes2HexString(
data
)
)
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39