学习目标

  • 能够说出微信支付开发的整体思路
  • 思路:生成支付二维码
  • 思路:查询支付状态
  • 实现支付成功修改订单的状态
    • MQ发送消息
    • MQ接收消息
  • 支付成功之后微信需要通知商户进行支付状态的信息

    1 开发准备

    1.1 开发文档

    微信支付接口调用的整体思路:
    按API要求组装参数,以XML方式发送(POST)给微信支付接口(URL),微信支付接口也是以XML方式给予响应。程序根据返回的结果(其中包括支付URL)生成二维码或判断订单状态。
    在线微信支付开发文档:
    https://pay.weixin.qq.com/wiki/doc/api/index.html
    如果你不能联网,请查阅讲义配套资源 (资源\配套软件\微信扫码支付\开发文档)
    我们在本章课程中会用到”统一下单”和”查询订单”两组API ```properties
  1. appid:微信公众账号或开放平台APP的唯一标识
  2. mch_id:商户号 (配置文件中的partner)
  3. partnerkey:商户密钥
  4. sign:数字签名, 根据微信官方提供的密钥和一套算法生成的一个加密信息, 就是为了保证交易的安全性

    1. <a name="kGoTL"></a>
    2. ### 1.2 微信支付模式
    3. **模式二**<br />![1558448510488.png](https://cdn.nlark.com/yuque/0/2021/png/12589476/1636275950612-73206401-d1d4-47ec-942b-2641db59a6e4.png#clientId=u8ef6b4fa-56e7-4&from=drop&id=u4a37ef48&margin=%5Bobject%20Object%5D&name=1558448510488.png&originHeight=1168&originWidth=1184&originalType=binary&ratio=1&size=213236&status=done&style=none&taskId=u8f26b017-2c78-4a7a-ba81-7bb5e6b9bee)<br />业务流程说明:
    4. ```properties
    5. 1.商户后台系统根据用户选购的商品生成订单。
    6. 2.用户确认支付后调用微信支付【统一下单API】生成预支付交易;
    7. 3.微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url。
    8. 4.商户后台系统根据返回的code_url生成二维码。
    9. 5.用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。
    10. 6.微信支付系统收到客户端请求,验证链接有效性后发起用户支付,要求用户授权。
    11. 7.用户在微信客户端输入密码,确认支付后,微信客户端提交授权。
    12. 8.微信支付系统根据用户授权完成支付交易。
    13. 9.微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。
    14. 10.微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。
    15. 11.未收到支付通知的情况,商户后台系统调用【查询订单API】。
    16. 12.商户确认订单已支付后给用户发货。

    1.3 微信支付SDK

    微信支付提供了SDK, 大家下载后打开源码,install到本地仓库。
    第12天 微信支付 - 图1
    课程配套的本地仓库已经提供jar包,所以安装SDK步骤省略。使用微信支付SDK,在使用微信SDK的maven工程中引入依赖

    1. <!--微信支付-->
    2. <dependency>
    3. <groupId>com.github.wxpay</groupId>
    4. <artifactId>wxpay-sdk</artifactId>
    5. <version>0.0.3</version>
    6. </dependency>

    我们主要会用到微信支付SDK的以下功能:
    获取随机字符串

    1. WXPayUtil.generateNonceStr()

    MAP转换为XML字符串(自动添加签名)

    1. WXPayUtil.generateSignedXml(param, partnerkey)

    XML字符串转换为MAP

    1. WXPayUtil.xmlToMap(result)

    为了方便微信支付开发,我们可以在changgou-common工程下引入依赖

    1. <!--微信支付-->
    2. <dependency>
    3. <groupId>com.github.wxpay</groupId>
    4. <artifactId>wxpay-sdk</artifactId>
    5. <version>0.0.3</version>
    6. </dependency>

    1.4 HttpClient工具类

    HttpClient是Apache Jakarta Common下的子项目,用来提供高效的、最新的、功能丰富的支持HTTP协议的客户端编程工具包,并且它支持HTTP协议最新的版本和建议。HttpClient已经应用在很多的项目中,比如Apache Jakarta上很著名的另外两个开源项目Cactus和HTMLUnit都使用了HttpClient。
    HttpClient通俗的讲就是模拟了浏览器的行为,如果我们需要在后端向某一地址提交数据获取结果,就可以使用HttpClient.
    关于HttpClient(原生)具体的使用不属于我们本章的学习内容,我们这里这里为了简化HttpClient的使用,提供了工具类HttpClient(对原生HttpClient进行了封装)
    HttpClient工具类代码:

    1. public class HttpClient {
    2. private String url;
    3. private Map<String, String> param;
    4. private int statusCode;
    5. private String content;
    6. private String xmlParam;
    7. private boolean isHttps;
    8. public boolean isHttps() {
    9. return isHttps;
    10. }
    11. public void setHttps(boolean isHttps) {
    12. this.isHttps = isHttps;
    13. }
    14. public String getXmlParam() {
    15. return xmlParam;
    16. }
    17. public void setXmlParam(String xmlParam) {
    18. this.xmlParam = xmlParam;
    19. }
    20. public HttpClient(String url, Map<String, String> param) {
    21. this.url = url;
    22. this.param = param;
    23. }
    24. public HttpClient(String url) {
    25. this.url = url;
    26. }
    27. public void setParameter(Map<String, String> map) {
    28. param = map;
    29. }
    30. public void addParameter(String key, String value) {
    31. if (param == null)
    32. param = new HashMap<String, String>();
    33. param.put(key, value);
    34. }
    35. public void post() throws ClientProtocolException, IOException {
    36. HttpPost http = new HttpPost(url);
    37. setEntity(http);
    38. execute(http);
    39. }
    40. public void put() throws ClientProtocolException, IOException {
    41. HttpPut http = new HttpPut(url);
    42. setEntity(http);
    43. execute(http);
    44. }
    45. public void get() throws ClientProtocolException, IOException {
    46. if (param != null) {
    47. StringBuilder url = new StringBuilder(this.url);
    48. boolean isFirst = true;
    49. for (String key : param.keySet()) {
    50. if (isFirst) {
    51. url.append("?");
    52. }else {
    53. url.append("&");
    54. }
    55. url.append(key).append("=").append(param.get(key));
    56. }
    57. this.url = url.toString();
    58. }
    59. HttpGet http = new HttpGet(url);
    60. execute(http);
    61. }
    62. /**
    63. * set http post,put param
    64. */
    65. private void setEntity(HttpEntityEnclosingRequestBase http) {
    66. if (param != null) {
    67. List<NameValuePair> nvps = new LinkedList<NameValuePair>();
    68. for (String key : param.keySet()) {
    69. nvps.add(new BasicNameValuePair(key, param.get(key))); // 参数
    70. }
    71. http.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8)); // 设置参数
    72. }
    73. if (xmlParam != null) {
    74. http.setEntity(new StringEntity(xmlParam, Consts.UTF_8));
    75. }
    76. }
    77. private void execute(HttpUriRequest http) throws ClientProtocolException,
    78. IOException {
    79. CloseableHttpClient httpClient = null;
    80. try {
    81. if (isHttps) {
    82. SSLContext sslContext = new SSLContextBuilder()
    83. .loadTrustMaterial(null, new TrustStrategy() {
    84. // 信任所有
    85. @Override
    86. public boolean isTrusted(X509Certificate[] chain,
    87. String authType)
    88. throws CertificateException {
    89. return true;
    90. }
    91. }).build();
    92. SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
    93. sslContext);
    94. httpClient = HttpClients.custom().setSSLSocketFactory(sslsf)
    95. .build();
    96. } else {
    97. httpClient = HttpClients.createDefault();
    98. }
    99. CloseableHttpResponse response = httpClient.execute(http);
    100. try {
    101. if (response != null) {
    102. if (response.getStatusLine() != null) {
    103. statusCode = response.getStatusLine().getStatusCode();
    104. }
    105. HttpEntity entity = response.getEntity();
    106. // 响应内容
    107. content = EntityUtils.toString(entity, Consts.UTF_8);
    108. }
    109. } finally {
    110. response.close();
    111. }
    112. } catch (Exception e) {
    113. e.printStackTrace();
    114. } finally {
    115. httpClient.close();
    116. }
    117. }
    118. public int getStatusCode() {
    119. return statusCode;
    120. }
    121. public String getContent() throws ParseException, IOException {
    122. return content;
    123. }
    124. }

    HttpClient工具类使用的步骤:

    1. HttpClient client=new HttpClient(请求的url地址);
    2. client.setHttps(true);//是否是https协议
    3. client.setXmlParam(xmlParam);//发送的xml数据
    4. client.post();//执行post请求
    5. String result = client.getContent(); //获取结果

    将HttpClient工具包放到common工程下并引入依赖,引入依赖后就可以直接使用上述的工具包了。

    1. <!--httpclient支持-->
    2. <dependency>
    3. <groupId>org.apache.httpcomponents</groupId>
    4. <artifactId>httpclient</artifactId>
    5. </dependency>

    1.5 支付微服务搭建

    (1)创建changgou-service-pay
    创建支付微服务changgou-service-pay,只要实现支付相关操作。
    (2)application.yml
    创建application.yml,配置文件如下:

    1. server:
    2. port: 18092
    3. spring:
    4. application:
    5. name: pay
    6. main:
    7. allow-bean-definition-overriding: true
    8. eureka:
    9. client:
    10. service-url:
    11. defaultZone: http://127.0.0.1:7001/eureka
    12. instance:
    13. prefer-ip-address: true
    14. feign:
    15. hystrix:
    16. enabled: true
    17. #hystrix 配置
    18. hystrix:
    19. command:
    20. default:
    21. execution:
    22. timeout:
    23. #如果enabled设置为false,则请求超时交给ribbon控制
    24. enabled: true
    25. isolation:
    26. strategy: SEMAPHORE
    27. #微信支付信息配置
    28. weixin:
    29. appid: wx8397f8696b538317
    30. partner: 1473426802
    31. partnerkey: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb
    32. notifyurl: http://www.itcast.cn

    appid: 微信公众账号或开放平台APP的唯一标识
    partner:财付通平台的商户账号
    partnerkey:财付通平台的商户密钥
    notifyurl: 回调地址
    (3)启动类创建
    changgou-service-pay中创建com.changgou.WeixinPayApplication,代码如下:

    1. @SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
    2. @EnableEurekaClient
    3. public class WeixinPayApplication {
    4. public static void main(String[] args) {
    5. SpringApplication.run(WeixinPayApplication.class,args);
    6. }
    7. }

    2 微信支付二维码生成

    2.1需求分析与实现思路

    在支付页面上生成支付二维码,并显示订单号和金额
    用户拿出手机,打开微信扫描页面上的二维码,然后在微信中完成支付
    第12天 微信支付 - 图2

    2.2 实现思路

    我们通过HttpClient工具类实现对远程支付接口的调用。
    接口链接:https://api.mch.weixin.qq.com/pay/unifiedorder
    具体参数参见“统一下单”API, 构建参数发送给统一下单的url ,返回的信息中有支付url,根据url生成二维码,显示的订单号和金额也在返回的信息中。

    2.3 代码实现

    (1)业务层
    新增com.changgou.service.WeixinPayService接口,代码如下:

    1. public interface WeixinPayService {
    2. /*****
    3. * 创建二维码
    4. * @param out_trade_no : 客户端自定义订单编号
    5. * @param total_fee : 交易金额,单位:分
    6. * @return
    7. */
    8. public Map createNative(String out_trade_no, String total_fee);
    9. }

    创建com.changgou.service.impl.WeixinPayServiceImpl类,并发送Post请求获取预支付信息,包含二维码扫码支付地址。代码如下:

    1. @Service
    2. public class WeixinPayServiceImpl implements WeixinPayService {
    3. @Value("${weixin.appid}")
    4. private String appid;
    5. @Value("${weixin.partner}")
    6. private String partner;
    7. @Value("${weixin.partnerkey}")
    8. private String partnerkey;
    9. @Value("${weixin.notifyurl}")
    10. private String notifyurl;
    11. /****
    12. * 创建二维码
    13. * @param out_trade_no : 客户端自定义订单编号
    14. * @param total_fee : 交易金额,单位:分
    15. * @return
    16. */
    17. @Override
    18. public Map createNative(String out_trade_no, String total_fee){
    19. try {
    20. //1、封装参数
    21. Map param = new HashMap();
    22. param.put("appid", appid); //应用ID
    23. param.put("mch_id", partner); //商户ID号
    24. param.put("nonce_str", WXPayUtil.generateNonceStr()); //随机数
    25. param.put("body", "畅购"); //订单描述
    26. param.put("out_trade_no",out_trade_no); //商户订单号
    27. param.put("total_fee", total_fee); //交易金额
    28. param.put("spbill_create_ip", "127.0.0.1"); //终端IP
    29. param.put("notify_url", notifyurl); //回调地址
    30. param.put("trade_type", "NATIVE"); //交易类型
    31. //2、将参数转成xml字符,并携带签名
    32. String paramXml = WXPayUtil.generateSignedXml(param, partnerkey);
    33. ///3、执行请求
    34. HttpClient httpClient = new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder");
    35. httpClient.setHttps(true);
    36. httpClient.setXmlParam(paramXml);
    37. httpClient.post();
    38. //4、获取参数
    39. String content = httpClient.getContent();
    40. Map<String, String> stringMap = WXPayUtil.xmlToMap(content);
    41. System.out.println("stringMap:"+stringMap);
    42. //5、获取部分页面所需参数
    43. Map<String,String> dataMap = new HashMap<String,String>();
    44. dataMap.put("code_url",stringMap.get("code_url"));
    45. dataMap.put("out_trade_no",out_trade_no);
    46. dataMap.put("total_fee",total_fee);
    47. return dataMap;
    48. } catch (Exception e) {
    49. e.printStackTrace();
    50. }
    51. return null;
    52. }
    53. }

    (2) 控制层
    创建com.changgou.controller.WeixinPayController,主要调用WeixinPayService的方法获取创建二维码的信息,代码如下:

    1. @RestController
    2. @RequestMapping(value = "/weixin/pay")
    3. @CrossOrigin
    4. public class WeixinPayController {
    5. @Autowired
    6. private WeixinPayService weixinPayService;
    7. /***
    8. * 创建二维码
    9. * @return
    10. */
    11. @RequestMapping(value = "/create/native")
    12. public Result createNative(String out_trade_no, String total_fee){
    13. Map<String,String> resultMap = weixinPayService.createNative(out_trade_no,total_fee);
    14. return new Result(true, StatusCode.OK,"创建二维码预付订单成功!",resultMap);
    15. }
    16. }

    这里我们订单号通过随机数生成,金额暂时写死,后续开发我们再对接业务系统得到订单号和金额
    Postman测试
    http://localhost:18092/weixin/pay/create/native?out_trade_no=No000000001&total_fee=1
    如下效果
    第12天 微信支付 - 图3
    打开支付页面/pay.html,copy 上边如图的code_url的值 到pay.html中的 value的值,再次打开,会出现二维码,可以扫码试试
    第12天 微信支付 - 图4
    测试如下:
    第12天 微信支付 - 图5

    3 检测支付状态

    3.1 需求分析

    当用户支付成功后跳转到成功页面
    第12天 微信支付 - 图6
    当返回异常时跳转到错误页面
    第12天 微信支付 - 图7

    3.2 实现思路

    我们通过HttpClient工具类实现对远程支付接口的调用。
    接口链接:https://api.mch.weixin.qq.com/pay/orderquery
    具体参数参见“查询订单”API, 我们在前端方法中轮询调用查询订单(例如间隔3秒),当返回状态为success时,我们会在controller方法返回结果。前端代码收到结果后跳转到成功页面。
    第12天 微信支付 - 图8

    3.3 代码实现

    (1)业务层
    修改com.changgou.service.WeixinPayService,新增方法定义

    1. /***
    2. * 查询订单状态
    3. * @param out_trade_no : 客户端自定义订单编号
    4. * @return
    5. */
    6. public Map queryPayStatus(String out_trade_no);

    在com.changgou.pay.service.impl.WeixinPayServiceImpl中增加实现方法

    1. /***
    2. * 查询订单状态
    3. * @param out_trade_no : 客户端自定义订单编号
    4. * @return
    5. */
    6. @Override
    7. public Map queryPayStatus(String out_trade_no) {
    8. try {
    9. //1.封装参数
    10. Map param = new HashMap();
    11. param.put("appid",appid); //应用ID
    12. param.put("mch_id",partner); //商户号
    13. param.put("out_trade_no",out_trade_no); //商户订单编号
    14. param.put("nonce_str",WXPayUtil.generateNonceStr()); //随机字符
    15. //2、将参数转成xml字符,并携带签名
    16. String paramXml = WXPayUtil.generateSignedXml(param,partnerkey);
    17. //3、发送请求
    18. HttpClient httpClient = new HttpClient("https://api.mch.weixin.qq.com/pay/orderquery");
    19. httpClient.setHttps(true);
    20. httpClient.setXmlParam(paramXml);
    21. httpClient.post();
    22. //4、获取返回值,并将返回值转成Map
    23. String content = httpClient.getContent();
    24. return WXPayUtil.xmlToMap(content);
    25. } catch (Exception e) {
    26. e.printStackTrace();
    27. }
    28. return null;
    29. }

    (2)控制层
    com.changgou.controller.WeixinPayController新增方法,用于查询支付状态,代码如下:
    上图代码如下:

    1. /***
    2. * 查询支付状态
    3. * @param outtradeno
    4. * @return
    5. */
    6. @GetMapping(value = "/status/query")
    7. public Result queryStatus(String out_trade_no){
    8. Map<String,String> resultMap = weixinPayService.queryPayStatus(out_trade_no);
    9. return new Result(true,StatusCode.OK,"查询状态成功!",resultMap);
    10. }

    4 订单状态操作

    4.1 需求分析

    第12天 微信支付 - 图9
    我们现在系统还有个问题需要解决:支付后订单状态没有改变
    订单状态修改的流程说明

    1. 1.用户提交订单发送请求到支付微服务
    2. 2.支付微服务调用【统一下单API】接口生成支付订单返回给前端
    3. 3.前端获取到code_url之后使用qrious.js生成支付二维码
    4. 4.用户使用手机扫码支付成功
    5. 5.微信支付系统收到用户扫码成功,并通过notifyurl设置的值异步通知到支付微服务
    6. 6.支付微服务发送 MQ消息到MQ服务器
    7. 7.订单微服务监听消息 并修改订单的状态即可

    定时轮询判断订单支付状态的流程(了解)

    1. 1.在用户生成二维码未支付之前,前端不停的轮询发送请求 查询支付状态 例如3
    2. 2.定时任务每隔30秒启动一次,找出最近10分钟内创建并且未支付的订单,调用《微信支付查单接口》核实订单状态。系统记录订单查询的次数,在10次查询之后状态还是未支付成功,则停止后续查询,并调用《关单接口》关闭订单。(轮询时间间隔和次数,商户可以根据自身业务场景灵活设置)

    第12天 微信支付 - 图10
    上图流程说明如下:

    1. 1.支付微服务定义定时任务
    2. 2.通过feign调用查询订单微服务的最新10分钟的订单列表
    3. 3.判断是否已经支付 如果支付 则更新状态 删除统计信息
    4. 4.如果没有支付,则判断是否已经达到统计次数上限 如果没有 则累计统计次数
    5. 5.如果已经达到上限 说明还没有支付,则关闭交易订单,并删除订单 把库存恢复即可。

    5 支付信息回调

    5.1 接口分析

    每次实现支付之后,微信支付都会将用户支付结果返回到指定路径,而指定路径是指创建二维码的时候填写的notifyurl参数,响应的数据以及相关文档参考一下地址:[https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_7&index=8](https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_7&index=8)
    第12天 微信支付 - 图11

    5.1.1 返回参数分析

    通知参数如下:

