Purpose
This page aims at helping L3 applications developers to use the OLA Java API.
...
Table of Contents | ||||
---|---|---|---|---|
|
Basic transaction
A basic OLA transaction includes the following steps:
OLA classes
...
instantiation (once at startup)
Code Block |
---|
private val finalola: 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 = "/data/data/ca.amadis.tester/" val cakeys private final OlaEmv emv;= 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, 0x01) val traceLevel private final OlaContactless contactless; private final Dev dev; this.ola = Ola.AccessLowLevel(); this.emv = OlaEmv.AccessLowLevel(); this.contactless = OlaContactless.AccessLowLevel(); this.dev = new Dev(this, this, this, false, "/data/data/<application_name>/"); /* internal storage path example */ |
OLA component initialization (Ola.ola_initialize)
Code Block |
---|
private void txnInit() {
final String path = "/data/data/com.amadis.agnos_tester/";
final String CAKeys = path + "CAKeys";
final String CRL = path + "CRL";
final String EFL = path + "EFL";
final String langIni = path + "lang.ini";
final byte[] params = {0x00, 0x00, 0x00, 0x10, 0x05, 0x00};
final byte paramsLen = (byte)params.length;
final byte traceLevel = (byte)5;
ola.ola_initialize(new OlaInit(path, CAKeys, CRL, EFL, langIni, params, paramsLen, traceLevel));
} |
Preprocessing (OlaContactless.ola_contactless_preprocess)
Code Block |
---|
private OlaContactlessResult txnPreProcess() { OlaContactlessResult ret = contactless.ola_contactless_preprocess(= byteArrayOf(0xdf.toByte(), 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) out.write(sredMode) ola.initializeAtStartUp(out.toByteArray()) } catch (e: IOException) { } } |
AID configuration
Code Block |
---|
private fun terminalContactlessConfig() {
Log.v("Transaction", "terminalContactlessConfig")
val contactlessCfg = arrayOf(
ContactlessCfg(AID.FromBytes(byteArrayOf(0xA0.toByte(), 0x00, 0x00, 0x00, 0x04, 0x10, 0x10)),
true,
2,
mcCfg),
ContactlessCfg(AID.FromBytes(byteArrayOf(0xA0.toByte(), 0x00, 0x00, 0x00, 0x04, 0x20, 0x10)),
true,
2,
mcCfg),
...)
contactless.flushAIDSupported()
for (config in contactlessCfg) {
contactless.addAIDSupported(
config.aid,
config.partial,
config.kernelId.toByte(),
config.additionalData
)
}
contactless.commitSupportedAIDs()
} |
Keys configuration
Code Block |
---|
private fun terminalPublicKeyConfig() {
Log.v("Transaction", "terminalPublicKeyConfig")
val publicKeyCfg = arrayOf(
/* MasterCard */
PublicKeyCfg(byteArrayOf(0xa0.toByte(), 0x00, 0x00, 0x00, 0x03),
0x92.toByte(),
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)) // 9A emv.setTag(OlaTag.TRANSACTION_TIME, byteArrayOf(0x12, 0x00, 0x00)) new byte[]{// 9F21 emv.setTag(OlaTag.AMOUNT_AUTHORISED, byteArrayOf(0x00, 0x00, 0x00, 0x00, 0x0A}0x01, 0x50)) // 9F02 new BCD.BCD_YYMMDD(20, 12, 31),emv.setTag(OlaTag.AMOUNT_OTHER_NUM, byteArrayOf(0x00, 0x00, 0x00, 0x00, 0x00, 0x50)) // 9F03 new BCD.BCD_YYMMDD(12, 59, 59),emv.setTag(OlaTag.TRANSACTION_CURRENCY_CODE, byteArrayOf(0x08, 0x40)) // 5F2A new BCD(0, 0, 0, 1), emv.setTag(OlaTag.TRANSACTION_CURRENCY_EXPONENT, byteArrayOf(0x02)) // 5F36 emv.setTag(OlaTag.TRANSACTION_TYPE, byteArrayOf(0x00)) // 9C /* * Other possible tags to set here: * Account Type (5F57) * Transaction Category newCode BCD(0)9F53) * * All other tags must be set through the combinations! */ emv.setTag(0x9F53, byteArrayOf(0x11)); } |
Preprocessing
Code Block |
---|
private fun txnPreProcess(): OlaError { Log.v("Transaction", "txnPreProcess") val ret = contactless.preprocess() if (ret != OlaContactlessResultOlaError.OLA_OK) { Log.e("OLA-TESTERTransaction", "txnPreProcess - contactless pre-process failed"); } return ret; } |
Card detection
Code Block |
---|
private shortfun txnGetCard(): Int { Log.dv("OLA-TESTERTransaction", "txnGetCard - **** PRESENT CARD ****"); intval foundTechnos = dev.technoPolling(6000030); l("ResultLog.v("Transaction", "txnGetCard - result of foundTechnos: $foundTechnos"+ foundTechnos); return if (foundTechnos == 4) { return clcEMV_CARD; //TODO remove hard-coding } else { return 0; } } |
...
Do transaction
Code Block |
---|
private OlaContactlessResultfun txnBuildCandidateListdoTransaction(): OlaError { Log.v("Transaction", "doTransaction - buildCandidateList") SingleObjectHolder<Integer>val nbCandidatesHolder = new SingleObjectHolder<Integer>SingleObjectHolder<Int>(); OlaContactlessResultvar ret = contactless.ola_contactless_build_candidate_listbuildCandidateList(nbCandidatesHolder); if (ret != OlaContactlessResultOlaError.OLA_OK) { Outcome.showOutcome(contactless, ret); } Log.v("Transaction", "doTransaction - nb returnof ret; } |
Select application (OlaContactless.ola_contactless_final_select_candidate)
Code Block |
---|
private OlaContactlessResult txnFinalSelectCandidate() {candidates: ${nbCandidatesHolder.get()}") Log.v("Transaction", "doTransaction - finalSelectCandidate") val kernel_id_holder = new SingleObjectHolder<Byte>(); OlaContactlessResult ret = contactless.ola_contactless_final_select_candidatefinalSelectCandidate(1, kernel_id_holder); if (ret != OlaContactlessResultOlaError.OLA_OK) { Outcome.showOutcome(contactless, ret); } return ret; } |
Start transaction (OlaContactless.ola_contactless_do_transaction)
Code Block |
---|
private void txnDoTransaction() { SingleObjectHolder<OlaEntryMode> entryMode_holder = new SingleObjectHolder<OlaEntryMode>(); OlaContactlessResult ret = contactless.ola_contactless_do_transaction(entryMode_holder); showOutcome(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.