前言

该文主要是手把手教你如何在SpringBoot 中集成微信扫码支付,以及集成的过程需要注意的问题事项。另外需要感谢 vbirdbest 关于微信支付和支付宝支付相关包博客总结。因为文中很多地方参考了vbirdbest的博客。 vbirdbest 博主关于支付宝和微信相关总结GitHub地址。vbirdbest总结已经很好了,那么我为什么要在写一篇呢?我想通过另一种角度带你如何看微信文档并能自己实现它。以及我们需要注意的问题。毕竟涉及支付不能太马虎。

准备工作

在开发微信支付功能之前,首先要确保你的微信公众号是服务号,因为只有服务号才有申请微信支付的权限。

申请服务号支付权限,相当于在微信商户平台申请与之对应的商户平台账号。当商户平台帐号申请完毕后,微信会给你绑定的邮箱发送一个包含商户号的邮件,这个商户号很重要,后面微信支付 API 调用会用到。

扫码支付还需要在微信商户平台-账户设置-安全设置-api安全 设置一个32位 Key。这个Key是用来生成验证的sign使用的。具体使用后面会介绍到。

详细操作请直接参考 vbirdbest 博主的博客:Spring Boot入门教程(三十九):微信支付集成-申请服务号和微信支付:

查看微信开发文档

访问:https://pay.weixin.qq.com/wiki/doc/api/index.html 如下图所示,选择 Native 支付

扫码支付有2中模式:

  • 模式一:需要设置回调 URL 使用流程比较复杂。
  • 模式二:无需设置回调 URL 使用流程比较简单。

该文介绍使用的是模式二的方式,模式二业务流程说明如下:

(1)商户后台系统根据用户选购的商品生成订单。

(2)用户确认支付后调用微信支付【统一下单API】生成预支付交易;

(3)微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url。

(4)商户后台系统根据返回的code_url生成二维码。

(5)用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。

(6)微信支付系统收到客户端请求,验证链接有效性后发起用户支付,要求用户授权。

(7)用户在微信客户端输入密码,确认支付后,微信客户端提交授权。

(8)微信支付系统根据用户授权完成支付交易。

(9)微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。

(10)微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。

(11)未收到支付通知的情况,商户后台系统调用【查询订单API】。

(12)商户确认订单已支付后给用户发货。

了解完业务流程后,需要查看微信统一下单API 文档。这里需要提示的是,这个统一下单API一定要好好阅读。

统一下单API 大致信息如下:

  • URL地址:https://api.mch.weixin.qq.com/pay/unifiedorder 不需要证书
  • 处了总金额类型是int外,其他参数一律是String
  • 必须的参数有:服务商的 appid、商户号(mch_id)、随机字符串 nonce_str、签名 sign、商品描述 body、商户订单号 out_trade_no、总金额 total_fee、终端IP spbill_create_ip、通知地址(回调地址) notify_url 、交易类型 trade_type。

需要注意的内容如下:

  • 总金额是以分计算的,需要我们将金额进行转换
  • sign是将参数字典排序然后最后拼接Key(微信商户平台-账户设置-安全设置-api安全 设置一个32位 Key)进行MD5或HMAC-SHA256 进行加密的结果。
  • 通知地址 notify_url 必须是外网访问URL,并且不能携带参数。

签名算法和生成随机数算法规则: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=4_3

需要使用的工具如下:
(签名校验工具): https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=20_1

根据文档编写最简代码实现扫码支付

阅读完API 我们就可以开始写调用微信支付的测试代码了。其实微信支付的调用,就是把我们的商品信息以及商品的价格数据发送给微信,然后微信在把支付的code返回。我们根据支付code生成二维码后让用户扫码,用户付款成功后通过通知地址 notify_url 回调我们的系统,通知系统支付成功。

在写代码之前,先吐槽一下微信。我记得我们公司当时在对接微信时,微信连 Java 的 SDK 都没有。有啥问题也联系不上客服,当时对接的时候出现问题只能百度。还有就是文档啥的,跟阿里比差远了。废话少说开始我们的测试代码编写了。