字段名 变量名 必填 类型 示例值 描述
返回状态码 return_code String(16) SUCCESS SUCCESS
返回信息 return_msg String(128) OK OK

以下字段在return_code为SUCCESS的时候有返回

字段名 变量名 必填 类型 示例值 描述
公众账号ID appid String(32) wx8888888888888888 微信分配的公众账号ID(企业号corpid即为此appId)
业务结果 result_code String(16) SUCCESS SUCCESS/FAIL
商户订单号 out_trade_no String(32) 1212321211201407033568112322 商户系统内部订单号
微信支付订单号 transaction_id String(32) 1217752501201407033233368018 微信支付订单号

5.1.2 响应分析

回调地址接收到数据后,需要响应信息给微信服务器,告知已经收到数据,不然微信服务器会再次发送4次请求推送支付信息。

字段名 变量名 必填 类型 示例值 描述
返回状态码 return_code String(16) SUCCESS 请按示例值填写
返回信息 return_msg String(128) OK 请按示例值填写

举例如下:

  1. <xml>
  2. <return_code><![CDATA[SUCCESS]]></return_code>
  3. <return_msg><![CDATA[OK]]></return_msg>
  4. </xml>

5.2 回调接收数据实现

修改changgou-service-pay微服务的com.changgou.pay.controller.WeixinPayController,添加回调方法,代码如下:

  1. /***
  2. * 支付回调
  3. * @param request
  4. * @return
  5. */
  6. @RequestMapping(value = "/notify/url")
  7. public String notifyUrl(HttpServletRequest request){
  8. InputStream inStream;
  9. try {
  10. //读取支付回调数据
  11. inStream = request.getInputStream();
  12. ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
  13. byte[] buffer = new byte[1024];
  14. int len = 0;
  15. while ((len = inStream.read(buffer)) != -1) {
  16. outSteam.write(buffer, 0, len);
  17. }
  18. outSteam.close();
  19. inStream.close();
  20. // 将支付回调数据转换成xml字符串
  21. String result = new String(outSteam.toByteArray(), "utf-8");
  22. //将xml字符串转换成Map结构
  23. Map<String, String> map = WXPayUtil.xmlToMap(result);
  24. //响应数据设置
  25. Map respMap = new HashMap();
  26. respMap.put("return_code","SUCCESS");
  27. respMap.put("return_msg","OK");
  28. return WXPayUtil.mapToXml(respMap);
  29. } catch (Exception e) {
  30. e.printStackTrace();
  31. //记录错误日志
  32. }
  33. return null;
  34. }

