微信扫码支付文档
前言:2021年2月左右完成,内容借鉴哔哩哔哩的教学视频(忘记up的昵称了)
1.官方文档
官网链接:https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_7_0.shtml
官方文档:https://pay.weixin.qq.com/wiki/doc/api/index.html
SDK下载路径:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=11_1
2.需要提前在官网获取的参数
公司通常已经申请好啦
1.mch_id 商户号
2.appId 公众号ID
3.密钥
API密钥,key设置路径:微信商户平台(pay.weixin.qq.com)–>账户设置–>API安全–>密钥设置
3.流程和流程图
1.业务流程
1)商户后台调用统一下单API,向微信端传入参数(xml),微信端接收参数并返回状态(xml),若状态为SUCCESS,则返回二维码url:code_url ,若状态为FAIL,则返回错误信息
2)商户根据返回的code_url ,通过前端生成为二维码展示给客户
3)客户扫描二维码并支付,消息返回微信端,微信给客户反馈支付结果,并通过回调URL给商户返回订单信息和状态(xml)
4)商户根据返回的状态,进行后续操作,并将接收信息返回微信端(如果微信端没有接收到商户返回,会多次向商户发送订单信息)(xml)
2.流程图
4.项目的搭建和解构
1.新建一个Spring Boot 项目
2.下载java版本的SDK,解压后将工具类直接复制到项目里
3.项目目录
4.解构
5.统一下单
URL地址:https://api.mch.weixin.qq.com/pay/unifiedorder
请求参数参照官网,必填必须传递
SDK里提供了将map转换成xml的方法,没必要自己写(太容易报错)
controller: orderSubmitController 相关代码
//支付
@RequestMapping("/getUrl")
@ResponseBody
public Map<String ,String> getUrl(
@RequestParam(value = "totalFee") String totalFee,
@RequestParam(value = "orderNo") String orderNo1,
@RequestParam(value = "remark") String remark){
Map<String ,String> result = new HashMap<>();
try {
//获取订单号
String orderNo = orderNo1;
//获取订单描述
String body = remark;
//获取订单价格
String price = totalFee;
//自己封装的工具类
DataJoinUtils wxPayUtils = new DataJoinUtils();
//指定回调地址(应该是外网可访问地址,不能包含参数,此次用内网穿透获取的url)
String url = "http://rh6sm4.natappfree.cc/orders/unifiedorderNotify";
//统一下单,微信返回结果
result = wxPayUtils.wxPay(url,orderNo,price,"127.0.0.1",body);
//此处仅为了看返回状态和二维码url,无意义
if (result.get("return_code").equals("SUCCESS")) {
System.out.println("成功:"+result.get("return_code"));
}else {
System.out.println("失败:"+result.get("return_code"));
}
System.out.println("UQ:"+result.get("code_url"));
}catch (Exception e){
e.printStackTrace();
}
return result;
}
工具类:DataJoinUtils 对应的代码
/**
* 微信统一下单接口
* @param notify_url 回调地址
* @param out_trade_no 商户订单号
* @param total_fee 订单总金额
* @param ip IP
* @param body 商品内容描述
* @return
* @throws Exception
*/
public Map<String, String> wxPay(String notify_url, String out_trade_no, String total_fee, String ip, String body) throws Exception {
//将请求参数放入map
Map<String ,String> map = new HashMap<String, String>();
//APPTID
map.put("appid",ConfigConstant.appId);
//非必填项
map.put("attach","支付测试");
//商户号
map.put("mch_id",ConfigConstant.mchId);
//随机字符串
map.put("nonce_str",WXPayUtil.generateNonceStr());
//商品描述
map.put("body",body);
//商户订单号
map.put("out_trade_no",out_trade_no);
//标价币种
map.put("fee_type", "CNY");
//订单总金额,分
map.put("total_fee",total_fee);
//终端ip
map.put("spbill_create_ip",ip);
//回调地址
map.put("notify_url",notify_url);
//支付类型
map.put("trade_type","NATIVE");
//签名类型
map.put("sign_type", WXPayConstants.MD5);
//
map.put("product_id",out_trade_no);
System.out.println("map:"+map.toString());
//生成带有 sign 的 XML 格式字符串
StringBuilder builder = new StringBuilder();
String xml = WXPayUtil.generateSignedXml(map,ConfigConstant.key);
//指定与微信交互的url地址
String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
//调用自己封装的工具类提交响应
String str = UrlPreUtils.post(url,xml);
System.out.println("str:"+str);
Map<String,String> retmap = new HashMap<String, String>();
try {
//与微信交互并获取信息,将xml转换成map
retmap = WXPayUtil.xmlToMap(str);
}catch (Exception e){
e.printStackTrace();
}
return retmap;
}
工具类:UrlPreUtils 对应代码
//编码格式
private static final String ENCODING = "UTF-8";
private final static Logger logger = LoggerFactory.getLogger(HttpClientUtils.class);
/**
*Post JSON
* @param url 提交url
* @param json 提交响应
* @return
*/
public static String post(String url, String json){
StringBuffer requestText = new StringBuffer();
CloseableHttpResponse response = null;
CloseableHttpClient client = null;
HttpPost httpPost = new HttpPost(url);
StringEntity entityParams = null;
try {
entityParams = new StringEntity(json,"utf-8");
httpPost.setEntity(entityParams);
httpPost.setHeader("Content-Type","application/x-www-form-urlencoded");
client = HttpClients.createDefault();
response = client.execute(httpPost);
byte[] x = EntityUtils.toByteArray(response.getEntity());
requestText.append(new String(x,"utf-8"));
} catch (Exception e) {
logger.info("Request-"+ requestText.toString());
} finally {
return requestText.toString();
}
}
6.查询订单
通常客户支付后,会调用回调url 返回状态,但商户也可以自己查询
orderSubmitController
//订单支付状态查询
@RequestMapping("/getOrderData")
@ResponseBody
public Map<String ,String> getOrderData(@RequestParam(value = "orderNo") String orderNo1){
Map<String,String> resultSet = new HashMap<>();
try {
//获取订单号
String orderNo = orderNo1;
DataJoinUtils wxPayUtils = new DataJoinUtils();
//微信订单查询结果
resultSet = wxPayUtils.wxOrderQuery(orderNo);
}catch (Exception e){
e.printStackTrace();
}
return resultSet;
}
工具类:DataJoinUtils 对应代码
/**
* 微信查询
* @param out_trade_no 订单编号
* @return
* @throws Exception
*/
public Map<String,String> wxOrderQuery(String out_trade_no) throws Exception {
Map<String,String> map = new HashMap<String,String>();
//APPTID
map.put("appid",ConfigConstant.appId);
//商户号
map.put("mch_id",ConfigConstant.mchId);
//随机字符串
map.put("nonce_str",WXPayUtil.generateNonceStr());
//商户订单号
map.put("out_trade_no",out_trade_no);
//生成带有 sign 的 XML 格式字符串
String xml = WXPayUtil.generateSignedXml(map,ConfigConstant.key);
//指定与微信交互的url地址
String url = "https://api.mch.weixin.qq.com/pay/orderquery";
//与微信交互并获取信息,发送请求并获取响应
String str = UrlPreUtils.post(url,xml);
Map<String,String> retmap = new HashMap<String, String>();
try {
//xml转换成map
retmap = WXPayUtil.xmlToMap(str);
}catch (Exception e){
e.printStackTrace();
}
return retmap;
}
7.关闭订单
关闭订单后,二维码不再刷新
orderSubmitController
//关闭订单(订单关闭二维码失效,不能再刷新)
@RequestMapping("/closeOrder")
@ResponseBody
public String closeOrder(@RequestParam(value = "orderNo") String orderNo1){
String status = "";
try {
//获取订单号
String orderNo = orderNo1;
DataJoinUtils wxPayUtils = new DataJoinUtils();
//关闭订单结果
Map<String,String> resultSet = wxPayUtils.wxCloseOrder(orderNo);
//返回的状态
status = resultSet.get("result_code");
}catch (Exception e){
e.printStackTrace();
}
return status;
}
工具类:DataJoinUtils 对应代码
/**
* 关闭订单
* @param out_trade_no
* @return
* @throws Exception
*/
public Map<String,String> wxCloseOrder(String out_trade_no) throws Exception{
Map<String,String> map = new HashMap<String,String>();
//APPTID
map.put("appid",ConfigConstant.appId);
//商户号
map.put("mch_id",ConfigConstant.mchId);
//随机字符串
map.put("nonce_str",WXPayUtil.generateNonceStr());
//商户订单号
map.put("out_trade_no",out_trade_no);
//生成带有 sign 的 XML 格式字符串
String xml = WXPayUtil.generateSignedXml(map,ConfigConstant.key);
//指定与微信交互的url地址
String url = "https://api.mch.weixin.qq.com/pay/closeorder";
//与微信交互并获取信息
String str = UrlPreUtils.post(url,xml);
Map<String,String> retmap = new HashMap<String, String>();
try {
//xml转换成map
retmap = WXPayUtil.xmlToMap(str);
}catch (Exception e){
e.printStackTrace();
}
return retmap;
}
8.回调接口
回调地址在统一下单传递给微信端,但如果微信端没有调用的话,需要去官网设置回调地址
orderSubmitController
/**
* 回调接口
*/
@RequestMapping(value = {"/unifiedorderNotify"})
public void unifiedorderNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("回调中");
//商户订单号
String outTradeNo = null;
String xmlContent =null;
try {
//输入流里获取微信端发送的data
InputStream inputStream = request.getInputStream();
String requestXml = DataJoinUtils.getStreamString(inputStream);
//xml转换成map
Map<String, String> map = WXPayUtil.xmlToMap(requestXml);
String returnCode = map.get("return_code");
//校验是否已经支付成功&& 签名是否正确
if (returnCode.equals("SUCCESS") && WXPayUtil.isSignatureValid(map, ConfigConstant.key,WXPayConstants.SignType.MD5)) {
//商户订单号
outTradeNo = map.get("out_trade_no");
System.out.println("outTradeNo : " + outTradeNo);
//微信支付订单号
String transactionId = map.get("transaction_id");
System.out.println("transactionId : " + transactionId);
//支付完成时间
SimpleDateFormat payFormat = new SimpleDateFormat("yyyyMMddHHmmss");
Date payDate = payFormat.parse(map.get("time_end"));
SimpleDateFormat systemFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("支付时间:" + systemFormat.format(payDate));
//todo 根据支付结果修改数据库订单状态
// ....
//通过 response 给微信的应答 xml
xmlContent = "<xml>" +
"<return_code><![CDATA[SUCCESS]]></return_code>" +
"<return_msg><![CDATA[OK]]></return_msg>" +
"</xml>";
}else {
xmlContent = "<xml>" +
"<return_code><![CDATA[FAIL]]></return_code>"+
"<return_msg><![CDATA[签名失败]]></return_msg>"+
"</xml>";
}
} catch (Exception e) {
e.printStackTrace();
}
//给微信反馈
DataJoinUtils.responsePrint(response, xmlContent);
}
工具类:DataJoinUtils 对应代码
/**
* 输入流转化为字符串
*
* @param inputStream 流
* @return String 字符串
* @throws Exception
*/
public static String getStreamString(InputStream inputStream) throws Exception {
StringBuffer buffer = new StringBuffer();
InputStreamReader inputStreamReader = null;
BufferedReader bufferedReader = null;
try {
inputStreamReader = new InputStreamReader(inputStream,"UTF-8");
bufferedReader = new BufferedReader(inputStreamReader);
String line;
while ((line = bufferedReader.readLine()) != null) {
buffer.append(line);
}
} catch (Exception e) {
throw new Exception();
} finally {
if (bufferedReader != null) {
bufferedReader.close();
}
if (inputStreamReader != null) {
inputStreamReader.close();
}
if (inputStream != null) {
inputStream.close();
}
}
return buffer.toString();
}
/**
* 返回信息给微信 商户已经接收到回调
*
* @param response
* @param content 内容
* @throws Exception
*/
public static void responsePrint(HttpServletResponse response, String content) throws Exception {
response.setCharacterEncoding("UTF-8");
response.setContentType("text/xml");
response.getWriter().print(content);
response.getWriter().flush();
response.getWriter().close();
}
9.内网穿透
地址:http://grbj7n.natappfree.cc
请按照官网教程操作
10.二维码生成
选择了jQuery 和QRCode
参考地址:http://code.ciaoca.com/javascript/qrcode/
<script src="qrcode.js"></script>
<div id="qrcode"></div>
var qrcode = new QRCode("qrcode");
qrcode.makeCode(codeUrl);
11.注意事项
1.回调地址必须是外网可访问的且无参
2.微信端与商户间传递的都是xml格式
3.jar包冲突可以将引用的依赖版本降低
4.前端和后端交互记得配置application.properties
文件,
spring.thymeleaf.prefix=classpath:/templates/
#禁止thymeleaf缓存(建议:开发环境设置为false,生成环境设置为true)
spring.thymeleaf.cache=false