Create Payment request and Query Payment requests require encrypting/decrypting and signing the message with the keys (Refer to Request sandbox access and request production secure keys.).
The answer message of the webhook does not require any signature.
Prerequisites:
Before you start development work, make sure you have completed the environment setup steps.
The workflow
Please follow the below workflow to sign and verify the signature while send and process webhook callback.
Workflow of sending request to BIEASES
Sign the request
The Create Payment request and Query Payment request message body requires to be signed.
Define the request object
import lombok.Data;
@Data
public class PaymentDto {
private String merchantId;
private String method;
private String format = "json";
private String charset = "UTF-8";
private String encryptType = "AES";
private String signType = "RSA";
private String sign;
private String timestamp;
private String body;
public static BieasesPaymentDto buildCreateOrder(PaymentOrderDto dto) {
return BieasesPaymentDto.builder()
.merchantId(dto.getMerchantID())
.method("bieases.trade.create")
.format("json")
.charset("utf-8")
.encryptType("AES")
.signType("RSA")
.timestamp(String.valueOf(System.currentTimeMillis()))
.body(JSON.toJSONString(dto))
.build();
}
public static BieasesPaymentDto buildQueryOrder(Map<String, String> dto, String merchantId) {
return BieasesPaymentDto.builder()
.merchantId(merchantId)
.method("bieases.trade.query")
.format("json")
.charset("utf-8")
.encryptType("AES")
.signType("RSA")
.timestamp(String.valueOf(System.currentTimeMillis()))
.body(JSON.toJSONString(dto))
.build();
}
}
Define the response object
import lombok.Data;
import java.io.Serializable;
/**
* REST API return result
*
* @author peng.shu
* @since 2024-06-24
*/
@Data
public class BieasesResponse implements Serializable {
private int code;
private String msg;
private PaymentDto data;
}
Encrypt request body and sign the request
Sign your request with your private key.
import cn.hutool.json.JSONUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class EncryptAndSignUtils {
public static final String SIGN_KEY = "sign";
/**
* encrypt body first,and then sort all params,sign all data
*
* @param param pay params
* @param encryptKey symmetric key
* @param iv encrypted vector
* @param privateKey mall private key
* @return
* @throws Exception
*/
public static BieasesPaymentDto encryptAndSign(BieasesPaymentDto param, String encryptKey, String iv, String privateKey) throws Exception {
String bizContent = param.getBody();
// encrypt
String encryptBizContent = BieasesEncrypt.encryptContent(bizContent, param.getEncryptType(), encryptKey, iv);
param.setBody(encryptBizContent);
Map<String, String> paramMap = JSONUtil.toBean(JSONUtil.toJsonStr(param), Map.class);
paramMap.remove(SIGN_KEY);
// sort params
String signContent = getSignContent(paramMap);
// generate signature
String sign = RSA2Encryptor.doSign(signContent, privateKey);
param.setSign(sign);
return param;
}
public static String getSignContent(Map<String, String> sortedParams) {
StringBuilder content = new StringBuilder();
List<String> keys = new ArrayList<String>(sortedParams.keySet());
Collections.sort(keys);
int index = 0;
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = sortedParams.get(key);
if (StringUtils.areNotEmpty(key, value)) {
content.append(index == 0 ? "" : "&").append(key).append("=").append(value);
index++;
}
}
return content.toString();
}
}
Verify signature and decrypt response body
Verify the signature.
import cn.hutool.json.JSONUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class VerifySignatureUtils {
/**
* verify sign first,and then decrypt body
*
* @param param pay params
* @param encryptKey symmetric key
* @param iv encrypted vector
* @param publicKey bieases wallet public key
* @return
* @throws Exception
*/
public static BieasesPaymentDto verifySignAndDecrypt(BieasesPaymentDto param, String encryptKey, String iv, String publicKey) {
Map<String, String> paramMap = JSONUtil.toBean(JSONUtil.toJsonStr(param), Map.class);
paramMap.remove(SIGN_KEY);
// sort params
String signContent = getSignContent(paramMap);
// verify signature
if (!AsymmetricManager.getByName(param.getSignType()).verify(signContent, param.getCharset(), publicKey, param.getSign())) {
throw new BieasesGatewayApiException("verifySign failed");
}
String bizContent = param.getBody();
// decrypt body
String encryptBizContent = BieasesEncrypt.decryptContent(bizContent, param.getEncryptType(), encryptKey, iv);
param.setBody(encryptBizContent);
return param;
}
public static String getSignContent(Map<String, String> sortedParams) {
StringBuilder content = new StringBuilder();
List<String> keys = new ArrayList<String>(sortedParams.keySet());
Collections.sort(keys);
int index = 0;
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = sortedParams.get(key);
if (StringUtils.areNotEmpty(key, value)) {
content.append(index == 0 ? "" : "&").append(key).append("=").append(value);
index++;
}
}
return content.toString();
}
}
Encrypt and decrypt utility
import com.example.demomall.entity.BieasesGatewayApiException;
import com.example.demomall.framework.reponse.ResultCode;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class AesEncrypt {
private static final String AES_ALG = "AES";
private static final String AES_CBC_PCK_ALG = "AES/CBC/PKCS5Padding";
public String encrypt(String plaintext, String keyB64, String ivB64) throws BieasesGatewayApiException {
byte[] key = Base64.getDecoder().decode(keyB64);
byte[] iv = Base64.getDecoder().decode(ivB64);
SecretKeySpec keySpec = new SecretKeySpec(key, AES_ALG);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher cipher;
byte[] encryptedBytes;
try {
cipher = Cipher.getInstance(AES_CBC_PCK_ALG);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
encryptedBytes = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
String errorMsg = String.format(ResultCode.ENCRYPT_ASE_ERROR.getMsg(),
plaintext, StandardCharsets.UTF_8);
throw new BieasesGatewayApiException(ResultCode.DECRYPT_ASE_ERROR.getCode(), errorMsg, e);
}
return Base64.getEncoder().encodeToString(encryptedBytes);
}
public String decrypt(String encrypted, String keyB64, String ivB64) throws BieasesGatewayApiException {
byte[] key = Base64.getDecoder().decode(keyB64);
byte[] iv = Base64.getDecoder().decode(ivB64);
SecretKeySpec keySpec = new SecretKeySpec(key, AES_ALG);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher cipher;
byte[] decryptedBytes;
try {
cipher = Cipher.getInstance(AES_CBC_PCK_ALG);
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] decodedBytes = Base64.getDecoder().decode(encrypted);
decryptedBytes = cipher.doFinal(decodedBytes);
} catch (Exception e) {
String errorMsg = String.format(ResultCode.DECRYPT_ASE_ERROR.getMsg(),
encrypted, StandardCharsets.UTF_8);
throw new BieasesGatewayApiException(ResultCode.DECRYPT_ASE_ERROR.getCode(), errorMsg, e);
}
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
}
Example of sign and verify signature
import com.example.demomall.framework.encrypt.code.Base64;
import javax.crypto.Cipher;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
public class RSAEncryptor extends BaseAsymmetricEncryptor {
public static String doSign(String content, String privateKey) throws Exception {
PrivateKey priKey = getPrivateKeyFromPKCS8(BieasesConstants.SIGN_TYPE_RSA,
new ByteArrayInputStream(privateKey.getBytes()));
Signature signature = Signature.getInstance(BieasesConstants.SIGN_SHA256RSA_ALGORITHMS);
signature.initSign(priKey);
signature.update(content.getBytes(StandardCharsets.UTF_8));
byte[] signed = signature.sign();
return Base64.encodeBase64String(signed);
}
protected boolean doVerify(String content, String charset, String publicKey, String sign) throws Exception {
PublicKey pubKey = getPublicKeyFromX509("RSA",
new ByteArrayInputStream(publicKey.getBytes()));
Signature signature = Signature.getInstance(getSignAlgorithm());
signature.initVerify(pubKey);
if (StringUtils.isEmpty(charset)) {
signature.update(content.getBytes());
} else {
signature.update(content.getBytes(charset));
}
return signature.verify(Base64.decodeBase64(sign.getBytes()));
}
public static PrivateKey getPrivateKeyFromPKCS8(String algorithm,
InputStream ins) throws Exception {
if (ins == null || StringUtils.isEmpty(algorithm)) {
return null;
}
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
byte[] encodedKey = StreamUtil.readText(ins).getBytes();
encodedKey = Base64.decodeBase64(encodedKey);
return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));
}
public static PublicKey getPublicKeyFromX509(String algorithm,
InputStream ins) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
StringWriter writer = new StringWriter();
StreamUtil.io(new InputStreamReader(ins), writer);
byte[] encodedKey = writer.toString().getBytes();
encodedKey = Base64.decodeBase64(encodedKey);
return keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
}
}