Java微信公众平台开发之公众号支付(微信内H5调起支付)
程序员文章站
2022-07-06 16:18:43
...
官方文档点击查看
准备工作:已通过微信认证的公众号,必须通过ICP备案域名(否则会报支付失败)
借鉴了很多大神的文章,在此先谢过了
整个支付流程,看懂就很好写了
详细源码,整合了H5和两种扫码方式点击查看
一、设置支付目录
在微信公众平台设置您的公众号支付支付目录,设置路径见下图。公众号支付在请求支付的时候会校验请求来源是否有在公众平台做了配置,所以必须确保支付目录已经正确的被配置,否则将验证失败,请求支付不成功。
支付授权目录就是指支付方法的请求全路径
二、设置授权域名
开发公众号支付时,在统一下单接口中要求必传用户openid,而获取openid则需要您在公众平台设置获取openid的域名,只有被设置过的域名才是一个有效的获取openid的域名,否则将获取失败。具体界面如下图所示:
三、授权进入支付页面
授权部分点击参考,我是用的静默授权,拿到openid就好了
授权进入支付页面方法
/** * 授权进入支付页面 * * @param request * @param response * @param url * @return * @throws Exception */ @RequestMapping(value = "prePayPage", method = { RequestMethod.GET }) public String jsPay(HttpServletRequest request, HttpServletResponse response) throws Exception { AuthAccessToken authAccessToken = null; String code = request.getParameter("code");//可用redis保存 if(code==null){ return null; } String state = request.getParameter("state"); if(state.equals(MD5Util.MD5Encode("ceshi", ""))){ AuthTokenParams authTokenParams = new AuthTokenParams(); authTokenParams.setAppid("your appid"); authTokenParams.setSecret(""your secret"); authTokenParams.setCode(code); authAccessToken = oAuthService.getAuthAccessToken(authTokenParams, ACCESS_TOKEN_URL); } if(authAccessToken!=null){ logger.info("正在支付的openid=" + authAccessToken.getOpenid()); } return "system/wxpay/testpay"; }
支付页面的的body
<script type="text/javascript"> var prepay_id ; var paySign ; var appId ; var timeStamp ; var nonceStr ; var packageStr ; var signType ; function pay(){ var url = '${ctx}/wxpay/jspay.shtml'; $.ajax({ type:"post", url:url, dataType:"json", data:{openId:'${openId}'}, success:function(data) { if(data.resultCode == 'SUCCESS'){ appId = data.appId; paySign = data.paySign; timeStamp = data.timeStamp; nonceStr = data.nonceStr; packageStr = data.packageStr; signType = data.signType; callpay(); }else{ alert("统一下单失败"); } } }); } function onBridgeReady(){ WeixinJSBridge.invoke( 'getBrandWCPayRequest', { "appId":appId, //公众号名称,由商户传入 "paySign":paySign, //微信签名 "timeStamp":timeStamp, //时间戳,自1970年以来的秒数 "nonceStr":nonceStr , //随机串 "package":packageStr, //预支付交易会话标识 "signType":signType //微信签名方式 }, function(res){ if(res.err_msg == "get_brand_wcpay_request:ok" ) { //window.location.replace("index.html"); alert('支付成功'); }else if(res.err_msg == "get_brand_wcpay_request:cancel"){ alert('支付取消'); }else if(res.err_msg == "get_brand_wcpay_request:fail" ){ alert('支付失败'); } //使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。 } ); } function callpay(){ if (typeof WeixinJSBridge == "undefined"){ if( document.addEventListener ){ document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false); }else if (document.attachEvent){ document.attachEvent('WeixinJSBridgeReady', onBridgeReady); document.attachEvent('onWeixinJSBridgeReady', onBridgeReady); } }else{ onBridgeReady(); } } </script>
四、统一下单并获取prepay_id并返回页面支付参数
统一下单的官方文档点击查看 prepay_id获取到就是成功了一半了
/** * 微信内H5调起支付 * * @param request * @param response * @param openId * @return * @throws Exception */ @ResponseBody @RequestMapping("jspay") public JsPayResult jsPay(HttpServletRequest request, HttpServletResponse response, String openId) throws Exception { JsPayResult result = null; logger.info("****正在支付的openId****" + openId); // 统一下单 String out_trade_no = PayUtil.createOutTradeNo(); int total_fee = 1; // 产品价格1分钱,用于测试 String spbill_create_ip = HttpReqUtil.getRemortIP(request); logger.info("支付IP" + spbill_create_ip); String nonce_str = PayUtil.createNonceStr(); // 随机数据 //参数组装 UnifiedOrderParams unifiedOrderParams = new UnifiedOrderParams(); unifiedOrderParams.setAppid(WeChatConfig.APP_ID);// 必须 unifiedOrderParams.setMch_id(WeChatConfig.MCH_ID);// 必须 unifiedOrderParams.setOut_trade_no(out_trade_no);// 必须 unifiedOrderParams.setBody("支付测试");// 必须 unifiedOrderParams.setTotal_fee(total_fee); // 必须 unifiedOrderParams.setNonce_str(nonce_str); // 必须 unifiedOrderParams.setSpbill_create_ip(spbill_create_ip); // 必须 unifiedOrderParams.setTrade_type("JSAPI"); // 必须 unifiedOrderParams.setOpenid(openId); unifiedOrderParams.setNotify_url(WeChatConfig.NOTIFY_URL);// 异步通知url // 统一下单 请求的Xml(正常的xml格式) String unifiedXmL = wechatPayService.abstractPayToXml(unifiedOrderParams);////签名并入service // 返回<![CDATA[SUCCESS]]>格式的XML String unifiedOrderResultXmL = HttpReqUtil.HttpsDefaultExecute(HttpReqUtil.POST_METHOD,WeChatConfig.UNIFIED_ORDER_URL, null, unifiedXmL); // 进行签名校验 if (SignatureUtil.checkIsSignValidFromWeiXin(unifiedOrderResultXmL)) { String timeStamp = PayUtil.createTimeStamp(); //统一下单响应 UnifiedOrderResult unifiedOrderResult = XmlUtil.getObjectFromXML(unifiedOrderResultXmL, UnifiedOrderResult.class); /**** 用map方式进行签名 ****/ // SortedMap<Object, Object> signMap = new TreeMap<Object, // Object>(); // signMap.put("appId", WeiXinConfig.APP_ID); // 必须 // signMap.put("timeStamp", timeStamp); // signMap.put("nonceStr", nonceStr); // signMap.put("signType", "MD5"); // signMap.put("package", "prepay_id=" + prepay_id); // String paySign = SignatureUtil.createSign(signMap, key, SystemConfig.CHARACTER_ENCODING); result = new JsPayResult(); result.setAppId(WeChatConfig.APP_ID); result.setTimeStamp(timeStamp); result.setNonceStr(unifiedOrderResult.getNonce_str());//直接用返回的 /**** prepay_id 2小时内都有效,再次支付方法自己重写 ****/ result.setPackageStr("prepay_id=" + unifiedOrderResult.getPrepay_id()); /**** 用对象进行签名 ****/ String paySign = SignatureUtil.createSign(result, WeChatConfig.API_KEY, SystemConfig.CHARACTER_ENCODING); result.setPaySign(paySign); result.setResultCode("SUCCESS"); } else { logger.info("签名验证错误"); } /**** 返回对象给页面 ****/ return result; }
五、完成支付并通知支付结果
支付通知结果官方文档点击查看
package com.phil.wechatpay.controller; import java.io.BufferedOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.log4j.Logger; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.phil.common.result.ResultState; import com.phil.common.util.HttpReqUtil; import com.phil.common.util.SignatureUtil; import com.phil.common.util.XmlUtil; import com.phil.wechatpay.model.resp.PayNotifyResult; /** * 微信支付结果通知(统一下单参数的notify_url) * @author phil * @date 2017年6月27日 * */ @Controller @RequestMapping("/wxpay/") public class WechatPayNotifyController { static final Logger logger = Logger.getLogger(WechatPayNotifyController.class); @ResponseBody @RequestMapping("notify") public ResultState notify(HttpServletRequest request, HttpServletResponse response) throws Exception { ResultState resultState = new ResultState(); logger.info("开始处理支付返回的请求"); String resXml = ""; // 反馈给微信服务器 String notifyXml = HttpReqUtil.inputStreamToStrFromByte(request.getInputStream());// 微信支付系统发送的数据(<![CDATA[product_001]]>格式) logger.debug("微信支付系统发送的数据"+notifyXml); // 验证签名 if (SignatureUtil.checkIsSignValidFromWeiXin(notifyXml)) { PayNotifyResult notify = XmlUtil.getObjectFromXML(notifyXml, PayNotifyResult.class); logger.debug("支付结果" + notify.toString()); if ("SUCCESS".equals(notify.getResult_code())) { resultState.setErrcode(0);// 表示成功,可以不写,int默认是0 resultState.setErrmsg(notify.getResult_code()); /**** 业务逻辑 保存openid之类的****/ // 通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了 resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> "; } else { resultState.setErrcode(-1);// 支付失败 resultState.setErrmsg(notify.getErr_code_des()); logger.info("支付失败,错误信息:" + notify.getErr_code_des()); resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[" + notify.getErr_code_des() + "]]></return_msg>" + "</xml> "; } } else { resultState.setErrcode(-1);// 支付失败 resultState.setErrmsg("签名验证错误"); logger.info("签名验证错误"); resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[签名验证错误]]></return_msg>" + "</xml> "; } BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream()); out.write(resXml.getBytes()); out.flush(); out.close(); return resultState; } }
ResultState.java
package com.phil.common.result; import java.io.Serializable; /** * 微信API返回状态 * * @author phil * @date 2017年7月2日 * */ public class ResultState implements Serializable { /** * */ private static final long serialVersionUID = 1692432930341768342L; //@SerializedName("errcode") private int errcode; // 状态 //@SerializedName("errmsg") private String errmsg; //信息 public int getErrcode() { return errcode; } public void setErrcode(int errcode) { this.errcode = errcode; } public String getErrmsg() { return errmsg; } public void setErrmsg(String errmsg) { this.errmsg = errmsg; } }