2019年6月5日 更新
今天收到微信官方平台发送的支付安全问题的信息,就是以前的支付流程在获取微信业务回调信息的时候可能会出现其它人的XXE的恶意攻击。
XXE的攻击原理:外界攻击者通过模拟回调通知,在回调通知中引入不安全的XML,商户服务器上执行系统命令。
其实对于微信订单回调这一模式,一般来说长点心的开发者都或多或少进行了平台方的二次的后台数据校验,大概率是不会担心虚假订单。
不过既然微信都已经发送了紧急通知,所以我们还是做好XML解析工作,防止恶意攻击吧。。。
再放一下我的微信支付Deom地址:
下载地址:https://download.youkuaiyun.com/download/qq_40562787/10363719
通过这个Deom可以看到我这边没有采用微信给的SDK,我是通过SAXBuilder builder = new SAXBuilder(); 来进行数据解析,所以这边只需要在Deom中的SAXBuilder builder = new SAXBuilder(); 后按照微信给的通知信息,插入以下代码就可以解决。
SAXBuilder builder = new SAXBuilder();
//下面是添加的代码
String FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
builder.setFeature(FEATURE, true);
FEATURE = "http://xml.org/sax/features/external-general-entities";
builder.setFeature(FEATURE, false);
FEATURE = "http://xml.org/sax/features/external-parameter-entities";
builder.setFeature(FEATURE, false);
FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
builder.setFeature(FEATURE, false);
感谢开源代码,让轮子造的更快乐。
第一次写微信小程序后台接口,却发现微信小程序有太多太多坑了。所以这里进行一个记录,防止忘记。
(现在补更一下,一定要和前端开发人员沟通好返回值啊!!最后一步给前端paySign值,结果前端老是报错requestPayment:fail parameter error: parameter.paySign should be String instead of Undefined;)————就是说paySign值是个未定义的。害的我苦苦找了一晚上和一早上,怎么都找不到问题,最后让对方发了一份他写的代码,我打开后才发现对方取值是
'paySign': res.data.sign
····你们体验过背了半天的锅是什么感觉吗?
'paySign'的值居然用sign取,不报错才怪啊!
)
想要作做微信小程序支付功能开发,首先我们要做一个热身!(新版本已经可以直接获取用户OpenID了,所以直接走下面支付流程吧。)
就是获取Open_Id。在关注者与公众号产生消息交互后,公众号可获得关注者的OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的。对于不同公众号,同一用户的openid不同)。
还不明白看这个:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_4
这个Open_Id是微信小程序调用wx.login方法先获取code,然后将code发送到后台,通过访问:
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
接口来获取到的Open_Id,如下
/**
* 获取OpenId
*
* @param js_code 小程序前端发送的Code值
* @return JSON格式的Open_Id返回值
*/
public static JSONObject getOpen_Id(String js_code) {
JSONObject json = new JSONObject();
String open_Id = "https://api.weixin.qq.com/sns/jscode2session?appid=wxXXXX2693XXXXXXXe&secret=7f4d62XXXXX2f795815XXXXXXXX&js_code="
+ js_code + "&grant_type=authorization_code";
String getOpen_Id = PayUtil.httpRequest(open_Id, "POST", "");
JSONObject fromObject = JsonUtil.StringtoJSON(getOpen_Id);
json.put("open_Id", fromObject);
return fromObject;
}
按照上面步骤:我们可以获取到这个openid。
————————————————————————————————————————————
接下来,我们就要开始进行支付操作了。
首先,备忘一下,我先把微信支付会用到的几个必传参数记录一下。
appid:微信的应用ID(固定值),也就是你开发的是什么微信,就填对应的Id
body:商品描述。
mch_id:微信支付商户信息号(固定值),需要提前注册为i系列商户。当时没有注册,让我等了两天审核才开始动手写支付。
nonce_str:随机字符串。
notify_url:接收微信支付异步通知回调地址,后台可以接收到微信小程序发送的订单消息,有很多很多消息。
out_trade_no:商户内部订单号,这个我有点不明白,我就当是本地订单索引发送的。
total_fee:订单总金额,单位为分
trade_type:交易类型JSAPI 公众号支付 NATIVE 扫码支付APP APP支付
openid:用户唯一标识,就是我上面获取到的那个值,现在要用了。
attach:附加数据,这个是我后面才加入的,我上传的Deom没有加入,你们需要的话可以添加进去,因为这个涉及到获取订单返回值的某些问题...
除了上面的这几个必传参外,涉及到小程序支付的还有一个特殊的必传参和一个固定值。
Key:商户秘钥。(固定值)生成后保存起来,以后用处大着。
sign:签名。获取上面的必传参数,然后将其转化为<xml>格式,然后加上key值,通过特定算法,生成的sign签名。
反正我就用了这么几行代码和Demo里的一些工具方法。完成了微信支付的二次签名
//微信的应用ID(固定值)
public final static String appid="wx1eXXXXXXXXXXXe";
//微信商号(固定值)
public final static String mch_id="1XXXXXXXXXX81";
//付费方法类型(固定值)
public final static String trade_type="JSAPI";
//商户密钥(固定值)
public final static String key="311XXXXXXXXXXXXXXXXXXXa9";
//统一下单API接口链接 (固定值)
public final static String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
/**
* 微信支付调用接口
* @param request
* @return JSON形式的参数值
* @throws UnsupportedEncodingException
*/
public static JSONObject wxOnePay(JSONObject param_json,String price) throws UnsupportedEncodingException {
Map<String,String> m=new HashMap<String,String>();
try {
String price1=Integer.toString((int)((Double.parseDouble(price)*100)));
System.out.println("价格是:"+price1+"分");
String notify_url="http://baidu.com/";//写你们自己的回调地址
//将获取到的map值转换为xml格式
m.put("appid", PayUtil.appid);//微信的应用ID(固定值)
m.put("body",param_json.getString("body") );//商品描述(其实可有可无)
m.put("mch_id", PayUtil.mch_id);//微信支付商户信息号(固定值)
m.put("nonce_str",PayUtil.getRandomNumbers());//随机字符串,不长于32位
m.put("notify_url",notify_url);//接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数。
m.put("out_trade_no",PayUtil.getRandomNumbers());//商户系统内部的订单号(咱们没有做,所以可不填),32个字符内、可包含字母
m.put("total_fee",price1);//订单总金额,单位为分
m.put("trade_type", PayUtil.trade_type);//固定值
m.put("openid", param_json.getString("openid"));//微信端下的唯一用户标识
Map<String,String> sPara = PayUtil.paraFilter(m);
String prestr = PayUtil.createLinkString(sPara);
String mysign = PayUtil.sign(prestr, PayUtil.key, "utf-8").toUpperCase();
m.put("sign", mysign);//sign签名,第一次随机签名
//打包要发送的xml
String respXml = PayUtil.getRequestXML(m);
//发起服务器请求
String result =PayUtil.httpRequest(PayUtil.url, "POST", respXml);
Map<?,?> map = PayUtil.doXMLParse(result);
//返回状态码
String return_code = (String) map.get("return_code");
//返回给小程序端需要的参数
Map<String, String> callbackMap = new HashMap<String, String>();
if(return_code=="SUCCESS"||return_code.equals(return_code)) {
//返回的预付单信息
String prepay_id = (String) map.get("prepay_id");
Long timeStamp = System.currentTimeMillis() / 1000;//微信是按照分来算的,我们是按照元来算的,所以需要转换
callbackMap.put("appId", PayUtil.appid);
callbackMap.put("timeStamp", timeStamp+ "");
callbackMap.put("nonceStr",notify_url);
callbackMap.put("signType", "MD5");
callbackMap.put("package", "prepay_id="+prepay_id);
Map<String,String> sPara2 = PayUtil.paraFilter(callbackMap);
String prestrTow = PayUtil.createLinkString(sPara2);
String mysignTow = PayUtil.sign(prestrTow, PayUtil.key, "utf-8").toUpperCase();
//sign签名,第二次随机签名
callbackMap.put("paySign", mysignTow);
JSONObject jsonObject = JSONObject.fromObject(callbackMap);
String respXml2 = PayUtil.getRequestXML(callbackMap);
Log4jUtil.infoLog("CaseController getDetailsCase()").info("传递的参数:" + respXml2);
return jsonObject;
}
} catch (Exception e) {
Log4jUtil.infoLog("PayUtil wxOnePay()").error("微信支付工具出错!:"+e);
e.printStackTrace();
return null;
}
return null;
}
生成签名的Deom和相关工具类文档:
下载地址:https://download.youkuaiyun.com/download/qq_40562787/10363719
输入正确的参数和调用工具类中对应的方法,正常情况下,你们的微信支付就已经完成。
——————————————————————————————————————————
接下来就是无(磨)关(人)紧(妖)要(精)的订单管理了。当然你们可以时情况而定,
因为触发资金流之后,微信商户平台就会自动记录订单信息,你们家产品狗好说话,可以让甲方直接去商户平台看吧。
如果不可以,你的notify_url参数就派上用场了。
微信支付发生,不管成功与否,微信平台都会异步发送消息到你的回调接口当中。
使用上面提供的工具类的
PayUtil.doXMLParse(data)方法。将接收的data值传进去,自动改为MAP格式,到时候你只要按照KEY取微信的回调值就可以了。
至此,小程序的支付功能算是全部走完。
如果还不明白,可以看这个官方文档,跟着逻辑走一般都没问题:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_4&index=3