Git Product home page Git Product logo

sunmi-card-reader's Introduction

Sunmi-Card-Reader

⚠️ Update (20-Oct-2022)

Is been awhile since last update, there still a lot of learning waiting for me, will update some doc regrading what i learn once i free, but if you want immediate answer feel free to contact me.

📝 Side note

I discovered that there are several names for this, such as Sunmi Card Reader, Sunmi Pay Library, Sumni Pay Service, Sunmi SDK. I don't care what you call it, all i want to do is demonstrate and show how to use the sdk, as their website is basically a trash and donesn't include much information even on the internet.

📱 Device Tested

  • Sunmi P2

📂 Preparation

Before we start, make sure you have import PayLib-release-1.4.43.aar library to your project and remember to copy all those thing in Callback and util folder (Just do it).


⚙ Manifest

Make sure you've enabled all of the permissions on the Manifest

    <uses-permission android:name="com.sunmi.perm.MSR" />
    <uses-permission android:name="com.sunmi.perm.ICC" />
    <uses-permission android:name="com.sunmi.perm.PINPAD" />
    <uses-permission android:name="com.sunmi.perm.SECURITY" />
    <uses-permission android:name="com.sunmi.perm.CONTACTLESS_CARD" />

Init SDK

    var mSMPayKernel: SunmiPayKernel? = null
    mSMPayKernel = SunmiPayKernel.getInstance()
    mSMPayKernel!!.initPaySDK(this,object : SunmiPayKernel.ConnectCallback {
        override fun onDisconnectPaySDK() {}
        override fun onConnectPaySDK() {
             try {
                BaseApp.mReadCardOptV2 = mSMPayKernel!!.mReadCardOptV2
                BaseApp.mEMVOptV2 = mSMPayKernel!!.mEMVOptV2
                BaseApp.mPinPadOptV2 = mSMPayKernel!!.mPinPadOptV2
                BaseApp.mBasicOptV2 = mSMPayKernel!!.mBasicOptV2
                BaseApp.mSecurityOptV2 = mSMPayKernel!!.mSecurityOptV2
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    })

Before we start scaning the card we have to init for the EMV process (Just copy from the repo), it's for init for AID(Application Identifiers), CAPK (CA Public Key) and terminal config

    EmvUtil().init()

Note: Every country have different format, Im not really sure either.


😒 Here is where pain started

Here's a function to get things started.

private fun checkCard(cardType: Int) {
    try {
        mEMVOptV2.abortTransactProcess()
        mEMVOptV2.initEmvProcess()

        mReadCardOptV2.checkCard(cardType, mCheckCardCallback, 60)
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

To call the function is pretty easy

binding.btnNFC.setOnClickListener {
    val type = AidlConstants.CardType.NFC.value
    checkCard(type)
}

or any other value

val type = AidlConstants.CardType.IC.value
        # or
val type = AidlConstants.CardType.MAGNETIC.value or 
                AidlConstants.CardType.NFC.value or 
                AidlConstants.CardType.IC.value

After you calling it the result will return on mCheckCardCallback.

private val mCheckCardCallback: CheckCardCallbackV2 = object : CheckCardCallback() {
    override fun findICCard(atr: String) {
        super.findICCard(atr)   
        Log.e("dd--", "ID: $atr")
    }
    override fun findMagCard(info: Bundle) {
        super.findMagCard(info)
        val track2 = info.getString("TRACK2")
        Log.e("dd--", "Track 2: $track2")
    }
    override fun findRFCard(uuid: String) {
        super.findRFCard(uuid)
        Log.e("dd--", "ID: $uuid")
    }
    ....
}

And that's it for the card reader.


EMV Process

There only two place we need to use this process which is NFC or IC because magnatic stripes will direct give you the raw value of the card info.

At start we calling this function on findICCard and findRFCard

private fun transactProcess() {
    Log.e("dd--", "transactProcess")
    try {
        val emvTransData = EMVTransDataV2()
        emvTransData.amount = "10"
        emvTransData.flowType = 1 //1 Standard Flow, 2 Simple Flow, 3 QPass
        emvTransData.cardType = mCardType
        mEMVOptV2.transactProcess(emvTransData, mEMVCallback)
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

Only 2 value u might need to change like amount (cent) and cardType (either NFC/IC)
And same thing as card reading the result will be return on mEMVCallback

private val mEMVCallback = object : EMVCallback(){

    override fun onWaitAppSelect(p0: MutableList<EMVCandidateV2>?, p1: Boolean) {}

    override fun onAppFinalSelect(p0: String?) {}

    override fun onConfirmCardNo(p0: String?) {}

    override fun onRequestShowPinPad(p0: Int, p1: Int) {}

    override fun onCertVerify(p0: Int, p1: String?) {}

    override fun onOnlineProc() {}

    override fun onTransResult(p0: Int, p1: String?) {}
        
} 

Here is where thing will becoming complicated, i will try explain whatever i found. (Judge me if im wrong)


🕐Step 1


override fun onWaitAppSelect(p0: MutableList<EMVCandidateV2>?, p1: Boolean) {
    super.onWaitAppSelect(p0, p1)

    Log.e("dd--", "onWaitAppSelect isFirstSelect:$p1")
    p0?.forEach {
        Log.e("dd--", "EMVCandidate:$it")
    }
    mEMVOptV2.importAppSelect(0)
}

Nothing will happen here unless you are plugging in a Debit Card, because in debit card there will two AID(Candidate) inside the card. One will be the "Card" for us to procced and another one is for the ATM.

Screenshot

But normally the card we need is always the first index (0), so we have to tell the SDK which are we select and notify to procced to next step.

mEMVOptV2.importAppSelect(0)

🕑Step 2

Set the normal TLV Data before we start checking the card prefix

override fun onAppFinalSelect(p0: String?) {
    super.onAppFinalSelect(p0)
    Log.e("dd--", "onAppFinalSelect value:$p0")

    val tags = arrayOf("5F2A", "5F36", "9F33", "9F66")
    val value = arrayOf("0458", "00", "E0F8C8", "B6C0C080")
    mEMVOptV2.setTlvList(TLVOpCode.OP_NORMAL, tags, value)
}

⚠⚠ Important Note ⚠⚠

5F2A = country code
5F36 = currency code exponent
Please refer this EFTLAB

(Side Note: I legit don't know what should i assign for 5F36 even the official document didn't state is 00 or 02 mean 2 decimal point.)

override fun onAppFinalSelect(p0: String?) {
    super.onAppFinalSelect(p0)

    ...

    if (p0 != null && p0.isNotEmpty()){
        val isVisa = p0.startsWith("A000000003")
        val isMaster = (p0.startsWith("A000000004") || p0.startsWith("A000000005"))

    }
}

So here we are checking which type are them but in this case i just going for Visa and Master Card, but if you want other prefix please refer to this

val isUnionPay = p0.startsWith("A000000333")
val isAmericanExpress = p0.startsWith("A000000025")
val isJCB = p0.startsWith("A000000065")
val isRuPay = p0.startsWith("A000000524")

Still inside onAppFinalSelect after checking with the prefix and now we have to set the other TLV value.

if (isVisa){

    // VISA(PayWave)
    Log.e("dd--", "detect VISA card")

}else if(isMaster){

    // MasterCard(PayPass)
    Log.e("dd--", "detect MasterCard card")
    val tagsPayPass = arrayOf(
        "DF8117", "DF8118", "DF8119", "DF811B", "DF811D",
        "DF811E", "DF811F", "DF8120", "DF8121", "DF8122",
        "DF8123", "DF8124", "DF8125", "DF812C"
    )
    val valuesPayPass = arrayOf(
        "E0", "F8", "F8", "30", "02",
        "00", "E8", "F45084800C", "0000000000", "F45084800C",
        "000000000000", "999999999999", "999999999999", "00"
    )
                    
    mEMVOptV2.setTlvList(TLVOpCode.OP_PAYPASS, tagsPayPass, valuesPayPass)

    //Reader CVM Required Limit (Malaysia => RM250)
    mEMVOptV2.setTlv(TLVOpCode.OP_PAYPASS,"DF8126","000000025000")

}                

📒Side Note

DF8126 -> Reader CVM Required Limit


For AmericanExpress

val tags = arrayOf("9F6D", "9F6E", "9F33", "9F35", "DF8168", "DF8167", "DF8169", "DF8170")
val values = arrayOf("C0", "D8E00000", "E0E888", "22", "00", "00", "00", "60")

And in the end, same thing to notify the SDK.

mEMVOptV2.importAppFinalSelectStatus(0)

🕒Step 3

Nothing much on onConfirmCardNo it will only show you the card number

override fun onConfirmCardNo(p0: String?) {
    super.onConfirmCardNo(p0)
    Log.e("dd--", "onConfirmCardNo cardNo:$p0")
    mCardNo = p0!!
    //notify sdk        
    mEMVOptV2.importCardNoStatus(0)
}

🕓Step 4

In onRequestShowPinPad it will prompt pin pad for user to input the pin.

override fun onRequestShowPinPad(p0: Int, p1: Int) {
    super.onRequestShowPinPad(p0, p1)
    Log.e("dd--", "onRequestShowPinPad pinType:$p0 remainTime:$p1")
    // 0 - online pin, 1 - offline pin
    mPinType = p0
    initPidPad()
}

On initPidPad()

private fun initPidPad(){
    Log.e("dd--", "initPinPad")
    try {
        val pinPadConfig = PinPadConfigV2()
        pinPadConfig.pinPadType = 0
        pinPadConfig.pinType = mPinType!!
        pinPadConfig.isOrderNumKey = true
        val panBytes = mCardNo.substring(mCardNo.length - 13, mCardNo.length - 1)
                .toByteArray(charset("US-ASCII"))
        pinPadConfig.pan = panBytes
        pinPadConfig.timeout = 60 * 1000 // input password timeout
        pinPadConfig.pinKeyIndex = 12 // pik index 
        pinPadConfig.maxInput = 6
        pinPadConfig.minInput = 0
        pinPadConfig.keySystem = 0 
        pinPadConfig.algorithmType = 0 
        mPinPadOptV2.initPinPad(pinPadConfig, mPinPadCallback)
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

⚠ Few thing need you need to pay attention about he pin pad config.
pinType 0 = online pin , 1 = offline pin
algorithmType 0 = 3DES , 1 = SM4
keySystem 0 = SEC_MkSk , 1 = SEC_DuKpt

If your pin block return you 00 try to change pinKeyIndex until where is works. I legit don't know what this thing do if anyone count help me explain.


Same thing result will return on `mPinPadCallback`
private val mPinPadCallback = object: PinPadCallback(){

    override fun onConfirm(p0: Int, p1: ByteArray?) {
        super.onConfirm(p0, p1)
        if (p1 != null) {
            val hexStr = ByteUtil.bytes2HexStr(p1)
            Log.e("dd--", "onConfirm pin block:$hexStr")
            importPinInputStatus(0)
        }else{
            importPinInputStatus(2)
        }
    }

    override fun onCancel() {
        super.onCancel()
        Log.e("dd--", "onCancel")
        importPinInputStatus(1)
    }

    override fun onError(p0: Int) {
        super.onError(p0)
        Log.e("dd--", "onError: ${AidlErrorCode.valueOf(p0).msg}")
        importPinInputStatus(3)
    }

}

After user done input the pin if successful it will generate the pin block and same thing we have to notify the SDK what is the status.

private fun importPinInputStatus(inputResult: Int) {
    Log.e("dd--", "importPinInputStatus:$inputResult")
    try {
        mEMVOptV2.importPinInputStatus(mPinType!!, inputResult)
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

We almost there, don't worry.


🕔Step 5

I legit don't know what this thing do (onCertVerify).

override fun onCertVerify(p0: Int, p1: String?) {
    super.onCertVerify(p0, p1)
    Log.e("dd--", "onCertVerify certType:$p0 certInfo:$p1")
    mCertInfo = p1.toString()
    mEMVOptV2.importCertStatus(0)
}

🕕Step 6

On this step, we almost there and here we gonna pull all the data (TLV data) inside the chip. (as long is not Magnetic Strip Card)

override fun onOnlineProc() {
    super.onOnlineProc()
    Log.e("dd--", "onOnlineProc")
    try{

        if(mCardType != AidlConstants.CardType.MAGNETIC.value){
            getTlvData()
        }
        importOnlineProcessStatus(0)

    }catch (e:Exception){
        e.printStackTrace()
        importOnlineProcessStatus(-1)
    }
}
private fun getTlvData() {
    try {
        val tagList = arrayOf(
            "DF02", "5F34", "9F06", "FF30", "FF31",
            "95", "9B", "9F36", "9F26", "9F27", 
            "DF31", "5A", "57", "5F24", "9F1A", 
            "9F03", "9F33", "9F10", "9F37", "9C",
            "9A", "9F02", "5F2A", "82", "9F34", 
            "9F35", "9F1E", "84", "4F", "9F09", "9F41",
            "9F63", "5F20", "9F12", "50"
        )
        //Only Mastercard have this extra tag
        val payPassTags = arrayOf(
                "DF811E",
                "DF812C",
                "DF8118",
                "DF8119",
                "DF811F",
                "DF8117",
                "DF8124",
                "DF8125",
                "9F6D",
                "DF811B",
                "9F53",
                "DF810C",
                "9F1D",
                "DF8130",
                "DF812D",
                "DF811C",
                "DF811D",
                "9F7C"
        )
        val outData = ByteArray(2048)
        val map: MutableMap<String, TLV> = HashMap()
        var len = mEMVOptV2.getTlvList(TLVOpCode.OP_NORMAL, tagList, outData)
        if (len > 0) {
            val hexStr = ByteUtil.bytes2HexStr(Arrays.copyOf(outData, len))
            map.putAll(TLVUtil.hexStrToTLVMap(hexStr))
        }
        len = mEMVOptV2.getTlvList(TLVOpCode.OP_PAYPASS, payPassTags, outData)
        if (len > 0) {
            val hexStr = ByteUtil.bytes2HexStr(Arrays.copyOf(outData, len))
            map.putAll(TLVUtil.hexStrToTLVMap(hexStr))
        }

        var temp = ""
        val set: Set<String> = map.keys
        set.forEach {
            val tlv = map[it]
            temp += if (tlv != null) {
                "$it : ${tlv.value} \n"
            } else {
                "$it : \n"
            }
        }
        Log.e("dd--", "TLV: $temp")

    } catch (e: Exception) {
        e.printStackTrace()
    }
}

It will be something like this

Screenshot

So what those it mean, don't worry i got you back.
You can refer this.

Take Example
5F24 => expire date
5F20 => card holder name
57 => track 2 equivalent data

Convert to string

ByteUtil.hexStr2Str(tlv.value)

🕖Step 7

Congrate this is the last step and nothing to do so. If the code is 0 mean you will be ok, otherwise error will be show on p1

override fun onTransResult(p0: Int, p1: String?) {
    super.onTransResult(p0, p1)
    Log.e("dd--", "onTransResult code:$p0 desc:$p1")
}

Reference

Repo

Doc

sunmi-card-reader's People

Contributors

snorsnor9998 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

sunmi-card-reader's Issues

Unresolved reference

i got this error while iam trying to add dependency

implementation(files("libs/PayLib-release-1.4.43.aar"))

but i got this error
Null extracted folder for artifact: ResolvedArtifact(componentIdentifier=PayLib-release-1.4.43.aar, variant=local file, variantName=null, artifactFile=C:\Users\User2\AndroidStudioProjects\SunmiPayment\app\libs\PayLib-release-1.4.43.aar, isTestFixturesArtifact=false, extractedFolder=null, publishedLintJar=null, dependencyType=ANDROID, isWrappedModule=false, buildMapping={__current_build__=C:\Users\User2\AndroidStudioProjects\SunmiPayment})

How to read Card Number and Balance Bank NFC Card?

I have a bank card money, Flazz (BCA) and Tapcash (BNI). I use the NFC reading method in the Sunmi library, tetapi hasilnya hanya mengembailkan nilai

  • uid
  • ats
  • cardCategory

How to get card information like Card Number, Balance, and more ?
I succeeded in getting it with the IC method, but not with NFC

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.