Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Online documentation (Javadoc)

...

Table of Contents
minLevel1
maxLevel7

Basic transaction

A basic OLA transaction includes the following steps:

OLA classes instantiation (once at startup)

Code Block
private final Olaval ola;: Ola
private val contact: OlaContact
private val contactless: OlaContactless
private val emv: OlaEmv
private val dev: Dev
private val publicKey: OlaPublicKey
...

init {
    ola = Ola.getInstance()
    contact = OlaContact.getInstance()
    contactless = OlaContactless.getInstance()
    emv = OlaEmv.getInstance()
    publicKey = OlaPublicKey()
    ...
}

OLA component initialisation (once at startup)

Code Block
/**
 * Configures OLA layer (key files paths, trace, etc...)
 */
private fun olaConfig() {
    val path = byteArrayOf(0xdf.toByte(), 0x01, 28)
    val pathValue private final OlaEmv emv;= "/data/data/ca.amadis.tester/"
    val cakeys = byteArrayOf(0xdf.toByte(), 0x02, 34)
    val cakeysValue = "/data/data/ca.amadis.tester/CAKeys"
    val crl = byteArrayOf(0xdf.toByte(), 0x03, 31)
    val crlValue = "/data/data/ca.amadis.tester/CRL"
    val efl = byteArrayOf(0xdf.toByte(), 0x04, 31)
    val eflValue = "/data/data/ca.amadis.tester/EFL"
    val pcsc = byteArrayOf(0xdf.toByte(), 0x05, 0x00) // pcsc not used
    val lang = byteArrayOf(0xdf.toByte(), 0x06, 0x00) // lang not used
    val runtimeParams = byteArrayOf(0xdf.toByte(), 0x07, 0x07, 0x00, 0x0F, 0x04, 0x0F, 0x0A, 0x00, private0x01)
final OlaContactless contactless;  this.olaval traceLevel = OlabyteArrayOf(0xdf.getInstancetoByte();
this.contactless = OlaContactless.getInstance();
this.emv = OlaEmv.getInstance();

OLA component initialisation (once at startup)

Code Block
/**
 * Configures OLA layer (key files paths, trace, etc...)
 */
private void olaConfig() {

    final byte[] path = {(byte)0xdf, 0x01, 28};
    final String pathValue = "/data/data/ca.amadis.tester/";
    final byte[] cakeys = {(byte)0xdf, 0x02, 34};
    final String cakeysValue = "/data/data/ca.amadis.tester/CAKeys";
    final byte[] crl = {(byte)0xdf, 0x03, 31};
    final String crlValue = "/data/data/ca.amadis.tester/CRL";
    final byte[] efl = {(byte)0xdf, 0x04, 31};
    final String eflValue = "/data/data/ca.amadis.tester/EFL";
    final byte[] pcsc = {(byte)0xdf, 0x05, 0x00}; // pcsc not used
    final byte[] lang = {(byte)0xdf, 0x06, 0x00}; // lang not used
    final byte[] runtimeParams = {(byte)0xdf, 0x07, 0x07, 0x00, 0x0F, 0x04, 0x0F, 0x0A, 0x00, 0x01};
    final byte[] traceLevel = {(byte)0xdf, 0x08, 0x01, 0x05};
    final byte[] pollingTechno = {(byte)0xdf, 0x10, 0x01, 0x04}; // Contactless only
    final byte[] sredMode = {(byte)0xdf, 0x17, 0x01, 0x01}; // SRED mode active

    try 
    {
, 0x08, 0x01, 0x05)
    val pollingTechno = byteArrayOf(0xdf.toByte(), 0x10, 0x01, 0x04) // Contactless only
    var sredMode: ByteArray
    if (BuildConfig.FLAVOR.lowercase().contains("nosred")) {
        sredMode = byteArrayOf(0xdf.toByte(), 0x17, 0x01, 0x00) // SRED mode not active
    } else {
        sredMode = byteArrayOf(0xdf.toByte(), 0x17, 0x01, 0x01) // SRED mode active
    }
  
    try {
        val out = ByteArrayOutputStream()
        out.write(path)
        out.write(pathValue.toByteArray())
        out.write(cakeys)
        out.write(cakeysValue.toByteArray())
        out.write(crl)
        out.write(crlValue.toByteArray())
        out.write(efl)
        out.write(eflValue.toByteArray())
        out.write(pcsc)
        out.write(lang)
        out.write(runtimeParams)
        out.write(traceLevel)
        out.write(pollingTechno)
       ByteArrayOutputStream out = new ByteArrayOutputStream();.write(sredMode)
        out.write(path);
        outola.writeinitializeAtStartUp(pathValueout.getBytestoByteArray());
  
    } out.write(cakeys);
catch (e: IOException) {
    }
}

AID configuration

Code Block
private fun out.write(cakeysValue.getBytes());
terminalContactlessConfig() {

      outLog.write(crl);v("Transaction", "terminalContactlessConfig")
    
   out.write(crlValue.getBytes());
    val contactlessCfg = arrayOf(
    out.write(efl);         out.write(eflValue.getBytes());
        out.write(pcsc);ContactlessCfg(AID.FromBytes(byteArrayOf(0xA0.toByte(), 0x00, 0x00, 0x00, 0x04, 0x10, 0x10)), 
          out.write(lang);         out.write(runtimeParams);    true, 
   out.write(traceLevel);         out.write(pollingTechno);         out.write(sredMode);        2, 
byte[] tlv = out.toByteArray();          ola.initializeAtStartUp(tlv);     }      catch (IOException e)mcCfg),
    {      }
}

AID configuration (once at startup)

Code Block
private void terminalContactlessConfig() {

    contactless.flushAIDSupported();ContactlessCfg(AID.FromBytes(byteArrayOf(0xA0.toByte(), 0x00, 0x00, 0x00, 0x04, 0x20, 0x10)),
        for (OLAConfig.ContactlessCfg config : OLAConfig.contactlessCfg)      {     true, 
  contactless.addAIDSupported(
            config.aid,             config.partial2, 
           (byte)config.kernelId,            mcCfg),
config.additionalData         ...);

   } 
    contactless.commitSupportedAIDsflushAIDSupported();
}

Keys configuration (once at startup)

Code Block
private void terminalPublicKeyConfig() {
    
    for publicKey.flush();(config in contactlessCfg) {
     byte rid[] = {(byte)0xA0, 0x00, 0x00, 0x00, 0x03};contactless.addAIDSupported(
        byte idx = (byte)0x92;
 config.aid,
   byte modulus[] = {0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11config.partial,
0x11, 0x11, 0x11};     byte exponentValue[] = {0x03};  config.kernelId.toByte(),
  byte expirDate[] = {0x22, 0x12, 0x31};     Holdersconfig.SingleObjectHolder<Boolean>additionalData
wasMaxReached_holder = new Holders.SingleObjectHolder<Boolean>(false);     OlaPublicKey.PublicKeyData)
key = publicKey.new PublicKeyData(rid, idx, modulus, exponentValue, expirDate); }
    
  publicKey.add(key, null, wasMaxReached_holder);

    publicKey.commit();
}

...

 contactless.commitSupportedAIDs()
}

Keys configuration

Code Block
private voidfun txnSetTransactionRelatedDataterminalPublicKeyConfig() {

    emvLog.setTag(OlaTag.TRANSACTION_DATE,v("Transaction", "terminalPublicKeyConfig")
    val publicKeyCfg = arrayOf(
     new byte[]{0x20, 0x10, 0x28});   /* MasterCard */
        PublicKeyCfg(byteArrayOf(0xa0.toByte(), 0x00, 0x00, 0x00, 0x03),
        // 9A     emv.setTag(OlaTag.TRANSACTION_TIME,              new byte[]{0x12, 0x00, 0x00});0x92.toByte(),
                    // 9F21
    emv.setTag(OlaTag.AMOUNT_AUTHORISED,             new byte[]{0x00, 0x00, 0x00, 0x00, 0x01, 0x50});   // 9F02
    emv.setTag(OlaTag.AMOUNT_OTHER_NUM,              new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x50});   // 9F03
    emv.setTag(OlaTag.TRANSACTION_CURRENCY_CODE,     new byte[]{0x08, 0x40});                           // 5F2A
    emv.setTag(OlaTag.TRANSACTION_CURRENCY_EXPONENT, new byte[]{0x02});                                 // 5F36
    emv.setTag(OlaTag.TRANSACTION_TYPE,              new byte[]{0x00});                                 // 9C

    /*
     * Other possible tags to set here:
     * Account Type (5F57)
     * Transaction Category Code (9F53)
     *
     * All other tags must be set through the combinations!
     */
}

Preprocessing

Code Block
private OlaError txnPreProcess() {

    OlaError ret = contactless.preprocess();
    if (ret != OlaError.OLA_OK)
    {
        Log.e("OLA-TESTER", "contactless pre-process failed");
    }

    return ret;
}

Card detection

Code Block
private short txnGetCard() {

    Log.d("OLA-TESTER", "PRESENT CARD");

    int foundTechnos = dev.technoPolling(60000);
    l("Result of foundTechnos: "+ foundTechnos);

    if (foundTechnos==4)
    {
        return clcEMV_CARD; //TODO remove hard-coding
    }
    else
    {
        return 0;
    }
}

Do transaction

Code Block
private OlaError doTransaction() {

    SingleObjectHolder<Integer> nbCandidatesHolder = new SingleObjectHolder<Integer>();
    OlaError ret = contactless.buildCandidateList(nbCandidatesHolder);

    if (ret != OlaError.OLA_OK)
    { byteArrayOf(0x99.toByte(), 0x6a, 0xf5.toByte(), 0x6f, 0x56, 0x91.toByte(), 0x87.toByte(), 0xd0.toByte(), 0x92.toByte(), 0x93.toByte(), 0xc1.toByte(), 0x48, 0x10, 0x45, 0x0e, 0xd8.toByte(), 0xee.toByte(), 0x33, 0x57, 0x39, 0x7b, 0x18, 0xa2.toByte(), 0x45, 0x8e.toByte(), 0xfa.toByte(), 0xa9.toByte(), 0x2d, 0xa3.toByte(), 0xb6.toByte(), 0xdf.toByte(), 0x65, 0x14, 0xec.toByte(), 0x06, 0x01, 0x95.toByte(), 0x31, 0x8f.toByte(), 0xd4.toByte(), 0x3b, 0xe9.toByte(), 0xb8.toByte(), 0xf0.toByte(), 0xcc.toByte(), 0x66, 0x9e.toByte(), 0x3f, 0x84.toByte(), 0x40, 0x57, 0xcb.toByte(), 0xdd.toByte(), 0xf8.toByte(), 0xbd.toByte(), 0xa1.toByte(), 0x91.toByte(), 0xbb.toByte(), 0x64, 0x47, 0x3b, 0xc8.toByte(), 0xdc.toByte(), 0x9a.toByte(), 0x73, 0x0d, 0xb8.toByte(), 0xf6.toByte(), 0xb4.toByte(), 0xed.toByte(), 0xe3.toByte(), 0x92.toByte(), 0x41, 0x86.toByte(), 0xff.toByte(), 0xd9.toByte(), 0xb8.toByte(), 0xc7.toByte(), 0x73, 0x57, 0x89.toByte(), 0xc2.toByte(), 0x3a, 0x36, 0xba.toByte(), 0x0b, 0x8a.toByte(), 0xf6.toByte(), 0x53, 0x72, 0xeb.toByte(), 0x57, 0xea.toByte(), 0x5d, 0x89.toByte(), 0xe7.toByte(), 0xd1.toByte(), 0x4e, 0x9c.toByte(), 0x7b, 0x6b, 0x55, 0x74, 0x60, 0xf1.toByte(), 0x08, 0x85.toByte(), 0xda.toByte(), 0x16, 0xac.toByte(), 0x92.toByte(), 0x3f, 0x15, 0xaf.toByte(), 0x37, 0x58, 0xf0.toByte(), 0xf0.toByte(), 0x3e, 0xbd.toByte(), 0x3c, 0x5c, 0x2c, 0x94.toByte(), 0x9c.toByte(), 0xba.toByte(), 0x30, 0x6d, 0xb4.toByte(), 0x4e, 0x6a, 0x2c, 0x07, 0x6c, 0x5f, 0x67, 0xe2.toByte(), 0x81.toByte(), 0xd7.toByte(), 0xef.toByte(), 0x56, 0x78, 0x5d, 0xc4.toByte(), 0xd7.toByte(), 0x59, 0x45, 0xe4.toByte(), 0x91.toByte(), 0xf0.toByte(), 0x19, 0x18, 0x80.toByte(), 0x0a, 0x9e.toByte(), 0x2d, 0xc6.toByte(), 0x6f, 0x60, 0x08, 0x05, 0x66, 0xce.toByte(), 0x0d, 0xaf.toByte(), 0x8d.toByte(), 0x17, 0xea.toByte(), 0xd4.toByte(), 0x6a, 0xd8.toByte(), 0xe3.toByte(), 0x0a, 0x24, 0x7c, 0x9f.toByte()),
                     byteArrayOf(0x03),
                     byteArrayOf(0x24, 0x12, 0x31)),
        ...)
        
    publicKey.flush()

    for (config in publicKeyCfg) {
        val maxReached = SingleObjectHolder(false)
        val key: PublicKeyData = publicKey.PublicKeyData(
            config.rid,
            config.idx,
            config.modulus,
            config.exponentValue,
            config.expirDate
        )
        publicKey.add(key, null, maxReached)
    }

    publicKey.commit()
}

Transaction related data ()

Code Block
private fun txnSetTransactionRelatedData() {

    Log.v("Transaction", "txnSetTransactionRelatedData")
    emv.setTag(OlaTag.TRANSACTION_DATE, byteArrayOf(0x20, 0x10, 0x28))               showOutcome(ret);     }      /*/ 9A
    * For now, finalSelectCandidate is useless. Everything from preprocessing to cardemv.setTag(OlaTag.TRANSACTION_TIME, byteArrayOf(0x12, 0x00, 0x00))             * processing is done in "buildCandidateList". This will change as soon as exit conditions// 9F21
    * are availble in OLA 2.x, hence we keep all the function calls here
     */
    SingleObjectHolder<Byte> kernel_id_holder = new SingleObjectHolder<Byte>();emv.setTag(OlaTag.AMOUNT_AUTHORISED, byteArrayOf(0x00, 0x00, 0x00, 0x00, 0x01, 0x50))       // 9F02
    emv.setTag(OlaTag.AMOUNT_OTHER_NUM, byteArrayOf(0x00, 0x00, 0x00, 0x00, 0x00, 0x50))     ret = contactless.finalSelectCandidate(1, kernel_id_holder);
 // 9F03
   if (ret != OlaError.OLA_OK) emv.setTag(OlaTag.TRANSACTION_CURRENCY_CODE, byteArrayOf(0x08, 0x40))       {         showOutcome(ret);     }  // 5F2A
  ret = contactless.doTransaction();emv.setTag(OlaTag.TRANSACTION_CURRENCY_EXPONENT, byteArrayOf(0x02))     showOutcome(ret);      if (ret == OlaError.OLA_OK)     {      // 5F36
 SingleObjectHolder<byte[]> bytesHolder = new SingleObjectHolder<byte[]>(); emv.setTag(OlaTag.TRANSACTION_TYPE, byteArrayOf(0x00))          for (int olaTag : batch)         {             OlaError result = emv.getTag(olaTag, bytesHolder);
// 9C

    /*
      * Other possible tags to ifset (resulthere:
== OlaError.OLA_OK)     * Account Type (5F57)
      * Transaction l("Result for ola_contactless_get_tag(): "+result+" => "+ Arrays.toString(bytesHolder.get()));
 Category Code (9F53)
      *
      * All other tags elsemust be set through the combinations!
      */
     l("Result for ola_contactless_get_tag(): "+result+" => tag missing");
 emv.setTag(0x9F53, byteArrayOf(0x11));
}

Preprocessing

Code Block
private fun txnPreProcess(): OlaError {

    Log.v("Transaction", "txnPreProcess")
    val ret = contactless.preprocess()
    if (ret }
 != OlaError.OLA_OK) {
  }      Log.de("OLA-TESTERTransaction", "**** REMOVE CARD **** ");
txnPreProcess - contactless pre-process failed")
    }
    return ret;
}
}

