这几天开发的项目要做一个微信公众号支付,也就是在微信网页内部进行调取支付插件进行支付的一个过程
所以需要到微信官方开通公众号支付 微信官网:https://pay.weixin.qq.com
1、登录后点击产品中心, 点击公众号支付
进入后就会看到这个页面
因为这边已经提前开通所以就不需要了
这是官方文档 : https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_3
点击开发配置
进行配置支付授权目录:也就是你的支付页面所在的目录
一定是生产环境的,微信不支持 ip +端口 形式的地址 异步通知也不支持,
所以测试都需要线上真实环境的域名+支付页面所在目录
登录公众号平台进行配置
https://mp.weixin.qq.com
公众号的按钮在下面
其次设置你的JS接口安全域名:也就是完整域名如:www.baidu.com
配置到这里基本就算完成了
现在我们需要获取几个必须的参数
appid,mch_id ,加密key
基本配置按钮也在下面
显示appid 点击基本配置就会看到了 如:wxf8xxxxxxxxxfca
mch_id 就是你登录微信商户后台的商户号,如:1594xxxxxxxxx98
key 获取,也是在微信商户后台
这个是自己设置的,看你自己设置了,
这里使用微信提供的官方工具类WXPayUtil进行生成随机字符串,也可以使用uuid生成32为随机数
4. sign 签名 用WXPayUtil中的generateSignature(finalMap<String, String> data, String key)方法,data是将除了sign外,其他10个参数放到map中,key是四大配置参数中的API秘钥(paternerKey)(这里不要着急管它,最后处理它);
-
body 所支付的名称
-
out_trade_no 自己后台生成的订单号,只要保证唯一就好:如“pay2018062521331”
-
total_fee 支付金额 单位:分,为了测试此值给1,表示支付1分钱
-
spbill_create_ip IP地址 网上很多ip的方法,自己找,此处测试给“127.0.0.1”
获取用户真实IP地址
/**
* 从request中获取请求方IP
* @param request
* @return
*/
public static String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
// 若以上方式均为获取到ip则证明获得客户端并没有采用反向代理直接使用getRemoteAddr()获取客户端的ip地址
ip = request.getRemoteAddr();
}
// 多个路由时,取第一个非unknown的ip
final String[] arr = ip.split(",");
for (final String str : arr) {
if (!"unknown".equalsIgnoreCase(str)) {
ip = str;
break;
}
}
return ip;
}
-
notify_url 回调地址:这是微信支付成功后,微信那边会带着一大堆参数(XML格式)请求这个地址多次,这个地址做我们业务处理如:修改订单状态,赠送积分等。Ps:支付还没成功还想这么远干嘛,最后再说。地址要公网可以访问。
-
trade_type 支付类型 咱们是公众号支付此处给“JSAPI”
-
openid 支付人的微信公众号对应的唯一标识,每个人的openid在不同的公众号是不一样的,这11个参数里,最费劲的就是他了,其他的几乎都已经解决,现在开发得到这个参数。
获得openid的部分内容应该不属于微信支付的范畴,属于微信公众号网页授权的东西,详情请参考微信网页授权:
https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842
获得openid步骤:
第一步:用户同意授权,获取code
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
注意:1. redirect_uri参数:授权后重定向的回调链接地址, 请使用 urlEncode 对链接进行处理。
-
scope:用snsapi_base 。
通过此链接可以获取code,可以在一个空页面设置一个a标签,链接至其redirect_uri的地址。点击a标签,即可链接到redirect_uri的地址,并携带code。
[html] view plain copy
去支付页面pay.jsp并携带code
第二步:通过code换取网页授权access_token(其实微信支付就没有必要获取access_token了,咱们只要其中openid,不是要用户信息,此步结果已经就含有咱们需要的openid了)
获取code后,请求以下链接获取access_token: https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
上一步的code有了,对于此链接的参数就容易了。可是在页面上如何处理是个问题,我是在pay.jsp页面加载完成后将获取code当做参数传异步到后台,在后台中用http相关类发送get请求(可以自行网上查找)。返回的JSON结果为:
[html] view plain copy
{ “access_token”:“ACCESS_TOKEN”,
“expires_in”:7200,
“refresh_token”:“REFRESH_TOKEN”,
“openid”:“OPENID”,//就是它,只要这个值
“scope”:“SCOPE” }
好了都有了,我们就可以开始写拼装参数了, 参数填写修改成你自己的就可以了
/**
* 创建支付订单
* @param out_trade_no
* @param total_fee
* @return
*/
public Map createJSAPI(String out_trade_no, String total_fee) {
try {
//封装参数
Map<String,String> dataMap=new HashMap<>();
dataMap.put("appid",appid);//应用ID,微信公众账号或开放平台APP的唯一标识
dataMap.put("mch_id",partner);//商户号
dataMap.put("nonce_str", WXPayUtil.generateNonceStr()); //随机字符串
dataMap.put("body","company-coupon");
dataMap.put("out_trade_no",out_trade_no); //商户生成的订单号
dataMap.put("total_fee",total_fee);
dataMap.put("spbill_create_ip",PayCommonUtil.getIpAddress(http));
dataMap.put("notify_url",notifyurl);
//获取用户的openId "okIDwjg2__0vcyd3HsBBcrd03BMY" "okIDwjnmMbRX-2oSiM5Qmrq8R9cg" (String)http.getAttribute("userId")
//支付类型 JSAPI
dataMap.put("trade_type","JSAPI");
dataMap.put("openid",(String)http.getAttribute("userId"));
String parameter = WXPayUtil.generateSignature(dataMap, partnerkey);
dataMap.put("sign",parameter);//TODO 签名
//将map转成xml
String mapToXml = WXPayUtil.mapToXml(dataMap);
//执行请求
HttpClient httpClient=new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder");
httpClient.setHttps(true);
httpClient.setXmlParam(mapToXml);
httpClient.post();
String content = httpClient.getContent();
System.out.println(content);
//将xml转成map
Map<String, String> map = WXPayUtil.xmlToMap(content);
//预付单id
String prepay_id = map.get("prepay_id");
//获取参数 传给前台
Map<String,String> ternMap=new HashMap<String, String>();
//二次签名参数
ternMap.put("appId",appid);
ternMap.put("timeStamp",String.valueOf(System.currentTimeMillis()/1000));
//注意这里的随机数应为统一下单的随机数
ternMap.put("nonceStr",dataMap.get("nonce_str"));
ternMap.put("package","prepay_id="+prepay_id); //预支付交易会话标识,2小时失效
ternMap.put("signType","MD5");
// 第二次的签名
String paySign = WXPayUtil.generateSignature(ternMap, partnerkey);
ternMap.put("prepay_id",ternMap.get("package"));
ternMap.put("outTradeNo",out_trade_no);
ternMap.put("total_fee",total_fee);
ternMap.put("return_code",map.get("return_code"));
ternMap.put("result_code",map.get("result_code"));
ternMap.put("paySign",paySign);
return ternMap;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
请求过后 微信端返回的也是XML 不利于我们处理,所以继续转map
Map<String, Object> respMap = WXPayUtil.xmlToMap()
转好map 后我们就开始取 prepay_id了
参数:
appid 也有
timeStamp 时间戳 你们new Date();即可,因为我语言是 Groovy 所以需要getTime 才是秒数
这下面的五个参数名必须一致
package prepay_id 已有了
nonceStr 随机数 getuuid方法就可以了
signType 固定值 MD5
sign 上面5个参数的签名结果
这里值得注意的是package 参数, 这个参数可不是简单的吧prepay_id 放进去
,要把 “prepay_id=”这个拼接上里面不能有多余的"或者’符号
之前没有拼接 ,这里要格外注意 package这个键的参数字段,由于将这个package为键传给前台,前台那边接收不到参数,于是我换了个值,结果坑就坑在这里,找了许久,最后在网上找到一遍文章,才意识到不同的参数会导致生成签名时字典排序不同。这是我在传不同值时所获得生成签名的排序顺序
结果改回package字段后再生成签名后再换个字段将这个参数值传给前台调起JS支付,这回成功了