5.3 测试

由于我们微信通知的地址值需要外网访问使用所以没有办法接收到,这里我们可以使用一个工具utools配置内网穿透即可
(1)下载utools
如下图已经下载好了,如果有需要可以自己在官网下载<http://u.tools/download.html>
第12天 微信支付 - 图12
(2)安装之后打开软件
第12天 微信支付 - 图13
(3)配置地址:
重新点击utools 在弹出框栏位输入地址:NAT
第12天 微信支付 - 图14
配置如下
第12天 微信支付 - 图15
再点击链接
第12天 微信支付 - 图16
第12天 微信支付 - 图17

6 MQ处理支付回调状态

6.1 业务分析

用户扫码支付成功之后,通过notifyurl通知到我们,我们需要正确的处理这些消息。那么我们支付系统只会处理支付相关的业务,不处理支付之外的业务。可以监听微信通知的消息,立即发送消息到MQ中,其他微服务监听消息 然后修改订单的状态即可。这样有以下好处:

  1. 1.业务解耦
  2. 2.异步处理提升系统吞吐量,及时反馈给微信,耗时操作可以放到别的系统进行处理

参考图如下:
第12天 微信支付 - 图18

6.2步骤实现

6.2.1 支付微服务环境配置准备

(1)集成RabbitMQ
修改支付微服务changgou-service-pay,集成RabbitMQ,添加如下依赖:

  1. <!--加入ampq-->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-amqp</artifactId>
  5. </dependency>

