Skip to main content

Embed Code

The source code is written in Java for native Android development.


Step 1 : Implement ECC Libraries

Implement ECC Libraries in build.gradle from .../android/app.

android {
...
defaultConfig {
...
multiDexEnabled true
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

packagingOptions {
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/LICENSE.md'
exclude 'META-INF/NOTICE.md'
}
...
}

dependencies {
...
// Crypto
implementation 'com.nimbusds:nimbus-jose-jwt:8.9'
implementation 'org.bouncycastle:bcprov-jdk15on:1.64'
...
}

Step 2 : Declare Input

Declare all input parameters in the request and create a JSON string.

Note
  • Void applicable for transactions before settlement or within the same day.
  • Refund applicable for transactions after settlement or beyond one day.
  • DuitNow QR payments are eligible for Refund only.
Void Request Parameters
public void receiveMessage(ReadableMap data) {
String msgVer = data.getString("msgVer");
String callerDevice = data.getString("callerDevice");
String callerDeviceVer = data.getString("callerDeviceVer");
String txnID = UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
String txnDateTime = data.getString("txnDateTime");
String email = data.getString("email");
String password = data.getString("password");
String crcyCde = data.getString("crcyCde");
String amt = data.getString("amt");
String stan = data.getString("stan");
String authIdResp = data.getString("authIdResp");
String rrn = data.getString("rrn");
String mrn = data.getString("mrn");

String jsonMessage = "{'TxnTyp' : '" + TestPos.TransactionType.VOD +
"', 'MsgVer' : '" + msgVer +
"', 'CallerDeviceType' : '" + callerDevice +
"', 'CallerDeviceVer' : '" + callerDeviceVer +
"', 'TxnID' : '" + txnID +
"', 'LocalTxnDTTime' : '" + txnDateTime +
"', 'Email' : '" + email +
"', 'Pwd' : '" + password +
"', 'AmtTxn' : '" + amt +
"', 'CrcyTxn' : '" + crcyCde +
"', 'STAN' : '" + stan +
"', 'AuthIdResp' : '" + authIdResp +
"', 'RRN' : '" + rrn +
"', 'MRN' : '" + mrn + "'}";

ECKey publicKeyASJWK = new ECKey.Builder(Curve.P_256, thirdAppPublicKey)
.keyID(UUID.randomUUID().toString())
.build();

String jsonMessageEncrypted = UtilCrypto.encryptWithFormatJWE(txnID,
publicKeyASJWK.toJSONString(), secretKey, JWEAlgorithm.DIR, jsonMessage);

startActivity(REQ_VOID, jsonMessageEncrypted);
}

Step 3 : Encrypt String

After obtaining the jsonMessage as a string, encrypt it using the encryptWithFormatJWE function.

In UtilCrypto.java
public static String encryptWithFormatJWE(String transactionID, String sdkPublicKeyAsJWK, SecretKey secretKey, JWEAlgorithm jweAlgorithm, String message) {
String encryptedMessage = "";

try {
//header
JWEHeader.Builder builder = new JWEHeader.Builder(jweAlgorithm, EncryptionMethod.A128CBC_HS256);
if (!TextUtils.isEmpty(transactionID)) {
builder.keyID(transactionID);
}
if (!TextUtils.isEmpty(sdkPublicKeyAsJWK)) {
builder.ephemeralPublicKey(ECKey.parse(sdkPublicKeyAsJWK));
}
JWEHeader header = builder.build();

//encrypt
if(jweAlgorithm == JWEAlgorithm.DIR) {
Provider bouncyCastleProvider = BouncyCastleProviderSingleton.getInstance();
Security.addProvider((bouncyCastleProvider));
JWEObject jweObject = new JWEObject(header, new Payload(message));
jweObject.encrypt(new DirectEncrypter(secretKey));
encryptedMessage = jweObject.serialize();
} else if (jweAlgorithm == JWEAlgorithm.ECDH_ES) {
// build JWE structure
JWECryptoParts jweParts = ContentCryptoProvider.encrypt(header, message.getBytes(), secretKey, null, new JWEJCAContext());

// JWE Protected Header
StringBuilder stringBuilder = new StringBuilder(header.toBase64URL().toString());
stringBuilder.append('.');

// Encrypted Key
if (jweParts.getEncryptedKey() != null) {
stringBuilder.append(jweParts.getEncryptedKey().toString());
}
stringBuilder.append('.');

// Initialization Vector
if (jweParts.getInitializationVector() != null) {
stringBuilder.append(jweParts.getInitializationVector().toString());
}
stringBuilder.append('.');

// Ciphertext
stringBuilder.append(jweParts.getCipherText().toString());
stringBuilder.append('.');

// Authentication Tag
if (jweParts.getAuthenticationTag() != null) {
stringBuilder.append(jweParts.getAuthenticationTag().toString());
}
encryptedMessage = stringBuilder.toString();
} else {
Log.d("Tag", "Unsupported other algorithms");
}
} catch (Exception ex) {
ex.printStackTrace();
}
return encryptedMessage;
}

Step 4 : Perform App-to-app calling using INTENT

To use INTENT, both the Kayaaku POS app and the merchant's POS app must be installed on the same device.

Use startActivityForResult() as per the sample code below.

INTENT
// start intent activity
private void startActivity (int messageType, String encryptedMessage) {
if (messageType == REQ_SALE) {
Intent intent = new Intent(ACTION_SALE); // ACTION_SALE = "com.finexuscards.yippiepos.xxx.SALES"
intent.putExtra("Data", encryptedMessage);
startActivityForResult(intent, REQ_SALE); // REQ_SALE = 0
} else if (messageType == REQ_VOID) {
Intent intent = new Intent(ACTION_VOID); // ACTION_VOID = "com.finexuscards.yippiepos.xxx.VOID"
intent.putExtra("Data", encryptedMessage);
startActivityForResult(intent, REQ_VOID); // REQ_VOID = 1
} else if (messageType == REQ_REFUND) {
Intent intent = new Intent(ACTION_REFUND); // ACTION_VOID = "com.finexuscards.yippiepos.xxx.REFUND"
intent.putExtra("Data", encryptedMessage);
startActivityForResult(intent, REQ_REFUND); // REQ_REFUND = 2
}
}

// get response from onActivityResult()
protected void onActivityResult(int requestCode,int resultCode,Intent data){
super.onActivityResult(requestCode,resultCode,data);
String encryptedResponse = data.getStringExtra("Data");
}

Step 5 : Private and Public Keys

Generate both the public and private keys.

Step 5.1 : Generate Keys

Please refer to Appendix 4 to generate EC Keypair.


Step 5.2 : Initialize Keys

Initialize the keys that will be used for the encryption and decryption process.

TestPos(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
try {
thirdAppPrivateKey = UtilCrypto.loadECPrivateKey(reactContext.getAssets().open("ec_private_key_pkcs8.pem"));
thirdAppPublicKey = UtilCrypto.loadECPublicKey(reactContext.getAssets().open("ec_public_key.pem"));
yippiePosPublicKey = UtilCrypto.loadECPublicKey(reactContext.getAssets().open("yippiepos_public_key.key"));
String refNum = "1.0";
secretKey = UtilCrypto.generateECDHSecret(thirdAppPrivateKey, yippiePosPublicKey, refNum);
} catch (IOException ex) {
ex.printStackTrace();
}

BA = BluetoothAdapter.getDefaultAdapter();
}

Step 6 : Create Class

Create a new Java class named UtilCrypto for the encryption and decryption functions.


Step 7 : Load the Initialized Keys

Load the initialized keys from the key file.

For Private Key in UtilCrypto.java
public static ECPrivateKey loadECPrivateKey(InputStream certFile) {
ECPrivateKey ecPrivateKey = null;
PrivateKey privateKey = null;
try {
byte[] encodedPrivateKey = new byte[certFile.available()];
certFile.read(encodedPrivateKey);
certFile.close();
try {
String certFileContent = new String(encodedPrivateKey)
.replace("-----BEGIN CERTIFICATE-----", "")
.replace("-----END CERTIFICATE-----","")
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "");
encodedPrivateKey = android.util.Base64.decode(certFileContent, android.util.Base64.DEFAULT);
} catch (Exception ex) {
ex.printStackTrace();
}
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encodedPrivateKey);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
privateKey = keyFactory.generatePrivate(privateKeySpec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException | IOException e) {
e.printStackTrace();
}
if (privateKey instanceof ECPrivateKey){
ecPrivateKey = (ECPrivateKey) privateKey;
}
return ecPrivateKey;
}

