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

}
1
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)
    }
    
}
1
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) {
           ...
        }
    }
}
1
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) {
           ...
        }
    }
}
1
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) {
           ...
        }
    }
}
1
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) {
           ...
        }
    }
}
1
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) {
            ...
        }
    }
}
1
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)
        }
    }
}
1
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)
    }
}
1
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() }
            }
        }
}
1
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) {
            ...
        }

}
1
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) {
        ...
    }
}
1
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 a000000677 kernel 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)
}
1
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;
}
1
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) {
        ...
    }
}
1
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) {
        ...
    }
}
1
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) {
        ""
    }
}
1
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
      ..,
    }
}
1
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
                )
            )
        }
    }
}
1
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
Last Updated:
Contributors: Supavit Panyafang