这里我们建议在后台手动创建队列,并绑定队列。如果使用程序创建队列,可以按照如下方式实现。
修改changgou-service-pay微服务中的application.yml,配置支付队列和交换机信息,代码如下:

  1. #位置支付交换机和队列
  2. mq:
  3. pay:
  4. exchange:
  5. order: exchange.order
  6. queue:
  7. order: queue.order
  8. routing:
  9. key: queue.order

创建队列以及交换机并让队列和交换机绑定,修改com.changgou.WeixinPayApplication,添加如下代码:

  1. @Autowired
  2. private Environment environment;
  3. //创建队列
  4. @Bean
  5. public Queue queueOrder(){
  6. return new Queue(environment.getProperty("mq.pay.queue.order"));
  7. }
  8. //创建交互机 路由模式的交换机
  9. @Bean
  10. public DirectExchange createExchange(){
  11. return new DirectExchange(environment.getProperty("mq.pay.exchange.order"));
  12. }
  13. //创建绑定
  14. @Bean
  15. public Binding createBinding(){
  16. return BindingBuilder.bind(queueOrder()).to(createExchange()).with(environment.getProperty("mq.pay.routing.key"));
  17. }

支付微服务changgou-service-pay中的application.yml配置rabbitmq的配置项:
第12天 微信支付 - 图19

