官方文檔: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方法
}