嵌入代码
VOID 操作适用于结算前或同一天的交易。
REFUND 操作适用于结算后或超过一天的交易。
DuitNow QR 付款仅支持REFUND 操作。
- Java
- Kotlin
- Xamarin
源代码是用 Java 编写的,用于原生 Android 开发。
第 1 步: 实施 ECC 库
在 .../android/app 的 build.gradle 中实现 ECC 库。
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'
...
}
第 2 步: 私钥和公钥
生成公钥和私钥。
第 2.1 步: 生成密钥
请参考 附录 4 生成 EC 密钥对。
第 2.2 步: 初始化按键
初始化将用于加密和解密过程的密钥。
TestPos(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
try {
thirdAppPrivateKey = loadECPrivateKey(reactContext.getAssets().open("ec_private_key_pkcs8.pem"));
thirdAppPublicKey = loadECPublicKey(reactContext.getAssets().open("ec_public_key.pem"));
yippiePosPublicKey = loadECPublicKey(reactContext.getAssets().open("yippiepos_public_key.key"));
String refNum = "1.0";
secretKey = generateECDHSecret(thirdAppPrivateKey, yippiePosPublicKey, refNum);
} catch (IOException ex) {
ex.printStackTrace();
}
BA = BluetoothAdapter.getDefaultAdapter();
}
第 3 步: 加载初始化密钥
从密钥文件加载初始化的密钥。
- Private Key
- Public Key
- ECDH Secret Key
对于 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;
}
对于 java 中的公钥
public static ECPublicKey loadECPublicKey(InputStream certFile) {
ECPublicKey ecPublicKey = null;
PublicKey publicKey = null;
try {
byte[] publicKeyAsBytes = new byte[certFile.available()];
certFile.read(publicKeyAsBytes);
certFile.close();
try {
String certFileContent = new String(publicKeyAsBytes)
.replace("-----BEGIN CERTIFICATE-----", "")
.replace("-----END CERTIFICATE-----", "")
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "");
publicKeyAsBytes = android.util.Base64.decode(certFileContent, android.util.Base64.DEFAULT);
} catch (Exception ex) {
ex.printStackTrace();
}
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyAsBytes);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
publicKey = keyFactory.generatePublic(publicKeySpec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException | IOException e) {
e.printStackTrace();
}
if (publicKey instanceof ECPublicKey) {
ecPublicKey = (ECPublicKey) publicKey;
}
String test = String.valueOf(publicKey);
return ecPublicKey;
}
用于在 java 中生成 ECDH 密钥
public static SecretKey generateECDHSecret(ECPrivateKey sdkPrivateKey, ECPublicKey thirdAppPublicKey, String partyVInfo) {
SecretKey secretKey = null;
try{
secretKey = ECDH.deriveSharedSecret(thirdAppPublicKey, sdkPrivateKey, null);
if(partyVInfo != null){
ConcatKDF kdf = new ConcatKDF("SHA-256");
int length = ECDH.sharedKeyLength(JWEAlgorithm.ECDH_ES,
EncryptionMethod.A128CBC_HS256);
if(partyVInfo.isEmpty()){
secretKey = kdf.deriveKey(secretKey, length, null, null, null, null, null);
} else {
secretKey = kdf.deriveKey(
secretKey,
length,
ConcatKDF.encodeDataWithLength(new byte[0]),
ConcatKDF.encodeDataWithLength(new byte[0]),
ConcatKDF.encodeDataWithLength(partyVInfo.getBytes()),
ConcatKDF.encodeIntData(length),
ConcatKDF.encodeNoData());
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
return secretKey;
}
第 4 步: 声明输入
声明请求中的所有输入参数 并创建 JSON 字符串。
注:
- 无效适用于结算前或同一天内的交易。
- 退款适用于结算后或超过一天的交易
- DuitNow QR 付款仅适用于退款。
- 无效
- 退款
无效请求参数
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 = encryptWithFormatJWE(txnID,
publicKeyASJWK.toJSONString(), secretKey, JWEAlgorithm.DIR, jsonMessage);
startActivity(REQ_VOID, jsonMessageEncrypted);
}
退款请求参数
public void receiveMessage(ReadableMap data) {
String msgVer = data.getString("msgVer");
String callerDevice = data.getString("callerDevice");
String callerDeviceVer = data.getString("callerDeviceVer");
String refundTxnID = UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
String oriTxnID = data.getString("oriTxnID");
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 jsonMessage = "{'MsgVer' : '" + msgVer +
"', 'CallerDeviceType' : '" + callerDevice +
"', 'CallerDeviceVer' : '" + callerDeviceVer +
"', 'RefundTxnID' : '" + refundTxnID +
"', 'OriTxnID' : '" + oriTxnID +
"', 'LocalTxnDTTime' : '" + txnDateTime +
"', 'Email' : '" + email +
"', 'Pwd' : '" + password +
"', 'RefundAmt' : '" + amt +
"', 'CrcyTxn' : '" + crcyCde + "'}";
ECKey publicKeyASJWK = new ECKey.Builder(Curve.P_256, thirdAppPublicKey)
.keyID(UUID.randomUUID().toString())
.build();
String jsonMessageEncrypted = encryptWithFormatJWE(txnID,
publicKeyASJWK.toJSONString(), secretKey, JWEAlgorithm.DIR, jsonMessage);
startActivity(REQ_REFUND, jsonMessageEncrypted);
}