6.2.2 支付微服务发送MQ消息

修改回调方法,在接到支付信息后,立即将支付信息发送给RabbitMQ,代码如下:
第12天 微信支付 - 图20
上图代码如下:

  1. @Value("${mq.pay.exchange.order}")
  2. private String exchange;
  3. @Value("${mq.pay.queue.order}")
  4. private String queue;
  5. @Value("${mq.pay.routing.key}")
  6. private String routing;
  7. @Autowired
  8. private WeixinPayService weixinPayService;
  9. @Autowired
  10. private RabbitTemplate rabbitTemplate;
  11. /***
  12. * 支付回调
  13. * @param request
  14. * @return
  15. */
  16. @RequestMapping(value = "/notify/url")
  17. public String notifyUrl(HttpServletRequest request){
  18. InputStream inStream;
  19. try {
  20. //读取支付回调数据
  21. inStream = request.getInputStream();
  22. ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
  23. byte[] buffer = new byte[1024];
  24. int len = 0;
  25. while ((len = inStream.read(buffer)) != -1) {
  26. outSteam.write(buffer, 0, len);
  27. }
  28. outSteam.close();
  29. inStream.close();
  30. // 将支付回调数据转换成xml字符串
  31. String result = new String(outSteam.toByteArray(), "utf-8");
  32. //将xml字符串转换成Map结构
  33. Map<String, String> map = WXPayUtil.xmlToMap(result);
  34. //将消息发送给RabbitMQ
  35. rabbitTemplate.convertAndSend(exchange,routing, JSON.toJSONString(map));
  36. //响应数据设置
  37. Map respMap = new HashMap();
  38. respMap.put("return_code","SUCCESS");
  39. respMap.put("return_msg","OK");
  40. return WXPayUtil.mapToXml(respMap);
  41. } catch (Exception e) {
  42. e.printStackTrace();
  43. //记录错误日志
  44. }
  45. return null;
  46. }

