小米支付
小米支付不像微信/支付宝支付那样的流程,而是直接由客户端APP进行发起的支付回调。
小米支付官网地址 https://dev.mi.com/distribute/doc/details?pId=1150#_21
个人支付案例git地址[微信支付/支付宝支付/华为支付/苹果支付/小米支付]:https://gitee.com/wazk2008/demo-pay
支付回调
yml配置
xiaomi:
appid: 自己的appid
app-secret: 自己的app对应的密钥
回调参数
@Data
public class PayMiCallbackRequest implements Serializable {
// 游戏ID
private String appId;
// 开发商订单ID
private String cpOrderId;
// 开发商透传信息
private String cpUserInfo;
// 用户ID
private String uid;
// 游戏平台订单ID
private String orderId;
// 订单状态,TRADE_SUCCESS 代表成功
private String orderStatus;
// 支付金额,单位为分,即0.01米币
private String payFee;
// 商品代码
private String productCode;
// 商品名称
private String productName;
// 商品数量
private String productCount;
// 支付时间,格式 yyyy-MM-dd HH:mm:ss
private String payTime;
// 签名
private String signature;
}
回调Controller
@RestController
@RequestMapping(value = "/pay")
@Slf4j
public class PayController {
@Autowired
private MiService miService;
@PostMapping(value = "/miPayCallback")
public Result<Object> payMiCallback (@RequestBody PayMiCallbackRequest payMiCallbackRequest) {
try {
log.info("小米支付回调接口开始执行,请求参数:{}", JSON.toJSON(payMiCallbackRequest));
miService.payCallback(payMiCallbackRequest);
log.info("小米支付回调接口执行成功,请求参数:{}", JSON.toJSON(payMiCallbackRequest));
return Result.getSuccess();
} catch (ResponseException e) {
log.error("小米支付回调接口执行失败1,请求参数:{},异常原因:{}", JSON.toJSON(payMiCallbackRequest), e.getMessage());
e.printStackTrace();
return Result.getFail(e.getCode(), e.getMessage());
} catch (Exception e) {
log.error("小米支付回调接口执行失败2,请求参数:{},异常原因:{}", JSON.toJSON(payMiCallbackRequest), e.getMessage());
e.printStackTrace();
return Result.getFail(ResultCode.INTERNAL_SERVER_ERROR);
}
}
}
回调Service
@Service
public class MiService {
@Value("${xiaomi.appid}")
private String appId;
@Value("${xiaomi.app-secret}")
private String appSecret;
@Autowired
private RestTemplate restTemplate;
private static final String MAC_NAME = "HmacSHA1";
private static final String MI_QUERY_ORDER_URL = "https://mis.migc.xiaomi.com/api/biz/service/queryOrder.do";
public void payCallback(PayMiCallbackRequest payMiCallbackRequest) {
if (!"TRADE_SUCCESS".equals(payMiCallbackRequest.getOrderStatus())) {
throw new ResponseException(ResultCode.MI_PAY_CALLBACK_ORDER_STATUS_ERROR);
}
if (!appId.equals(payMiCallbackRequest.getAppId())) {
throw new ResponseException(ResultCode.MI_PAY_CALLBACK_APPID_ERROR);
}
// 校验签名
String encryptText = generateEncryptText(convertOrderPayXiaomiCallbackRequestToMap(payMiCallbackRequest));
String checkSignature = hmacSHA1Encrypt(encryptText, appSecret);
if (!payMiCallbackRequest.getSignature().equals(checkSignature)) {
// 签名校验失败
throw new ResponseException(ResultCode.MI_PAY_CALLBACK_VALID_SIGN_ERROR);
}
// 请求小米支付服务端,获取小米支付状态
boolean validMiRemoteService = validMiRemoteService(payMiCallbackRequest);
if (!validMiRemoteService) {
// 小米支付回调处理错误,请求小米校验订单状态错误
throw new ResponseException(ResultCode.MI_PAY_CALLBACK_ORDER_STATUS_ERROR);
}
// todo 校验订单是否存在
String outTradeNo = payMiCallbackRequest.getCpOrderId();
// todo 校验订单是否已支付
// todo 校验支付金额
Double buyerAmount = Double.valueOf(payMiCallbackRequest.getPayFee());
// todo 修改支付和订单的状态
}
// 生成验签字符串
private String generateEncryptText (Map<String, String> map) {
// 排序
Set<String> keySet = map.keySet();
String[] keyArray = keySet.toArray(new String[keySet.size()]);
Arrays.sort(keyArray);
StringBuilder sb = new StringBuilder();
for (String k : keyArray) {
sb.append("&").append(k).append("=").append(map.get(k).trim());
}
String sign = sb.toString();
return sign.replaceFirst("&", "");
}
// 将 PayMiCallbackRequest 中的非空属性全部转为 map 格式
private Map<String, String> convertOrderPayXiaomiCallbackRequestToMap (PayMiCallbackRequest payMiCallbackRequest){
Class<? extends PayMiCallbackRequest> clazz = payMiCallbackRequest.getClass();
Field[] fields = clazz.getDeclaredFields();
Map<String, String> map = new HashMap<>();
try {
for (Field field : fields) {
field.setAccessible(true);
String fieldName = field.getName();
Object fieldValue = field.get(payMiCallbackRequest);
if (!Objects.isNull(fieldValue) && !StringUtils.isEmpty(fieldValue.toString()) && !"signature".equals(fieldName) && !"ivyTransactionId".equals(fieldName)) {
map.put(fieldName, URLDecoder.decode(fieldValue.toString(), CharEncoding.UTF_8));
}
}
} catch (Exception e) {
throw new ResponseException(ResultCode.MI_PAY_CALLBACK_PARAMS_CONVERT_ERROR);
}
return map;
}
// 使用 HMAC-SHA1 签名方法对对encryptText进行签名
private String hmacSHA1Encrypt( String encryptText, String encryptKey) {
byte[] data = encryptKey.getBytes(StandardCharsets.UTF_8);
// 根据给定的字节数组构造一个密钥,第二参数指定一个密钥算法的名称
SecretKey secretKey = new SecretKeySpec( data, MAC_NAME );
// 生成一个指定 Mac 算法 的 Mac 对象
Mac mac = null;
try {
mac = Mac.getInstance( MAC_NAME );
// 用给定密钥初始化 Mac 对象
mac.init( secretKey );
} catch (Exception e) {
// 生成签名错误
throw new ResponseException(ResultCode.MI_PAY_CALLBACK_GENERATE_SIGN_ERROR);
}
byte[] text = encryptText.getBytes(StandardCharsets.UTF_8);
// 完成 Mac 操作
byte[] digest = mac.doFinal( text );
StringBuilder sBuilder = bytesToHexString( digest );
return sBuilder.toString();
}
private StringBuilder bytesToHexString( byte[] bytesArray ){
if ( bytesArray == null ){
return null;
}
StringBuilder sBuilder = new StringBuilder();
for ( byte b : bytesArray ){
String hv = String.format("%02x", b);
sBuilder.append( hv );
}
return sBuilder;
}
// 向小米服务端发起校验
private boolean validMiRemoteService (PayMiCallbackRequest payMiCallbackRequest) {
// 请求小米支付服务端,获取小米支付状态
String cpOrderId = payMiCallbackRequest.getCpOrderId();
String uid = payMiCallbackRequest.getUid();
Map<String, String> paramMap = new HashMap<>();
paramMap.put("appId", appId);
paramMap.put("cpOrderId", payMiCallbackRequest.getCpOrderId());
paramMap.put("uid", uid);
String newSignature = hmacSHA1Encrypt(generateEncryptText(paramMap), appSecret);
String url = MI_QUERY_ORDER_URL + "?appId=" + appId + "&cpOrderId=" + cpOrderId + "&uid=" + uid + "&signature=" + newSignature;
JSONObject miResponse = sendHttpRequest(url);
return "TRADE_SUCCESS".equals(miResponse.getString("orderStatus"));
}
public JSONObject sendHttpRequest(String url) {
HttpHeaders headers = new HttpHeaders();
HttpEntity<String> httpEntity = new HttpEntity(headers);
ResponseEntity<String> exchange = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class, new Object[0]);
String response = (String)exchange.getBody();
return JSON.parseObject(response);
}
}