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的那个用户点击才能测试,一般是本人的),然后点击开始测试支付。