6.2.3 订单微服务环境配置准备

在订单微服务中,先集成RabbitMQ,再监听队列消息。changgou-service-order微服务中在pom.xml中引入如下依赖:

  1. <!--加入ampq-->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-amqp</artifactId>
  5. </dependency>

在application.yml中配置rabbitmq配置,代码如下:
第12天 微信支付 - 图21
在application.yml中配置队列名字,代码如下:

  1. mq:
  2. pay:
  3. exchange:
  4. order: exchange.order
  5. queue:
  6. order: queue.order
  7. routing:
  8. key: queue.order

在启动类中添加如下代码:

  1. @Autowired
  2. private Environment environment;
  3. //创建队列 交给spring容器
  4. @Bean
  5. public Queue queueOrder(){
  6. return new Queue(environment.getProperty("mq.pay.queue.order"));
  7. }
  8. //创建交换机 交给spring
  9. @Bean
  10. public DirectExchange createExchange(){
  11. return new DirectExchange(environment.getProperty("mq.pay.exchange.order"));
  12. }
  13. //创建绑定 交给spring容器
  14. @Bean
  15. public Binding createBinding(){
  16. return BindingBuilder.bind(queueOrder()).to(createExchange()).with(environment.getProperty("mq.pay.routing.key"));
  17. }

