申请微信支付
一、微信支付首先需要先去申请资格,申请条件及步骤如下:
1、注册公众号(服务号-企业才能注册),可以根据营业执照类型选择:个体商户 | 企业/公司 | 政府 | 媒体 | 其他类型。
2、认证公众号(认证费:300元/年)
3、提交资料申请微信支付(审核时间为1-5个工作日内)。
4、开户成功,登录商户平台进行验证。
资料审核通过后,请登录联系人邮箱查收商户号和密码,并登录商户平台填写财付通备付金打的小额资金数额,完成账户验证。
二、获取公众号/商户信息支付信息
1、微信公众账号或开放平台APP的唯一标识 - appid
2、财付通平台的商户账号 - mch_id
3、财付通平台的商户密钥 - partnerkey
4、启用Native
5、根据API进行开发:https://pay.weixin.qq.com/wiki/doc/api/index.html
微信支付基本使用
一、创建二维码
后端需要根据订单id和支付金额来返回一个Map集合给前端,前端通过qrcode创建一个支付二维码。
先在微信支付相关操作的类里面注入商户信息(商户信息在properties配置文件中)
// 微信公众账号或开放平台APP的唯一标识
@Value("${appid}")
private String appid;
// 财付通平台的商户账号
@Value("${partner}")
private String partner;
// 财付通平台的商户秘钥
@Value("${partnerkey}")
private String partnerkey;
// 回调地址
@Value("${notifyurl}")
private String notifyurl;
1、后端代码
①Controller层代码 - 根据自己的业务来做
/**
* 获取当前登录用户名,
* 根据用户名获取redis中的支付日志对象,
* 根据支付日志对象中的支付单号和总金额
* 调用微信统一下单接口, 生成支付链接返回
* @return
*/
@RequestMapping("/createNative")
public Map createNative() {
//1. 获取当前登录用户的用户名 - SpringSecurity
String userName = SecurityContextHolder.getContext().getAuthentication().getName();
//2. 根据用户名获取支付日志对象 - 根据自己业务调用
PayLog payLog = orderService.getPayLogByUserName(userName);
if (payLog != null) {
//3. 调用统一下单接口生成支付链接 - 订单号、价格(单位:分)
Map map = payService.createNative(payLog.getOutTradeNo(), "1");
return map;
}
return new HashMap();
}
②Service层代码 - 根据自己的业务来做,传一些其他附加参数
**
* 支付创建二维码
* @param outTradeNo 订单号
* @param totalFee 支付金额
* @return 前端需要的相关信息
*/
public Map createNative(String outTradeNo, String totalFee){
// 创建参数Map
Map<String, String> payParam = new HashMap<>();
payParam.put("appid",appid); // 微信公众账号或开放平台APP的唯一标识
payParam.put("mch_id",partner); // 财付通平台的商户账号
payParam.put("body","可口可乐250ML"); // 商品描述
payParam.put("nonce_str",WXPayUtil.generateNonceStr()); // 随机字符串
payParam.put("out_trade_no",outTradeNo); // 订单号
payParam.put("total_fee",totalFee); // 支付金额(单位:分)
payParam.put("spbill_create_ip","127.0.0.1"); // APP和网页支付提交用户端ip
payParam.put("notify_url",notifyurl); // 回调地址
payParam.put("trade_type","NATIVE"); // 交易类型 - 二维码支付
try{
// 生成要发送的xml,调用微信SDK的API接口将封装的Map数据转换成Xml格式字符串
String xmlParam = WXPayUtil.generateSignedXml(payParam, partnerkey); // 参数2:财付通平台的商户密钥
// 使用HttpClient发送请求 - 参数为支付请求地址
HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder");
client.setHttps(true);
client.setXmlParam(xmlParam);
client.post();
// 获得结果 - xml字符串
String result = client.getContent();
// 调用微信SDK的API接口将Xml数据转换成Map对象
Map<String, String> resultMap = WXPayUtil.xmlToMap(result);
// 创建返回给前端去创建二维码的结果Map
Map<String, String> returnMap = new HashMap<>();
returnMap.put("code_url",resultMap.get("code_url")); // 支付地址
returnMap.put("total_fee",totalFee); // 总金额
returnMap.put("out_trade_no",outTradeNo); // 订单号
}catch(Excption e){
e.printStackTrace();
return new HashMap<>();
}
}
2、前端代码(Vue) - 请求创建二维码
/**
* 创建支付二维码
*/
createNative: function () {
let _this = this;
axios.get('/pay/createNative.do').then(function (response) {
// 显示金额
_this.money = (response.data.total_fee / 100).toFixed(2);
// 显示订单号
_this.out_trade_no = response.data.out_trade_no;
// 使用QRcode创建二维码
let qrcode = new QRCode(document.getElementById("qrcode"), {
width: 250,
height: 250
});
qrcode.makeCode(response.data.code_url);
// 查询支付结果 - 这里采用的是长轮询方式查询支付结果
_this.queryPayStatus();
}).catch(function (reason) {
console.log(reason);
})
},
二、查询订单支付结果
1、后端代码
①Controller层代码 - 根据自己的业务来做
/**
* 调用查询订单接口, 查询是否支付成功
* @param out_trade_no 订单号
* @return
*/
@RequestMapping("/queryPayStatus")
public MessageResult queryPayStatus(String out_trade_no) {
String userName = SecurityContextHolder.getContext().getAuthentication().getName();
MessageResult MessageResult = null;
int flag = 1;
// 长轮询的方式查询结果 - 建议使用WebSocket或通过消息中间件RabbitMQ Web STOMP来解决
while(true) {
// 1. 判断支付单号等于null
if (out_trade_no == null) {
MessageResult = new MessageResult(false, "二维码超时");
break;
}
// 2. 调用查询接口查询支付是否成功
Map map = payService.queryPayStatus(out_trade_no);
if ("SUCCESS".equals(map.get("trade_state"))) {
MessageResult = new MessageResult(true, "支付成功!");
//3. 如果支付成功, 支付日志表和订单表的支付状态改为已支付, redis的支付日志对象删除
orderService.updatePayStatus(userName);
break;
}
try {
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
// 如果5分钟没有支付则支付超时
if (flag > 100) {
MessageResult = new MessageResult(false, "二维码超时");
break;
}
flag++;
}
return MessageResult;
}
②Service层代码 - 根据自己的业务来做
/**
* 查询是否已经支付
* @param out_trade_no 订单号
* @return
*/
@Override
public Map queryPayStatus(String out_trade_no) {
Map param = new HashMap();
param.put("appid", appid); // 微信公众账号或开放平台APP的唯一标识
param.put("mch_id", partner); // 财付通平台的商户账号
param.put("out_trade_no", out_trade_no); // 订单号
param.put("nonce_str", WXPayUtil.generateNonceStr()); // 随机字符串
String url = "https://api.mch.weixin.qq.com/pay/orderquery"; // 查询url
try {
// 生成要发送的xml,调用微信SDK的API接口将封装的Map数据转换成Xml格式字符串
String xmlParam = WXPayUtil.generateSignedXml(param, partnerkey); // 参数2:财付通平台的商户密钥
// 使用HttpClient发送请求 - 参数为支付请求地址
HttpClient client = new HttpClient(url);
client.setHttps(true);
client.setXmlParam(xmlParam);
client.post();
// 获得结果 - xml字符串
String result = client.getContent();
// 调用微信SDK的API接口将Xml数据转换成Map对象
Map<String, String> map = WXPayUtil.xmlToMap(result);
return map;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
2、前端代码(Vue) - 查询支付结果
/**
* 查询支付结果
*/
queryPayStatus: function () {
let _this = this;
axios.get("/pay/queryPayStatus.do?out_trade_no=" + this.out_trade_no).then(function (response) {
if (response.data.success) { // 跳转支付成功页面
location.href = "paysuccess.html?money=" + _this.money;
} else {
if (response.data.message === '二维码超时') {
// 重新生成二维码
this.createNative();
} else { // 跳转支付失败页面
location.href = "payfail.html"
}
}
}).catch(function (reason) {
console.log(reason);
});
}
三、关闭订单操作
这里只提供后端服务层代码,因为我的业务里因为之前没考虑周全,是没有做这个功能的。但是这个功能在真正的项目中是必须要有的,假如某个人下单买了一个东西,他一直不支付怎么办?通常业务里都会有30分钟不支付,关闭订单的操作(含关闭微信支付)。如果真的要做的话,可以通过MQ延时队列来做。
/****
* 关闭订单操作
* @param out_trade_no 订单号
* @return
*/
public Map closePay(String out_trade_no) {
Map param=new HashMap();
param.put("appid", appid); // 微信公众账号或开放平台APP的唯一标识
param.put("mch_id", partner); // 财付通平台的商户账号
param.put("out_trade_no", out_trade_no); // 订单号
param.put("nonce_str", WXPayUtil.generateNonceStr()); // 随机字符串
// 关闭微信支付Url
String url="https://api.mch.weixin.qq.com/pay/closeorder";
try {
// 生成要发送的xml,调用微信SDK的API接口将封装的Map数据转换成Xml格式字符串
String xmlParam = WXPayUtil.generateSignedXml(param, partnerkey);
HttpClient client=new HttpClient(url);
client.setHttps(true);
client.setXmlParam(xmlParam);
client.post();
// 获得结果 - xml字符串
String result = client.getContent();
// 调用微信SDK的API接口将Xml数据转换成Map对象
Map<String, String> map = WXPayUtil.xmlToMap(result);
return map;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
HttpClient源码
/**
* http请求客户端
* @author Administrator
*/
public class HttpClient {
/** 请求地址 */
private String url;
/** 参数 */
private Map<String, String> param;
private int statusCode;
private String content;
private String xmlParam;
private boolean isHttps;
public boolean isHttps() {
return isHttps;
}
public void setHttps(boolean isHttps) {
this.isHttps = isHttps;
}
public String getXmlParam() {
return xmlParam;
}
public void setXmlParam(String xmlParam) {
this.xmlParam = xmlParam;
}
public HttpClient(String url, Map<String, String> param) {
this.url = url;
this.param = param;
}
public HttpClient(String url) {
this.url = url;
}
public void setParameter(Map<String, String> map) {
param = map;
}
public void addParameter(String key, String value) {
if (param == null)
param = new HashMap<String, String>();
param.put(key, value);
}
public void post() throws ClientProtocolException, IOException {
HttpPost http = new HttpPost(url);
setEntity(http);
execute(http);
}
public void put() throws ClientProtocolException, IOException {
HttpPut http = new HttpPut(url);
setEntity(http);
execute(http);
}
public void get() throws ClientProtocolException, IOException {
if (param != null) {
StringBuilder url = new StringBuilder(this.url);
boolean isFirst = true;
for (String key : param.keySet()) {
if (isFirst)
url.append("?");
else
url.append("&");
url.append(key).append("=").append(param.get(key));
}
this.url = url.toString();
}
HttpGet http = new HttpGet(url);
execute(http);
}
/**
* set http post,put param
*/
private void setEntity(HttpEntityEnclosingRequestBase http) {
if (param != null) {
List<NameValuePair> nvps = new LinkedList<NameValuePair>();
for (String key : param.keySet())
nvps.add(new BasicNameValuePair(key, param.get(key))); // 参数
http.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8)); // 设置参数
}
if (xmlParam != null) {
http.setEntity(new StringEntity(xmlParam, Consts.UTF_8));
}
}
private void execute(HttpUriRequest http) throws ClientProtocolException,
IOException {
CloseableHttpClient httpClient = null;
try {
if (isHttps) {
SSLContext sslContext = new SSLContextBuilder()
.loadTrustMaterial(null, new TrustStrategy() {
// 信任所有
public boolean isTrusted(X509Certificate[] chain,
String authType)
throws CertificateException {
return true;
}
}).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslContext);
httpClient = HttpClients.custom().setSSLSocketFactory(sslsf)
.build();
} else {
httpClient = HttpClients.createDefault();
}
CloseableHttpResponse response = httpClient.execute(http);
try {
if (response != null) {
if (response.getStatusLine() != null)
statusCode = response.getStatusLine().getStatusCode();
HttpEntity entity = response.getEntity();
// 响应内容
content = EntityUtils.toString(entity, Consts.UTF_8);
}
} finally {
response.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
httpClient.close();
}
}
public int getStatusCode() {
return statusCode;
}
public String getContent() throws ParseException, IOException {
return content;
}
}
经验记录:
1、二维码支付的时候查询是否支付成功的问题:
①Ajax短轮询,通过定时器异步刷新查询是否支付成功。
缺点:这种方式实时效果较差,而且对服务端的压力也较大。
②长轮询,后端循环查询是否支付成功。
缺点:长轮询服务端会长时间地占用资源,如果消息频繁发送的话会给服务端带来较大的压力。
③WebSocket 双向通信
RabbitMQ可以通过RabbitMQ Web STOMP 插件来做。
2、订单超时关闭订单的问题:
下单的时候发送MQ延迟消息,30分钟未支付,则消费这个延迟消息做一些未支付的业务处理。
3、微信支付回调地址需要外网能访问,如果是本地的话,可以通过内网穿透去做。