公众号微信支付开发
1.第一步:设置微信支付目录,这个地址指到支付页面的上一级即可。
例如:支付页面的地址是http://www.baidu.com/wechat/pay/shopping,只需填写http://www.baidu.com/wechat/pay/,
一定要以"/"(左斜杆)符号结尾。
2.第二步:设置授权域名,授权域名是为了获取支付中不可缺少的参数openid。每个用户对于每个公众号的openid都是不同的且是唯一的,即是说一个用户在不同的公众号中,他的openid是不同的,并且一直不变。在开发中可以事先获取你自己的在这个公众号(正式公众号,具有支付权限的)的openid,然后就可以跳过授权过程,直接开发并测试支付功能。
3.第三步:引入微信开发jar包,这是别人已经封装好的微信支付API,当然也可以使用官方的微信支付SDK,不过为了方便快速开发,
所以这里我使用了封装好的别人封装好的API,这是API文档的github地址:https://github.com/wechat-group/weixin-java-tools/wiki ,里面有具体的使用方法和开发步骤,如果你嫌看文档麻烦的话可以直接看我的开发步骤:
这是基于springboot开发的,首先引入jar包:
<dependency> <groupId>com.github.binarywang</groupId> <artifactId>weixin-java-pay</artifactId> <version>3.1.4.BETA</version> </dependency>
4.加入微信开发包两个基本类:
(1)在springboot配置文件application.properties中加入微信支付基本参数:
#微信公众号或者小程序等的appid wechatpay.appId = #微信支付商户号 wechatpay.mchId = #微信支付商户密钥 wechatpay.mchKey= #服务商模式下的子商户公众账号ID,用不上就注释掉 #wechatpay.subAppId= #服务商模式下的子商户号,用不上就注释掉 #wechatpay.subMchId= # p12证书的位置,可以指定绝对路径,也可以指定类路径(以classpath:开头)用不上就注释掉 #wechatpay.keyPath=
(2)添加支付配置文件参数类WxPayProperties:
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.springframework.boot.context.properties.ConfigurationProperties; /** * wxpay pay properties * * @author Binary Wang */ @ConfigurationProperties(prefix = "wechatpay") public class WxPayProperties { /** * 设置微信公众号的appid */ private String appId; /** * 微信支付商户号 */ private String mchId; /** * 微信支付商户密钥 */ private String mchKey; /** * 服务商模式下的子商户公众账号ID,普通模式请不要配置,请在配置文件中将对应项删除 */ private String subAppId; /** * 服务商模式下的子商户号,普通模式请不要配置,最好是请在配置文件中将对应项删除 */ private String subMchId; /** * apiclient_cert.p12文件的绝对路径,或者如果放在项目中,请以classpath:开头指定 */ private String keyPath; public String getAppId() { return this.appId; } public void setAppId(String appId) { this.appId = appId; } public String getMchId() { return mchId; } public void setMchId(String mchId) { this.mchId = mchId; } public String getMchKey() { return mchKey; } public void setMchKey(String mchKey) { this.mchKey = mchKey; } public String getSubAppId() { return subAppId; } public void setSubAppId(String subAppId) { this.subAppId = subAppId; } public String getSubMchId() { return subMchId; } public void setSubMchId(String subMchId) { this.subMchId = subMchId; } public String getKeyPath() { return this.keyPath; } public void setKeyPath(String keyPath) { this.keyPath = keyPath; } @Override public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); } }
(3)初始化微信支付配置类WxPayConfiguration:
import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.github.binarywang.wxpay.config.WxPayConfig; import com.github.binarywang.wxpay.service.WxPayService; import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl; /** * @author Binary Wang */ @Configuration @ConditionalOnClass(WxPayService.class) @EnableConfigurationProperties(WxPayProperties.class) public class WxPayConfiguration { private WxPayProperties properties; @Autowired public WxPayConfiguration(WxPayProperties properties) { this.properties = properties; } @Bean @ConditionalOnMissingBean public WxPayService wxService() { WxPayConfig payConfig = new WxPayConfig(); payConfig.setAppId(StringUtils.trimToNull(this.properties.getAppId())); payConfig.setMchId(StringUtils.trimToNull(this.properties.getMchId())); payConfig.setMchKey(StringUtils.trimToNull(this.properties.getMchKey())); payConfig.setSubAppId(StringUtils.trimToNull(this.properties.getSubAppId())); payConfig.setSubMchId(StringUtils.trimToNull(this.properties.getSubMchId())); payConfig.setKeyPath(StringUtils.trimToNull(this.properties.getKeyPath())); WxPayService wxPayService = new WxPayServiceImpl(); wxPayService.setConfig(payConfig); return wxPayService; } }
5.生成支付订单:
import com.alibaba.fastjson.JSONObject; import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult; import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult; import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest; import com.github.binarywang.wxpay.exception.WxPayException; import com.github.binarywang.wxpay.service.WxPayService; import com.park.pojo.park.ParkingRecord; import com.park.service.Interface.ParkService; import com.park.utils.parkInterfaceUtil.ParkInterfaceRequestParam; import com.park.utils.parkInterfaceUtil.ParkProtocolParamUtil; import com.park.utils.wechatUtil.ConstantUtil; import com.park.utils.wechatUtil.HttpRequest; import com.park.utils.wechatUtil.SignUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * 微信支付接口 */ @Slf4j @RestController @RequestMapping(value = "/wechat/pay") public class WechatPayController { @Autowired private WxPayService wxService; /** * 统一下单 * 在发起微信支付前,需要调用统一下单接口,返回微信支付的所需要的参数 * 接口地址:https://api.mch.weixin.qq.com/pay/unifiedorder * * @param request 请求对象,注意一些参数如appid、mchid等不用设置,方法内会自动从配置对象中获取到(前提是对应配置中已经设置) */ @PostMapping("/getPayInfo") public WxPayMpOrderResult getPayInfo(@RequestBody WxPayUnifiedOrderRequest request , HttpServletRequest httpServletRequest) throws WxPayException,Exception { request.setOutTradeNo(SignUtil.buildRandom(10)); //随机字订单号 request.setSpbillCreateIp(ConstantUtil.getRemortIP(httpServletRequest)); //用户ip request.setTradeType("JSAPI"); //公众号支付 request.setNonceStr(SignUtil.createNonceStr()); //随机字符串 request.setNotifyUrl("http://qwrerewrwqewq/wechat/pay/PayResultNotify"); //回调通知支付结果地址(必须外网能访问的地址) request.setDeviceInfo("WEB"); //客户终端类型 request.setSignType("MD5"); //加密方式(必须参数,虽然官方文档说是非必需,亲测不加一直报签名错误) return this.wxService.createOrder(request); } /** * 接受微信支付返回通知 * @param request * @param response * @throws IOException */ @RequestMapping("/PayResultNotify") public void PayResultNotify(HttpServletRequest request, HttpServletResponse response) throws IOException { log.info("微信支付返回通知函数开始---------------------"); InputStream inStream = request.getInputStream(); ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len = 0; while ((len = inStream.read(buffer)) != -1) { outSteam.write(buffer, 0, len); } outSteam.close(); inStream.close(); String result = new String(outSteam.toByteArray(), "utf-8"); boolean isPayOk =false; WxPayOrderNotifyResult wxPayOrderNotifyResult =null; // 此处调用订单查询接口验证是否交易成功 try { wxPayOrderNotifyResult = wxService.parseOrderNotifyResult(result); if("SUCCESS".equals(wxPayOrderNotifyResult.getResultCode())){ isPayOk=true; } log.info("解析数据:"+wxPayOrderNotifyResult.toString()); } catch (WxPayException e) { e.printStackTrace(); } String noticeStr=""; // 支付成功,商户处理后同步返回给微信参数 PrintWriter writer = response.getWriter(); if (isPayOk) { //建议在这里处理付款完成的业务(虽然前端也可以处理后续业务,但是前端处理并不安全,例如:客户突然关闭浏览器了等情况,付款成功后续的业务将中断) System.out.println("===============付款成功,业务处理完毕=============="); // 通知微信已经收到消息,不要再给我发消息了,否则微信会8连击调用本接口 noticeStr = setXML("SUCCESS", "OK"); log.info("收到通知返回给微信api信息:-----------"+noticeStr); writer.write(noticeStr); writer.flush(); } else { // 支付失败, 记录流水失败 noticeStr = setXML("FAIL", ""); writer.write(noticeStr); writer.flush(); System.out.println("===============支付失败=============="); } } public static String setXML(String return_code, String return_msg) { return "<xml><return_code><![CDATA[" + return_code + "]]></return_code><return_msg><![CDATA[" + return_msg + "]]></return_msg></xml>"; } }
6.所用到的工具类:
import javax.servlet.http.HttpServletRequest; import java.security.MessageDigest; import java.util.Arrays; import java.util.Date; import java.util.UUID; public class SignUtil { private static String token = "weixin"; /** * 获取acassToken校验 * @param signature * @param timestamp * @param nonce * @return */ public static boolean checkSignature(String signature, String timestamp, String nonce) { boolean result = false; // 对token、timestamp和nonce按字典序排序 String[] array = new String[]{token, timestamp, nonce}; Arrays.sort(array); // 将三个参数字符拼接成一个字符串 String str = array[0].concat(array[1]).concat(array[2]); String sha1Str = null; try { // 对拼接后的字符串进行sha1加密 MessageDigest md = MessageDigest.getInstance("SHA-1"); byte[] digest = md.digest(str.getBytes()); sha1Str = byte2str(digest); } catch(Exception e) { } if(sha1Str != null && sha1Str.equals(signature)) { result = true; } return result; } /* * 将字节数组转换成字符串 */ public static String byte2str(byte[] array) { StringBuffer hexstr = new StringBuffer(); String shaHex=""; for(int i = 0; i < array.length; i++) { shaHex = Integer.toHexString(array[i] & 0xFF); if(shaHex.length() < 2) { hexstr.append(0); } hexstr.append(shaHex); } return hexstr.toString(); } /** * 获取acaccessToken * @param grant_type * @param appid * @param secret * @return */ public static String getAcaccessToken(String grant_type,String appid,String secret){ String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type="+grant_type+"&appid="+appid+"&secret="+secret; String Str = HttpRequest.httpRequestGet(url); return Str; } /** * 创建随机字符串 * @return */ public static String createNonceStr() { return UUID.randomUUID().toString().substring(0, 20); } /** * 获取时间戳 * @return */ public static String getCurrTime(){ long times = new Date().getTime(); return times+""; } /** * 随机数 * @param length * @return */ public static String buildRandom(int length){ long times = new Date().getTime(); int randomNum = (int)((Math.random()*9+1)*(10*length)); return randomNum+""+times; } /** * 获取用户IP * @param request * @return */ public static String getRemortIP(HttpServletRequest request) { if (request.getHeader("x-forwarded-for") == null) { return request.getRemoteAddr(); } String ipListStr = request.getHeader("x-forwarded-for"); if(!(ipListStr.indexOf(",")<0)){ String [] list = ipListStr.split(","); return list[0]; //当服务部署使用代理,其获取到的IP地址将会是多个,取第一个 }else { return ipListStr ; } } }
7.前端代码:
我的前端是用vue写的,看下逻辑就可以了:
getPayInfo(params){ //调用统一下单接口获取js支付参数 参数:金额、商品名称等 具体可以看统一接口的接收参数类WxPayUnifiedOrderRequest this.$api.gotoPay(params).then(res=>{ if(res!=null){ this.payInfo.appId = res.data.appId; this.payInfo.timeStamp = res.data.timeStamp; this.payInfo.nonceStr = res.data.nonceStr; this.payInfo.packages = res.data.packages; this.payInfo.sign = res.data.sign; } }) }, onBridgeReady(){ //使用微信浏览器内置的对象调起微信支付插件,并传入统一接口返回的参数 WeixinJSBridge.invoke( ‘getBrandWCPayRequest‘, { "appId":this.payInfo.appId, //公众号名称,由商户传入 "timeStamp":this.payInfo.timeStamp, //时间戳,自1970年以来的秒数 "nonceStr":this.payInfo.nonceStr, //随机字符串 "package":this.payInfo.packages, //支付验证pay_id "signType":"MD5", //微信签名方式 "paySign":this.payInfo.sign //微信签名 }, function(res){ if(res.err_msg == "get_brand_wcpay_request:ok" ){ // 使用以上方式判断前端返回,微信团队郑重提示: //res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。 } }); } , Gopay(){ //点击付款按钮开始支付 console.log("开始支付") if (typeof WeixinJSBridge == "undefined"){ if( document.addEventListener ){ document.addEventListener(‘WeixinJSBridgeReady‘, this.onBridgeReady(), false); }else if (document.attachEvent){ document.attachEvent(‘WeixinJSBridgeReady‘, this.onBridgeReady()); document.attachEvent(‘onWeixinJSBridgeReady‘, this.onBridgeReady()); } }else{ this.onBridgeReady(); } }
(8)测试支付功能:
可以将支付页面的地址发送到正式的公众号(必须要正式的公众号才行,而且后台支付相关的参数也要用正式,同时要用传入的openid的那个用户点击才能测试,一般是本人的),然后点击开始测试支付。