6.2.4 订单微服务监听消息修改状态

在订单微服务于中创建com.changgou.order.consumer.OrderPayMessageListener,并在该类中consumeMessage方法,用于监听消息,并根据支付状态处理订单,代码如下:

  1. @Component
  2. @RabbitListener(queues = "queue.order")
  3. public class PayOrderUpdateListener {
  4. @Autowired
  5. private OrderMapper orderMapper;
  6. @RabbitHandler//方法用处理监听到queue.order队列的消息 可靠性消息
  7. public void handler(String msg) {
  8. System.out.println(msg);
  9. //1.获取消息本身
  10. //2.将其转换成MAP对象
  11. Map<String, String> map = JSON.parseObject(msg, Map.class);
  12. if (map != null) {
  13. String out_trade_no = map.get("out_trade_no");
  14. //3.判断是否支付成功 如果成功 则 更新状态
  15. if ("SUCCESS".equals(map.get("return_code")) && "SUCCESS".equals(map.get("result_code"))) {
  16. //1.获取订单号 更新数据到数据库
  17. Order order = orderMapper.selectByPrimaryKey(out_trade_no);
  18. order.setPayStatus("1");//设置支付柱状图为1
  19. String time_end = map.get("time_end");
  20. SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
  21. try {
  22. Date parse = simpleDateFormat.parse(time_end);
  23. order.setPayTime(parse);//付款时间
  24. order.setUpdateTime(parse);
  25. order.setTransactionId(map.get("transaction_id"));
  26. } catch (ParseException e) {
  27. e.printStackTrace();
  28. }
  29. orderMapper.updateByPrimaryKeySelective(order);
  30. } else {
  31. Order order = orderMapper.selectByPrimaryKey(out_trade_no);
  32. order.setIsDelete("1");//删除
  33. //4.如果失败 删除相关信息 为了简单我们 删除掉订单
  34. orderMapper.updateByPrimaryKeySelective(order);
  35. }
  36. }
  37. }
  38. }