目录
一、官方概述
二、准备工作
三、着手开发
com.github.wxpay.sdk.WXPay类下提供了对应的方法:
方法名 | 说明 |
---|---|
microPay | 刷卡支付 |
unifiedOrder | 统一下单 |
orderQuery | 查询订单 |
reverse | 撤销订单 |
closeOrder | 关闭订单 |
refund | 申请退款 |
refundQuery | 查询退款 |
downloadBill | 下载对账单 |
report | 交易保障 |
shortUrl | 转换短链接 |
authCodeToOpenid | 授权码查询openid |
注意:
证书文件不能放在web服务器虚拟目录,应放在有访问权限控制的目录中,防止被他人下载
建议将证书文件名改为复杂且不容易猜测的文件名
商户服务器要做好病毒和木马防护工作,不被非法侵入者窃取证书文件
请妥善保管商户支付密钥、公众帐号SECRET,避免密钥泄露
参数为Map
对象,返回类型也是Map
方法内部会将参数会转换成含有appid
、mch_id
、nonce_str
、sign\_type
和sign
的XML
可选HMAC-SHA256算法和MD5算法签名
通过HTTPS请求得到返回数据后会对其做必要的处理(例如验证签名,签名错误则抛出异常)
对于downloadBill,无论是否成功都返回Map,且都含有return_code
和return_msg
,若成功,其中return_code
为SUCCESS
,另外data
对应对账单数据
1.申请微信支付平台账号(如果是做公众号最好微信公众平台也同时设置完毕)
2.获取appid
路径:微信支付 -- 首页 -- AppId账号
3.获取mchId
路径:微信支付 -- 首页 -- 商户号(mchId)
4.配置JSAPI域名
路径:微信支付 -- 产品中心 -- 开发配置 -- JSAPI支付 -- 支付授权目录 -- 添加
5.获取api密钥(遗忘需要重置)
路径:微信支付商户平台 -- 账户中心 -- API 安全 -- API 密钥 -- 设置密钥
6.公众号设置配置域名
路径: 公众号 -- 公众号设置 -- 功能设置 -- 网页授权域名 -- 设置
在配置域名的时候需要下载 MP_verify_cqlNsdZY4xZktC2l.txt 文件,放在项目中让微信可以获取到
7.获取apiclient_cert.p12文件
路径:微信支付 -- API安全 -- API证书(需放在可访问目录下,在MyConfig文件设置访问地址)
引入依赖
com.github.binarywang
weixin-java-pay
3.7.0
com.github.binarywang
weixin-java-mp
3.7.0
com.github.binarywang
weixin-java-mp
3.1.0
GetMpController 文件(获取 MP_verify_cqlNsdZY4xZktC2l.txt 文件内容)
由于实际开发时,微信方面会一直自动调用 MP_verify_cqlNsdZY4xZktC2l.txt 所以编写此类,也可以将MP_verify_cqlNsdZY4xZktC2l.txt文件放在服务器项目相同根目录
package com.zygh.webapi.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* @program: 微信支付
* @description: 获取 MP_verify_cqlNsdZY4xZktC2l.txt 文件内容
* @author: ID-Tang
* @create: 2020-12-09 17:15
**/
@RestController
public class GetMpController {
@RequestMapping(value = "/MP_verify_cqlNsdZY4xZktC2l.txt",method = RequestMethod.GET)
public Object GetMp(){
return "{文件内容}";
}
}
WXinConfig
package com.zygh.webapi.config;
import me.chanjar.weixin.mp.api.WxMpConfigStorage;
import me.chanjar.weixin.mp.api.WxMpInMemoryConfigStorage;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
public class WXinConfig {
@Bean
public WxMpService wxmpservice() {
WxMpServiceImpl wxMpService = new WxMpServiceImpl();
wxMpService.setWxMpConfigStorage(wxMpConfigStorage());
return wxMpService;
}
@Bean
public WxMpConfigStorage wxMpConfigStorage() {
MyConfig config = new MyConfig();
WxMpInMemoryConfigStorage wxMpConfigStorage = new WxMpInMemoryConfigStorage();
wxMpConfigStorage.setSecret(config.getAppSecret());
wxMpConfigStorage.setAppId(config.getAppID());
return wxMpConfigStorage;
}
}
MyConfig文件
package com.zygh.webapi.config;
import com.github.wxpay.sdk.WXPayConfig;
import java.io.*;
/**
* @program: 微信支付
* @description: 微信支付基本配置
* @author: ID-Tang
* @create: 2020-12-11 11:29
**/
public class MyConfig implements WXPayConfig {
private byte[] certData;
/**
* 获取微信证书内容
*/
public MyConfig(){
//证书存放位置
String certPath = "/home/developer/apiclient_cert.p12";
File file = new File(certPath);
InputStream certStream = null;
try {
certStream = new FileInputStream(file);
this.certData = new byte[(int) file.length()];
certStream.read(this.certData);
certStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public String getAppID() {
return "{appId}";
}
@Override
public String getMchID() {
return "{mchId}";
}
@Override
public String getKey() {
return "{key}";
}
@Override
public InputStream getCertStream() {
ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
return certBis;
}
@Override
public int getHttpConnectTimeoutMs() {
return 8000;
}
@Override
public int getHttpReadTimeoutMs() {
return 10000;
}
/**
* 通知地址(通知URL必须为直接可访问的URL,不允许携带查询串)
*
* @return notifyUrl
*/
public String getHttpNotifyUrl() {
return "{通知URL}";
}
/**
* 商户密钥
* @return appSecret
*/
public String getAppSecret(){
return "{appSecret}";
}
/**
* redirect_uri设置
* 微信授权之后回调地址,回调时可获取所需code
*
* @return 域名(例 : http://243300e5s6.wicp.vip) + /WxPay/getCode(此处固定)
*/
// public String getRedirectUri() {
// return "{redirect_uri}";
// }
/**
* 微信授权方式,可选 隐式授权 或 显式授权
* snsapi_base 隐式授权,静默授权并自动跳转到回调页
* snsapi_userinfo 显式授权,需要用户手动同意
*
* @return 授权方式
*/
// public String getScope() {
// return "snsapi_userinfo";
// }
}
WxPayEntity文件
package com.zygh.db.entity;
import io.swagger.annotations.ApiModelProperty;
/**
* @program: 微信支付
* @description: 微信支付需要的参数
* @author: ID-Tang
* @create: 2020-12-12 13:42
**/
public class WxPayEntity {
/**
* 商品描述
*/
@ApiModelProperty("商品描述")
private String body;
/**
* 商户订单号
*/
@ApiModelProperty("商户订单号")
private String outTradeNo;
/**
* 订单总金额,单位为分
*/
@ApiModelProperty("订单总金额,单位为分")
private String totalFee;
/**
* 用户标识(用户在直连商户appId下的唯一标识)
*/
@ApiModelProperty("用户标识(用户在直连商户appId下的唯一标识)")
private String openid;
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public String getOutTradeNo() {
return outTradeNo;
}
public void setOutTradeNo(String outTradeNo) {
this.outTradeNo = outTradeNo;
}
public String getTotalFee() {
return totalFee;
}
public void setTotalFee(String totalFee) {
this.totalFee = totalFee;
}
public String getOpenid() {
return openid;
}
public void setOpenid(String openid) {
this.openid = openid;
}
public WxPayEntity() {
}
public WxPayEntity(String body, String outTradeNo, String totalFee, String openid) {
this.body = body;
this.outTradeNo = outTradeNo;
this.totalFee = totalFee;
this.openid = openid;
}
@Override
public String toString() {
return "WxPayEntity{" +
"body='" + body + '\'' +
", outTradeNo='" + outTradeNo + '\'' +
", totalFee='" + totalFee + '\'' +
", openid='" + openid + '\'' +
'}';
}
}
WxPayController文件
package com.zygh.webapi.controller;
import com.alibaba.fastjson.JSON;
import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayUtil;
import com.zygh.common.component.CommonResult;
import com.zygh.db.entity.WxPayEntity;
import com.zygh.webapi.config.MyConfig;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
/**
* 该类不可使用@RestController
* 如需使用json,请手动在方法上面加入@ResponseBody注解
*
* @program: 微信支付
* @description: 微信支付Controller
* @author: ID-Tang
* @create: 2020-12-09 13:50
**/
@Controller
@Api(tags = "微信支付")
@RequestMapping(value = "/WxPay")
public class WxPayController {
@Resource
WxMpService wxmpservice;
/**
* 引入自定义config类
*/
private static final MyConfig config = new MyConfig();
/**
* 声明微信支付对象
*/
private static final WXPay wxpay = new WXPay(config);
// @ApiOperation("微信授权")
// @RequestMapping(value = "/getWxCode", method = RequestMethod.GET)
// public Object getWxCode() throws UnsupportedEncodingException {
// MyConfig config = new MyConfig();
//
// // 1、redirect_uri设置
// String redict = URLEncoder.encode(config.getRedirectUri(), "UTF-8");
//
//
// // 2、获取code的url
// String url = "https://open.weixin.qq.com/connect/oauth2/authorize?" +
// "appid=" + config.getAppID() + "&redirect_uri=" + redict +
// "&response_type=code&scope=" + config.getScope() + "#wechat_redirect";
//
// // 3、重定向去微信获取code
// return "redirect:" + url;
// }
// @ResponseBody
// @RequestMapping(value = "/getCode", method = RequestMethod.GET)
// public Object getWxOpenId(HttpServletRequest request) throws WxErrorException {
// // 1、获取code
// String code = request.getParameter("code");
// //传入code生成Token
// WxMpOAuth2AccessToken wxMpOAuth2AccessToken = wxmpservice.oauth2getAccessToken(code);
//
// //获取openId
// String openId = wxMpOAuth2AccessToken.getOpenId();
//
// System.out.println(openId);
//
// return openId;
// }
@ResponseBody
@RequestMapping(value = "/getCode", method = RequestMethod.GET)
@ApiOperation("获取用户openid(此接口无需手动调用,如需获取openid请调用微信授权(/getWxCode)接口)")
public Object getWxOpenId(String code) throws WxErrorException {
//传入code生成Token
WxMpOAuth2AccessToken wxMpOAuth2AccessToken = wxmpservice.oauth2getAccessToken(code);
//获取openId
String openId = wxMpOAuth2AccessToken.getOpenId();
return openId;
}
@ResponseBody
@ApiOperation("微信JSAPI支付统一下单")
@RequestMapping(value = "/toWxPay", method = RequestMethod.POST)
public Object toWxPay(@RequestBody WxPayEntity wxPayEntity) {
// 声明一个map集合用于提交
Map data = new HashMap<>(16);
// 商品描述
data.put("body", wxPayEntity.getBody());
// 商户订单号
data.put("out_trade_no", wxPayEntity.getOutTradeNo());
// 订单总金额,单位为分。
data.put("total_fee", wxPayEntity.getTotalFee());
// 通知地址(通知URL必须为直接可访问的URL,不允许携带查询串)
data.put("notify_url", config.getHttpNotifyUrl());
// 此处指定为JSAPI支付(注:此方法为公众号支付方法,故不建议更改为其它方式)
data.put("trade_type", "JSAPI");
// 用户标识(用户在直连商户appid下的唯一标识)
data.put("openid", wxPayEntity.getOpenid());
try {
// 统一下单返回的map集合,用于返回给前端
Map resp = wxpay.unifiedOrder(data);
// 获取时间戳
long l = System.currentTimeMillis() / 1000;
// 将时间戳加入返回值
resp.put("timeStamp", l + "");
// 声明一个新的map集合用于二次签名
// 固定以下五个字段,除加密方式外别的不用修改
// 签名需要五个字段:appId、package、timeStamp、nonceStr、signType
Map signMap = new HashMap<>(16);
//用户标识(注:虽然一次授权时参数名为appid但是二次授权时必须为appId)
signMap.put("appId", resp.get("appid"));
signMap.put("package", "prepay_id=" + resp.get("prepay_id"));
signMap.put("timeStamp", resp.get("timeStamp"));
signMap.put("nonceStr", resp.get("nonce_str"));
// 签名类型,此处使用MD5(注:使用新版需使用MD5)
signMap.put("signType", "MD5");
// 二次签名
String signature = WXPayUtil.generateSignature(signMap, config.getKey());
// 前端返回集合中加入package字段,内容为统一下单所返回的预支付标识
// 注:数据格式为"prepay_id=wx121126358624152875fa2a35b144c50000"
resp.put("package", signMap.get("package"));
// 返回前端集合中加入签名
// 注:这里需要的是二次签名后的签名
resp.put("paySign", signature);
//将返回前端的map集合转换为json格式的字符串
String jsonString = JSON.toJSONString(resp);
return jsonString;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@ResponseBody
@ApiOperation(value = "订单查询")
@RequestMapping(value = "/orderInquiry", method = RequestMethod.GET)
public Object orderInquiry(@RequestParam String orderId) {
Map data = new HashMap<>(16);
data.put("out_trade_no", orderId);
try {
Map resp = wxpay.orderQuery(data);
System.out.println(resp);
return new CommonResult().success(resp);
} catch (Exception e) {
e.printStackTrace();
}
return new CommonResult().validateFailed("查询失败");
}
@ResponseBody
@ApiOperation(value = "申请退款")
@RequestMapping(value = "/orderRefund", method = RequestMethod.POST)
public Object orderRefund(@RequestBody WxPayEntity wxPayEntity) {
//声明一个集合存放退款所需参数
Map data = new HashMap<>(16);
data.put("appid", config.getAppID());
data.put("mch_id", config.getMchID());
data.put("nonce_str", WXPayUtil.generateNonceStr());
data.put("out_trade_no", wxPayEntity.getOutTradeNo());
data.put("out_refund_no", wxPayEntity.getOutTradeNo());
data.put("total_fee", wxPayEntity.getTotalFee());
data.put("refund_fee", wxPayEntity.getTotalFee());
try {
// 签名
String signature = WXPayUtil.generateSignature(data, config.getKey());
data.put("sign",signature);
Map map = wxpay.refund(data);
return new CommonResult().success(map);
} catch (Exception e) {
e.printStackTrace();
}
return new CommonResult().validateFailed("申请退款失败!");
}
}
前端
获取code
window.location.href ='https://open.weixin.qq.com/connect/oauth2/authorize?appid={appId}&redirect_uri={redirect_uri}&response_type=code&scope=snsapi_userinfo&state=1&connect_redirect=1#wechat_redirect';
获取openId
this.$axiox({
method: 'GET',
url: this.$AXIOSURL + "/WxPay/getCode",
params: {
code: this.getQueryVariable("code")
}
}).then((response) => {
// this.openId = response;
sessionStorage.setItem('openId', response);
this.$router.replace('/myCenter');
// console.log(response, 418);
}).catch((response) => {
console.log(response)
Toast.fail('网络异常');
})
调起微信支付
wechatPay() {
// Toast.loading;
this.unpaid = this.unpaid.toString();
var dataobj = {
"body": this.form.name,
"outTradeNo": this.outTradeNo,
"totalFee": "1",
// "totalFee": this.unpaid,
"openid": this.openId,
}
this.$axiox({
method: 'POST',
url: this.$AXIOSURL + "/WxPay/toWxPay",
data: dataobj
}).then((res) => {
// console.log(res)
this.payData = res;
console.log(this.unpaid)
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();
}
})
},
onBridgeReady() {
console.log(this.payData)
let that = this;
// let data = getLocalStorage('prepay_data');
var dataobj = {
"appId": that.payData.appid, //公众号名称,由商户传入
"timeStamp": that.payData.timeStamp, //时间戳,自1970年以来的秒数
"nonceStr": that.payData.nonce_str, //随机串
"package": that.payData.package,
"signType": "MD5", //微信签名方式:
"paySign": that.payData.paySign, //微信签名
}
WeixinJSBridge.invoke('getBrandWCPayRequest', dataobj,
// {
// "appId": that.payData.appid, //公众号名称,由商户传入
// "timeStamp": that.payData.timeStamp, //时间戳,自1970年以来的秒数
// "nonceStr": that.payData.nonce_str, //随机串
// "package": "prepay_id=" + that.payData.prepay_id,
// "signType": "MD5", //微信签名方式:
// "paySign": that.payData.paySign, //微信签名
// },
function(res) {
if (res.err_msg == 'get_brand_wcpay_request:ok') {
that.gotosuccess();
// window.location.href = 'http://pay.yisuide.com/#/paysuccess?id=this.outTradeNo'
} else if (res.err_msg == 'get_brand_wcpay_request:cancel') {
//支付取消
alert('支付取消');
that.$router.replace({
path: "/myorder",
})
// window.location.href = 'http://www.shiyuanlive.com/shiyuanmall/#/order'
} else if (res.err_msg == "get_brand_wcpay_request:fail") {
//支付失败
alert('支付失败');
// WeixinJSBridge.call('closeWindow');
} //使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
}
);
},
*注:所有{}内的内容为需要替换的内容,所有前后端代码已脱敏处理,前端接口替换未使用{}进行标注,需自行查找并修改