admin 管理员组

文章数量: 887016

微信公众号支付验证签名失败

这几天开发的项目要做一个微信公众号支付,也就是在微信网页内部进行调取支付插件进行支付的一个过程

所以需要到微信官方开通公众号支付 微信官网:

1、登录后点击产品中心, 点击公众号支付

进入后就会看到这个页面

因为这边已经提前开通所以就不需要了

这是官方文档 : .php?chapter=7_3

点击开发配置

进行配置支付授权目录:也就是你的支付页面所在的目录
一定是生产环境的,微信不支持 ip +端口 形式的地址 异步通知也不支持,

所以测试都需要线上真实环境的域名+支付页面所在目录

登录公众号平台进行配置

公众号的按钮在下面

其次设置你的JS接口安全域名:也就是完整域名如:www.baidu

配置到这里基本就算完成了

现在我们需要获取几个必须的参数

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)(这里不要着急管它,最后处理它);

  1.    body 所支付的名称
    
  2.    out_trade_no 自己后台生成的订单号,只要保证唯一就好:如“pay2018062521331”
    
  3.    total_fee 支付金额 单位:分,为了测试此值给1,表示支付1分钱
    
  4.    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的ipfinal String[] arr = ip.split(",");for (final String str : arr) {if (!"unknown".equalsIgnoreCase(str)) {ip = str;break;}}return ip;}
  1.    notify_url 回调地址:这是微信支付成功后,微信那边会带着一大堆参数(XML格式)请求这个地址多次,这个地址做我们业务处理如:修改订单状态,赠送积分等。Ps:支付还没成功还想这么远干嘛,最后再说。地址要公网可以访问。
    
  2. trade_type 支付类型 咱们是公众号支付此处给“JSAPI”

  3. openid 支付人的微信公众号对应的唯一标识,每个人的openid在不同的公众号是不一样的,这11个参数里,最费劲的就是他了,其他的几乎都已经解决,现在开发得到这个参数。

获得openid的部分内容应该不属于微信支付的范畴,属于微信公众号网页授权的东西,详情请参考微信网页授权:

=resource/res_main&id=mp1421140842

获得openid步骤:
第一步:用户同意授权,获取code

=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect

注意:1. redirect_uri参数:授权后重定向的回调链接地址, 请使用 urlEncode 对链接进行处理。

  1. 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: =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")//支付类型  JSAPIdataMap.put("trade_type","JSAPI");dataMap.put("openid",(String)http.getAttribute("userId"));String parameter = WXPayUtil.generateSignature(dataMap, partnerkey);dataMap.put("sign",parameter);//TODO 签名//将map转成xmlString mapToXml = WXPayUtil.mapToXml(dataMap);//执行请求HttpClient httpClient=new HttpClient("");httpClient.setHttps(true);httpClient.setXmlParam(mapToXml);httpClient.post();String content = httpClient.getContent();System.out.println(content);//将xml转成mapMap<String, String> map = WXPayUtil.xmlToMap(content);//预付单idString 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支付,这回成功了

本文标签: 微信公众号支付验证签名失败