创建 WeiXinPayTestController 然后定义 nativePay 访问方法

public class WeiXinPayTestController {private static final String UNIFIEDORDERURL = "https://api.mch.weixin.qq.com/pay/unifiedorder";private static final String GETSIGNKEYURL = "https://api.mch.weixin.qq.com/sandboxnew/pay/getsignkey";private static final String SANDBOXUNIFIEDORDERURL = "https://api.mch.weixin.qq.com/sandboxnew/pay/unifiedorder";private static final String ORDERQUERYURL = "https://api.mch.weixin.qq.com/pay/orderquery";@Autowiredprivate WeiXinPayProperties weiXinPayProperties;@Autowiredprivate RestTemplate restTemplate;/*** 正式支付* @return*/@RequestMapping("/nativePay")public String nativePay(){}

我尝试用沙盒进行支付结果各种密钥不对,最后只好暂时先放弃了。

WeiXinPayProperties 是通过SpringBoot @ConfigurationProperties定义的配置文件类具体内容如下:

@Component
@ConfigurationProperties(prefix="wx.pay")
public class WeiXinPayProperties {/**合作身份者ID */private String appid;/** 商户号 */private String mchId;/** 商户号密钥 */private String appsecret;/** API 密钥 商户后台配置的一个32位的key 微信商户平台-账户设置-安全设置-api安全 */private String key;/**是否使用沙箱*/private String useSandbox;/** 沙箱环境API 密钥  */private String sandboxKey;/** 回调地址 */private String notifyUrl;省略get and set方法
}

微信支付Api的调用我们通过 RestTemplate 来完成。

在 nativePay 方法内配置微信的基础信息:公众账号ID、商户号,随机字符串、终端IP、交易类型

Map<String, String> requestData = new HashMap<String, String>();
requestData.put("appid", weiXinPayProperties.getAppid());//公众账号ID
requestData.put("mch_id", weiXinPayProperties.getMchId());//商户号
requestData.put("nonce_str", RandomUtil.randomString(15));//随机字符串 32位以内
requestData.put("spbill_create_ip", "15.23.160.111");
requestData.put("trade_type", "NATIVE");//交易类型 扫码支付

spbill_create_ip APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP。

配置微信支付自定义支付信息参数

requestData.put("attach", "附加数据远洋返回");
requestData.put("body", "订单号 BW_000001");//商品简单描述
requestData.put("out_trade_no", "BW_000001");//商户订单号
requestData.put("total_fee", WeiXinUtil.getMoney("0.01"));//标价金额 按照分进行计算
requestData.put("notify_url", "www.beiwaiclass.com");//通知地址 异步接收微信支付结果通知的回调地址必须外网访问 不能携带参数

配置微信支付sign信息参数

String sign = null;
String payUrl = null;
if(Boolean.valueOf(weiXinPayProperties.getUseSandbox())){sign = WeiXinUtil.generateSign(requestData,weiXinPayProperties.getKey());//生成签名payUrl = UNIFIEDORDERURL;
}else{sign = WeiXinUtil.generateSign(requestData,weiXinPayProperties.getSandboxKey());//生成签名payUrl = SANDBOXUNIFIEDORDERURL;
}requestData.put("sign", sign);

将配置Map信息转换成String

String mapToXmlStr = XmlUtil.mapToXmlStr(requestData, "xml");

将mapToXmlStr作为参数调用微信统一下单Api

 HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_XML);HttpEntity<String> formEntity = new HttpEntity<>(mapToXmlStr, headers);ResponseEntity<String> postForEntity = restTemplate.postForEntity(payUrl, formEntity, String.class);
//获取微信返回的信息
String returnXmlString = postForEntity.getBody();
Map<String, Object> xmlToMap = XmlUtil.xmlToMap(returnXmlString);
String returnCode = (String)xmlToMap.get("return_code");
if("SUCCESS".equals(returnCode)){String codeUrl = (String)xmlToMap.get("code_url");return codeUrl;
}

