虛擬支付米大師

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

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章