官方文档:https://developers.weixin.qq.com/minigame/dev/api-backend/midasCancelPay.html
签名分析(官网签名介绍:https://developers.weixin.qq.com/minigame/dev/tutorial/open-ability/midas-signature.html):
//请求的url都是需要加上“sndbox”的
POST https://api.weixin.qq.com/cgi-bin/midas/sandbox/cancelpay?access_token=ACCESS_TOKEN
POST https://api.weixin.qq.com/cgi-bin/midas/sandbox/getbalance?access_token=ACCESS_TOKEN
POST https://api.weixin.qq.com/cgi-bin/midas/sandbox/pay?access_token=ACCESS_TOKEN
POST https://api.weixin.qq.com/cgi-bin/midas/sandbox/present?access_token=ACCESS_TOKEN
//一、对参与米大师签名的参数按照key=value的格式,并按照参数名ASCII字典序升序排序如下:
stringA="appid=wx1234567&offer_id=12345678&openid=odkx20ENSNa2w5y3g_qOkOvBNM1g&pf=android&ts=1507530737&zone_id=1";
//二、拼接uri、method和米大师密钥:
//1.此处uri需要接口的不同,uri也不同,查询用getbalance,取消订单用cancelpay
//2.此处的uri如果是沙箱测试环境则为/cgi-bin/midas/sandbox/getbalance,
//3.secret为开发平台上虚拟支付的AppKey,对象的沙箱环境和线上环境
stringSignTemp=stringA+"&org_loc=/cgi-bin/midas/getbalance&method=POST&secret=zNLgAGgqsEWJOg1nFVaO5r7fAlIQxr1u"
//三、把米大师密钥作为key,使用HMAC-SHA256得到签名,如果要得到mg_sig,则需要使用session_key作为key进行加密
sig=hmac_sha256(key,stringSignTemp)
如果是正式环境需要改为如下,包括签名的时候(本人就是在签名的时候测试坑了好久,一直没发现签名也是分沙箱和正式环境点,而且记住修改接口名。如:取消订单 /cgi-bin/midas/cancelpay;获取余额: /cgi-bin/midas/getbalance;签名时也是一样)
签名时,不可将非必传参数加入到签名中,加入会报 90009 mg_sig error 错误。
一共四个接口:
1.取消订单(midasCancelPay)
2.获取游戏币余额(midasGetBalance)
3.扣除游戏币(midasPay)
4.给用户赠送游戏币(midasPresent)
接口代码(Controller)如下:
@ControllerLog(Message = "获取游戏币余额")
@PostMapping("/api/midas-balance")
public ResponseResult midasGetBalance(@RequestBody JSONObject param) {
JSONObject params = new JSONObject();
String code = param.getString("code");
if (code == null) {
return ResponseResult.error(ErrCode.PARAM_ERROR, "认证已过期,请尝试重新打开");
}
try {
//此处是用到WxMaServiceImpl,根据前端传参code(与登录类似),获取openid
WxMaJscode2SessionResult session = userWxBaseService.getUserService().getSessionInfo(code);
if (session != null) {
String openId = session.getOpenid();
// 获取固定参数
params = MidasUtils.putParams(userWxPayConfig);
params.put("openid", openId);
params.put("method", "getbalance");
//将params传入自定义签名方法获取sig
params.put("sig", MidasUtils.getSig(params));
//获取accessToken和sessionKey,用于签名和传参
String accessToken = userWxBaseService.getAccessToken();
params.put("access_token", accessToken);
params.put("session_key", session.getSessionKey());
//将params传入自定义签名方法获取mg_sig
String mpSig = MidasUtils.getMpSig(params);
params.put("mp_sig", mpSig);
// 移除不需要的参数(如果不将不用的参数拿掉是会报错点)
params = MidasUtils.removeParams(params);
//拼接url,并将其与params参数一同传入doPost方法中,进行请求
String url = "https://api.weixin.qq.com/cgi-bin/midas/sandbox/getbalance?access_token=" + accessToken;
// Post请求
HttpResponse response = MidasUtils.doPost(params, url);
// 检验返回码
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
//使用MidasBack(自定义entity用于接收返回数据)
MidasBack midasBack = MidasUtils.changeObject(response.getEntity().getContent());
// 判断errcode
if (midasBack.getErrcode() != 0) {
return ResponseResult.error(ErrCode.BUSINESS_ERROR, "请求失败");
}
return ResponseResult.success(midasBack);
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return ResponseResult.error(ErrCode.BUSINESS_ERROR, "请求失败");
}
@ControllerLog(Message = "给用户赠送游戏币")
@PostMapping("/api/midas-present")
public ResponseResult midasPresent(@RequestBody JSONObject param) {
JSONObject params = new JSONObject();
String code = param.getString("code"); // 获取code
String billNo = param.getString("bill_no"); // 获取订单号
// 应判断订单号是否正确
Orders orders = ordersService.queryByNumber(billNo);
if(orders == null) {
return ResponseResult.error(ErrCode.PARAM_ERROR,"订单不存在");
}
if(!orders.getPayStatus().equals(OrderStatus.UNPAID)) {
return ResponseResult.error(ErrCode.BUSINESS_ERROR,"订单未支付");
}
String presentCounts = param.getString("present_counts"); // 获取赠送数量
if (code == null) {
return ResponseResult.error(ErrCode.PARAM_ERROR, "认证已过期,请尝试重新打开");
}
try {
WxMaJscode2SessionResult session = userWxBaseService.getUserService().getSessionInfo(code);
if (session != null) {
String openId = session.getOpenid();
// 获取固定参数
params = MidasUtils.putParams(userWxPayConfig);
params.put("openid", openId);
params.put("bill_no", billNo);
params.put("present_counts", presentCounts);
params.put("method", "present");
String sig = MidasUtils.getSig(params);
if (sig == null) {
return ResponseResult.error(ErrCode.BUSINESS_ERROR, "验签失败");
}
params.put("sig", sig);
String accessToken = userWxBaseService.getAccessToken();
params.put("access_token", accessToken);
params.put("session_key", session.getSessionKey());
String mpSig = MidasUtils.getMpSig(params);
if (mpSig == null) {
return ResponseResult.error(ErrCode.BUSINESS_ERROR, "验签失败");
}
// 移除不需要的参数
params = MidasUtils.removeParams(params);
params.put("mp_sig", mpSig);
String url = "https://api.weixin.qq.com/cgi-bin/midas/sandbox/present?access_token=" + accessToken;
// Post请求
HttpResponse response = MidasUtils.doPost(params, url);
// 检验返回码
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
MidasBack midasBack = MidasUtils.changeObject(response.getEntity().getContent());
// 判断errcode
if (midasBack.getErrcode() == 0) {
return ResponseResult.success(midasBack);
}else if(midasBack.getErrcode() == 90012) {
//相同的订单不允许操作多次
return ResponseResult.error(ErrCode.BUSINESS_ERROR, "订单不允许重复该操作");
}
return ResponseResult.error(ErrCode.BUSINESS_ERROR, "请求失败");
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return ResponseResult.error(ErrCode.BUSINESS_ERROR, "请求失败");
}
@ControllerLog(Message = "扣除游戏币")
@PostMapping("/api/midas-pay")
public ResponseResult midasPay(@RequestBody JSONObject param) {
JSONObject params = new JSONObject();
String code = param.getString("code"); // 获取code
String billNo = param.getString("bill_no"); // 获取订单号
// 应判断订单号是否正确
String amt = param.getString("amt"); // 获取扣除数量
String payItem = param.getString("pay_item"); // 获取道具名称(非必选)
String appRemark = param.getString("app_remark"); // 获取备注(非必选)
if (code == null) {
return ResponseResult.error(ErrCode.PARAM_ERROR, "认证已过期,请尝试重新打开");
}
try {
WxMaJscode2SessionResult session = userWxBaseService.getUserService().getSessionInfo(code);
if (session != null) {
String openId = session.getOpenid();
// 获取固定参数
params = MidasUtils.putParams(userWxPayConfig);
params.put("openid", openId);
params.put("bill_no", billNo);
params.put("amt", amt);
params.put("method", "pay");
params.put("sig", MidasUtils.getSig(params));
String accessToken = userWxBaseService.getAccessToken();
params.put("access_token", accessToken);
params.put("session_key", session.getSessionKey());
params.put("mp_sig", MidasUtils.getMpSig(params));
// 移除不需要的参数
params = MidasUtils.removeParams(params);
String url = "https://api.weixin.qq.com/cgi-bin/midas/sandbox/pay?access_token=" + accessToken;
// Post请求
HttpResponse response = MidasUtils.doPost(params, url);
// 检验返回码
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
MidasBack midasBack = MidasUtils.changeObject(response.getEntity().getContent());
// 判断errcode
if (midasBack.getErrcode() != 0) {
return ResponseResult.error(ErrCode.BUSINESS_ERROR, "请求失败");
}
return ResponseResult.success(midasBack);
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return ResponseResult.error(ErrCode.BUSINESS_ERROR, "请求失败");
}
@ControllerLog(Message = "扣除游戏币")
@PostMapping("/api/midas-cancel-pay")
public ResponseResult midasCancelPay(@RequestBody JSONObject param) {
JSONObject params = new JSONObject();
String code = param.getString("code"); // 获取code
String billNo = param.getString("bill_no"); // 获取订单号
// 应判断订单号是否正确
String payItem = param.getString("pay_item"); // 获取道具名称(非必选)
if (code == null) {
return ResponseResult.error(ErrCode.PARAM_ERROR, "认证已过期,请尝试重新打开");
}
try {
WxMaJscode2SessionResult session = userWxBaseService.getUserService().getSessionInfo(code);
if (session != null) {
String openId = session.getOpenid();
// 获取固定参数
params = MidasUtils.putParams(userWxPayConfig);
params.put("openid", openId);
params.put("bill_no", billNo);
params.put("method", "cancelpay");
params.put("sig", MidasUtils.getSig(params));
String accessToken = userWxBaseService.getAccessToken();
params.put("access_token", accessToken);
params.put("session_key", session.getSessionKey());
params.put("mp_sig", MidasUtils.getMpSig(params));
// 移除不需要参数
params = MidasUtils.removeParams(params);
String url = "https://api.weixin.qq.com/cgi-bin/midas/sandbox/cancelpay?access_token=" + accessToken;
// Post请求
HttpResponse response = MidasUtils.doPost(params, url);
// 检验返回码
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
MidasBack midasBack = MidasUtils.changeObject(response.getEntity().getContent());
// 判断errcode
if (midasBack.getErrcode() != 0) {
return ResponseResult.error(ErrCode.BUSINESS_ERROR, "请求失败");
}
return ResponseResult.success(midasBack);
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return ResponseResult.error(ErrCode.BUSINESS_ERROR, "请求失败");
}
工具类MidasUtils:
public class MidasUtils {
// 加密sig
public static String getSig(JSONObject params) {
String stringA = "";
if(params.get("amt") != null) {
stringA += "amt=" + params.get("amt") + "&appid=" + params.get("appid");
}else {
stringA += "appid=" + params.get("appid");
}
if (params.get("bill_no") != null) {
stringA += "&bill_no=" + params.get("bill_no");
}
stringA += "&offer_id=" + params.get("offer_id") + "&openid=" + params.get("openid");
stringA += "&pf=" + params.get("pf");
if (params.get("present_counts") != null) {
stringA += "&present_counts=" + params.get("present_counts");
}
stringA += "&ts=" + params.get("ts") + "&zone_id=1";
String stringSignTemp = stringA
+ "&org_loc=/cgi-bin/midas/sandbox/"+params.get("method")+"&method=POST&secret="+params.get("secret");
String sig = hmac_sha256(stringSignTemp, params.get("secret").toString());
return sig;
}
// 加密mg_sig
public static String getMpSig(JSONObject params) {
String stringA = "access_token=" + params.get("access_token");
if(params.get("amt") != null) {
stringA += "&amt=" + params.get("amt");
}
stringA += "&appid=" + params.get("appid");
if (params.get("bill_no") != null) {
stringA += "&bill_no=" + params.get("bill_no");
}
stringA += "&offer_id=" + params.get("offer_id") + "&openid=" + params.get("openid");
stringA += "&pf=" + params.get("pf");
if (params.get("present_counts") != null) {
stringA += "&present_counts=" + params.get("present_counts");
}
stringA += "&sig=" + params.get("sig") + "&ts=" + params.get("ts") + "&zone_id=1";
String stringSignTemp = stringA + "&org_loc=/cgi-bin/midas/sandbox/"+params.get("method")+"&method=POST&session_key="
+ params.get("session_key");
String sig = hmac_sha256(stringSignTemp, params.get("session_key").toString());
return sig;
}
// 请求
public static HttpResponse doPost(JSONObject params, String url) {
HttpPost post = null;
HttpResponse response = null;
try {
HttpClient httpClient = HttpClients.createDefault();
post = new HttpPost(url);
// 构造消息头
post.setHeader("Content-type", "application/json; charset=utf-8");
// 构建消息实体
StringEntity entity = new StringEntity(params.toString());
entity.setContentEncoding("UTF-8");
// 发送Json格式的数据请求
entity.setContentType("application/json");
post.setEntity(entity);
response = httpClient.execute(post);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (post != null) {
post.releaseConnection();
}
}
return response;
}
//将请求的结果转换为MidasBack对象
public static MidasBack changeObject(InputStream is) {
String str = "";
try {
byte[] back = new byte[is.available()];
is.read(back);
str = new String(back);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return JSON.parseObject(str, MidasBack.class);
}
//添加固定参数
public static JSONObject putParams(UserWxPayConfig userWxPayConfig) {
JSONObject params = new JSONObject();
params.put("appid", userWxPayConfig.getMidasAppId());
params.put("offer_id", userWxPayConfig.getOfferId());
params.put("ts", System.currentTimeMillis() / 1000);
params.put("zone_id", "1");
params.put("pf", "android");
params.put("secret", userWxPayConfig.getAppKey());
return params;
}
//移除不需要参数
public static JSONObject removeParams(JSONObject params) {
params.remove("secret");
params.remove("access_token");
params.remove("session_key");
params.remove("method");
return params;
}
// hmac_sha256
private static String hmac_sha256(String message, String secret) {
String hash = "";
try {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
sha256_HMAC.init(secret_key);
byte[] bytes = sha256_HMAC.doFinal(message.getBytes());
hash = byteArrayToHexString(bytes);
System.out.println(hash);
} catch (Exception e) {
System.out.println("Error HmacSHA256 ===========" + e.getMessage());
}
return hash;
}
private static String byteArrayToHexString(byte[] b) {
StringBuilder hs = new StringBuilder();
String stmp;
for (int n = 0; b != null && n < b.length; n++) {
stmp = Integer.toHexString(b[n] & 0XFF);
if (stmp.length() == 1)
hs.append('0');
hs.append(stmp);
}
return hs.toString().toLowerCase();
}
}
接收实体类MidasBack:
public class MidasBack {
private int errcode; //错误码
private String errmsg; //错误信息
private int balance; //游戏币个数(包含赠送)
private int gen_balance; //赠送游戏币数量(赠送游戏币数量)
private boolean first_save; //是否满足历史首次充值
private int save_amt; //累计充值金额的游戏币数量
private int save_sum; //历史总游戏币金额
private int cost_sum; //历史总消费游戏币金额
private int present_sum; //历史累计收到赠送金额
private String bill_no; //订单号
private int used_gen_amt; //本次扣的赠送币的金额
//省去get,set方法
}