前台访问的页面(gotoNativePage.html)代码如下:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body ><h3>购买商品:苹果</h3>
<h3>价格:20</h3>
<h3>数量:10个</h3>
<div id="qrcodeCanvas"></div><button style="width: 8%; height: 30px; alignment: center; background: #b49e8f" onclick="commitOrder()">生成支付二维码</button>
<script type="text/javascript" src="/sbe2/jquery-1.8.3.min.js"></script>
<script type="text/javascript" src="/sbe2/jquery.qrcode.js"></script>
<script type="text/javascript" src="/sbe2/qrcode.js"></script>
<script>function commitOrder() {$.ajax({type: "POST",url: "http://localhost:8090/sbe2/wx/naitvePay",data: null,success: function(data) {jQuery('#qrcodeCanvas').qrcode({text: data,typeNumber:-10});    }})}
</script></body>
</html>

测试结果:
http://localhost:8090/sbe2/gotoNativePage.html
点击生成支付二维码会生成要支付的二维码在数量下面。

支付回调的代码:

     /*** 支付回调* @throws Exception */@RequestMapping("/wxNotify")public void wxNotify(HttpServletRequest request) throws Exception{Map<String, String> parseNotifyParameter = parseNotifyParameter(request);String sign = WeiXinUtil.generateSign(parseNotifyParameter,weiXinPayProperties.getKey());//生成签名if(sign.equals(parseNotifyParameter.get("sign"))){//支付成功}}
    /*** 从request的inputStream中获取参数* @param request* @return* @throws Exception*/public Map<String, String> parseNotifyParameter(HttpServletRequest request) throws Exception {InputStream inputStream = request.getInputStream();ByteArrayOutputStream outSteam = new ByteArrayOutputStream();byte[] buffer = new byte[1024];int length = 0;while ((length = inputStream.read(buffer)) != -1) {outSteam.write(buffer, 0, length);}outSteam.close();inputStream.close();// 获取微信调用我们notify_url的返回信息String resultXml = new String(outSteam.toByteArray(), "utf-8");Map<String, String> notifyMap = WeiXinUtil.xmlToMap(resultXml);return notifyMap;}

支付回调需要外网,如果你想进行测试可以进行本地域名映射到外网进行整体的测试。因为账号我无法在进行更改,所以就没有做。但是代码是没有啥大问题的。

使用官方 SDK 实现扫码支付

我们已经简单的完成微信支付的集成,但是个人建议使用官方的SDK ,因为其他的SDK可能有XXE漏洞。关于XXE漏洞官方处理方案 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=23_5

下载JAVA版的SDK,然后导入到将SDK 通过 mvn install 安装到本地仓库中。然后再SpringBoot中引入 SDK 的依赖。
我下载的 SDK 版本是:3.0.9

     <dependency><groupId>com.github.wxpay</groupId><artifactId>wxpay-sdk</artifactId><version>3.0.9</version></dependency>

你也可以根据自己的需要对SDK进行修改。
依赖添加完毕后第一步需要集成 微信SDK的配置抽象类:

@Component
@ConfigurationProperties(prefix="wxpay")
public class WeiXinOfficPayProperties extends WXPayConfig {private String appID;private String mchID;private String key;private InputStream certStream;private int httpConnectTimeoutMs;private int httpReadTimeoutMs;private IWXPayDomain WXPayDomain;
}

微信扫码支付代码:

 /*** 微信扫码支付* @return*/@RequestMapping("/naitvePay")public String naitvePay(){try {WXPay wxpay = new WXPay(weiXinPayProperties);Map<String, String> data = new HashMap<String, String>();data.put("body", "购买苹果10个");data.put("out_trade_no", "2016090910595900000012");data.put("device_info", "");data.put("fee_type", "CNY");data.put("total_fee", "1");data.put("spbill_create_ip", "123.12.12.123");data.put("notify_url", "http://www.example.com/wxpay/notify");data.put("trade_type", "NATIVE");  // 此处指定为扫码支付data.put("product_id", "12");Map<String, String> resp = wxpay.unifiedOrder(data);String returnCode = resp.get("return_code");if("SUCCESS".equals(returnCode)){String codeUrl = resp.get("code_url");return codeUrl;}} catch (Exception e) {e.printStackTrace();}return null;}

微信支付的回调:

/*** 微信支付成功后回调地址* @param request*/@RequestMapping("/wxNotify")public void wxNotify(HttpServletRequest request) {try {Map<String, String> notifyMap = WeiXinPaySupportUtil.getNotifyParameter(request);WXPay wxpay = new WXPay(weiXinPayProperties);if (wxpay.isPayResultNotifySignatureValid(notifyMap)) {// 签名正确// 进行处理。// 注意特殊情况:订单已经退款,但收到了支付结果成功的通知,不应把商户侧订单状态从退款改成支付成功}else {// 签名错误,如果数据里没有sign字段,也认为是签名错误}} catch (Exception e) {e.printStackTrace();}}

进行测试:
http://localhost:8090/sbe2/gotoNativePage2.html

需要说明的是官方 3.0.9 Java SDK 正式支付生成 sign 默认使用的是 HMAC-SHA256

小结

微信支付集成并不是很难,但是集成的时候有很多地方要注意:

  • 首先是 sign 的生成需要排序传递参数,但是不包含 sign 本身这个字段。
  • 回调地址 notify_url 切记需要外网访问,写完回调的方法后建议自己先调用一下看看能不能调用成功。
  • 订单在重复支付的时候,要保证支付的参数和第一次支付一致,否则会生成code失败。
  • 如果没有使用微信支付官方SDK,要注意XXE漏洞。也可以直接通过前台轮询进行查询的方式判断是否支付成功。
  • 后台要添加手动判断订单是否支付成功,并且同步的操作。防止订单支付成功,用户没有够买到的情况。

代码示例

具体代码示例请查看 :
博客的读者可以通过查看下面仓库的中的模块工程名: spring-boot-2.x-wxpay

Github:spring-boot-2.x-wxpay
如果您对这些感兴趣,欢迎 star、或转发给予支持!转发请标明出处!

参考文献

Spring Boot入门教程(四十一):微信支付集成-扫码支付:

Spring Boot入门教程(三十九):微信支付集成-申请服务号和微信支付:

SpringBoot 2 集成微信扫码支付相关推荐

  1. 微擎支付返回商户单号_一步一步教你在SpringBoot中集成微信扫码支付

    一:准备工作 使用微信支付需要先开通服务号,然后还要开通微信支付,最后还要配置一些开发参数,过程比较多. 申请服务号(企业) 开通微信支付 开发配置 具体准备工作请参考Spring Boot入门教程( ...

  2. PC网站微信扫码支付,Native支付,“当前商户号暂不支持关联该类型的appid“,“签名错误,请检查后再试““springBoot 微信支付“

    springBoot 微信支付 PC网站微信扫码支付-Native支付 一.采坑大合集 1.当前商户号暂不支持关联该类型的appid 2.签名错误,请检查后再试 二.springboot集成微信支付D ...

  3. 微信支付(一)SpringBoot 实现微信扫码支付/Native支付

    SpringBoot 实现微信扫码支付/Native支付 一.背景 在开发一个捐赠项目时须在pc端接入微信扫码支付(Native 扫码支付),在微信端接入微信公众号支付(Jsapi 支付).后端使用的 ...

  4. 微信支付宝服务器分布,支付宝微信扫码支付中间件「第二届立创商城电子制作节30强作品」...

    原标题:支付宝微信扫码支付中间件「第二届立创商城电子制作节30强作品」 本作品为第二届立创商城电子制作节30强入围作品,作者立创社区ID:云逸Baby:转载请注明出处,未经允许不得用作商业用途.作品原 ...

  5. vue+element模仿电商商城,前后端分离实现,下单微信扫码支付

    1.前言 接上一篇<vue+element+SpringBoot+OAuth2+Spring Security+Redis+mybatis-plus+mysql+swagger模仿商城,前后端分 ...

  6. 微信支付:支付流程分析、微信扫码支付(HttpClient)、微信支付二维码生成、检测支付状态、订单状态操作准备工作、支付信息回调、MQ处理支付回调状态、定时处理订单状态

    微信支付 微信支付开发的整体思路 生成支付二维码 查询支付状态(微信的服务器) 实现订单状态的修改.删除订单 支付状态回查->微信服务器将支付状态返回给支付微服务 MQ处理支付回调状态 Rabb ...

  7. JAVA微信扫码支付及微信App支付开发(模式二)完整功能实现

    一,准备工作 事前申请一个商家版的微信公众号(目前微信支付只有商家版公众号可开通),然后开通微信支付功能,并做相应的配置. 申请开通微信公众号和开通微信支付需要等待审核,一般都5个工作日左右.开通成功 ...

  8. 2021年django微信扫码支付流程--全攻略(阻止回调请看下一章)

    PC端,微信扫码支付, 我们产生支付二维码后,等用户支付, 用户扫码支付成功后,我们接收腾迅的回调数据 但是我们如果不阻止回调,则我们的服务器会被腾迅请求很多次在几个小时之内, 所以我们一定要阻止回调 ...

  9. c#版在pc端发起微信扫码支付

    c#版在pc端发起微信扫码支付 参考此连接(https://www.cnblogs.com/vinsonLu/p/5166214.html#!comments) posted on 2019-01-0 ...

最新文章

  1. c语言 如何读多种数据类型 非类,c语言程序设计教学大纲(非电气类)文档.doc
  2. 【跃迁之路】【473天】刻意练习系列232(2018.05.24)
  3. 微信公众平台:反正公众号的一切操作都从这里入手!
  4. 组件库实战 | 用vue3+ts实现全局Header和列表数据渲染ColumnList
  5. 计算机二级考试python_全国计算机等级考试考试大纲(2018年版)
  6. python3 面向对象编程 下载_Python3(7) Python 面向对象编程
  7. 纪念逝去的岁月——C/C++快速排序
  8. hdu_3555 bomb
  9. 服务器修改开机启动项,启动项设置_服务器开机启动项
  10. c语言读写txt坐标文件数据,用c语言读写sgy格式的地震数据文件.pdf
  11. CodeMirror用户手册
  12. 怎样将UltraISO做的启动U盘还原成原来的样子
  13. 什么是智能建筑,智能建筑系统主要有哪几部分组成?
  14. PTA 7-10 查询水果价格
  15. 脱壳_00_压缩壳_ASPACK
  16. 无尘间手把手教你西数开盘
  17. java中的空指针异常处理
  18. 【第59篇】MetaFormer实际上是你所需要的视觉
  19. django教程day06
  20. linux 硬盘使用监控,zabbix3.2监控linux磁盘使用空间

热门文章

  1. mfc formview中的关闭视图函数_VC|API消息处理(回调函数+分支语句)与MFC中的消息映射函数...
  2. java struts2 上传图片_Java框架Struts2实现图片上传功能
  3. python多线程有用吗_当CPU利用率已经接近100%时,多线程有帮助吗?
  4. mysql在test库中创建表stu_1.在mysql的test数据库中新建表,表名为student,表结构如下:...
  5. c语言学习-使用指针求一个字符串的长度
  6. android 串口通信丢包,新手求教为什么串口接收数据总丢包
  7. OpenShift 4 - DevSecOps Workshop (12) - 用CodeReady Workspace编辑提交应用代码
  8. RHEL 8 - 记录用户会话操作
  9. 使用Entity Developer构建ASP.NET Core Web API应用程序
  10. sessionstorage ie8下跨页面_前端页面布局困难?教你用盒子模型一招解决