虚拟支付米大师

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

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(后端)