Card detection

Code Block
private fun txnGetCard(): Int {

    Log.v("Transaction", "txnGetCard - **** PRESENT CARD ****")
    val foundTechnos = dev.technoPolling(30)
    Log.v("Transaction", "txnGetCard - result of foundTechnos: $foundTechnos")
    return if (foundTechnos == 4) {
        clcEMV_CARD //TODO remove hard-coding
    } else {
        0
    }
}

Do transaction

Code Block
private fun doTransaction(): OlaError {

    Log.v("Transaction", "doTransaction - buildCandidateList")
    val nbCandidatesHolder = SingleObjectHolder<Int>()
    var ret = contactless.buildCandidateList(nbCandidatesHolder)
    if (ret != OlaError.OLA_OK) {
        Outcome.showOutcome(contactless, ret)
    }
    
    Log.v("Transaction", "doTransaction - nb of candidates: ${nbCandidatesHolder.get()}")
    Log.v("Transaction", "doTransaction - finalSelectCandidate")
    val kernel_id_holder = SingleObjectHolder<Byte>()
    ret = contactless.finalSelectCandidate(1, kernel_id_holder)
    if (ret != OlaError.OLA_OK) {
        Outcome.showOutcome(contactless, ret)
    }

    Log.v("Transaction", "doTransaction - doTransaction")
    ret = contactless.doTransaction()
    Outcome.showOutcome(contactless, ret)
    if (ret == OlaError.OLA_OK) {
        val bytesHolder = SingleObjectHolder<ByteArray>()
        for (olaTag in batch) {
            val result = emv.getTag(olaTag, bytesHolder)
            if (result == OlaError.OLA_OK) {
                Log.v("Transaction", "doTransaction - result for getTag(): $result => " +
                      Utils.bytesToHex(bytesHolder.get()))
            } else {
                Log.v("Transaction", "doTransaction - result for getTag(): $result => tag missing")

            }
        }
    }

    Log.d("Transaction", "doTransaction - **** REMOVE CARD **** ")

    contactless.clean();

    return ret
}