Step 8 : Decrypt Response Message

Decrypt the response message retrieved from the Kayaaku POS application using the decryptWithFormatJWE function.

In UtilCrypto.java
public static String decryptWithFormatJWE(SecretKey secretKey, String message) {
String decryptedMessage = "";
try {
// parse message to Object
JWEObject jweObject = JWEObject.parse(message);

// get header
JWEAlgorithm algorithm = jweObject.getHeader().getAlgorithm();
EncryptionMethod encryptionMethod = jweObject.getHeader().getEncryptionMethod();

// decrypt
if (algorithm.equals(JWEAlgorithm.DIR)) {
if (encryptionMethod == EncryptionMethod.A128CBC_HS256 || encryptionMethod == EncryptionMethod.A128GCM) {
Provider bouncyCastleProvider = BouncyCastleProviderSingleton.getInstance();
Security.addProvider(bouncyCastleProvider);

// start decrypting
jweObject.decrypt(new DirectDecrypter(secretKey));
decryptedMessage = jweObject.getPayload().toString();
} else {
Log.d("Tag", "Unsupported other encryption method:" + encryptionMethod);
}
} else {
Log.d("Tag", "Unsupported other algorithms");
}
} catch (Exception ex) {
Log.e("Tag", "decryptedMessage Failed:" + ex.getMessage());
ex.printStackTrace();
}
return decryptedMessage;
}