Outcome and errors management

At the end of a transaction, the OlaOutcomeParameter structure should be retrieved for analysis. To retrieve the outcome, use the following code:

Code Block
val olaOutcomeParameterHolder = Holders.SingleObjectHolder<OlaOutcomeParameter>()
val ret = agnos.olaContactlessGetOutcome(olaOutcomeParameterHolder)
val olaOutcomeParameter = olaOutcomeParameterHolder.get()

Please refer to the Javadoc/Kotlindoc for more information on the OlaOutcomeParameter class.

The first element to verify is the value of the outcome member. In a Tap to Phone environement, its value should be either of the following:

  • SelectNext,

  • TryAgain,

  • Declined

  • OnlineRequest,

  • EndApplication

The transaction should never be accepted offline.

EndApplication outcome

In case the outcome is EndApplication, the application should look into OlaErrorIndicator element to understand what possibly caused the premature end of the transaction. To retrieve that information, one should use the following API:

Code Block
val olaErrorIndicatorHolder = Holders.SingleObjectHolder<OlaErrorIndicator>()
val ret = agnos.olaContactlessGetErrorIndicator(olaErrorIndicatorHolder)
val olaErrorIndicator: OlaErrorIndicator = errorHolder.get()

The OlaErrorIndicator class contains different levels of errors represented by the following members:

Code Block
OlaErrorL2 errorL2
OlaErrorL3 errorL3

Only one of them can be filled at once.

Please refer to the Javadoc/Kotlindoc for more information on the OlaErrorL2 and OlaErrorL3 values.