当前位置: 首页 > news >正文

徐汇网站制作设计aso安卓优化

徐汇网站制作设计,aso安卓优化,柳州专业做网站,成都广告制作安装公司文章目录一、支付微服务1、微信支付 API2、HttpClient 工具类3、支付微服务搭建二、微信支付二维码生成三、检测支付状态四、内网穿透五、支付结果通知1、支付结果回调通知2、Rabbit MQ 配置(1)发送支付状态(2)监听六、修改订单状…

文章目录

      • 一、支付微服务
        • 1、微信支付 API
        • 2、HttpClient 工具类
        • 3、支付微服务搭建
      • 二、微信支付二维码生成
      • 三、检测支付状态
      • 四、内网穿透
      • 五、支付结果通知
        • 1、支付结果回调通知
        • 2、Rabbit MQ 配置
          • (1)发送支付状态
          • (2)监听
      • 六、修改订单状态
      • 七、超时订单处理
      • 八、关闭订单与回滚库存
      • 九、总结

代码链接: https://github.com/betterGa/ChangGou

一、支付微服务

1、微信支付 API

    微信支付提供了 SDK 和 Demo(然而我并没有找到 sdk,可能微信团队已经把它上传到 Maven 中了):
1537902584152
使用微信支付SDK,在 common 工程的 pom.xml 中中引入依赖

<!--微信支付-->
<dependency><groupId>com.github.wxpay</groupId><artifactId>wxpay-sdk</artifactId><version>0.0.3</version>
</dependency>

我们主要会用到微信支付 SDK 的以下功能:

获取随机字符串

WXPayUtil.generateNonceStr()

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

 WXPayUtil.generateSignedXml(param, partnerkey)

XML 字符串转换为 MAP(当然,Map 也可以转化成 XML)

WXPayUtil.xmlToMap(result)

在 changgou-common工程下引入依赖:

<!--微信支付-->
<dependency><groupId>com.github.wxpay</groupId><artifactId>wxpay-sdk</artifactId><version>0.0.3</version>
</dependency>

进行测试:

public class WeixinPayTest {@Testpublic void test() throws Exception {// 生成随机字符System.out.println("随机字符串" + WXPayUtil.generateNonceStr());// 将 Map 转化成 XMLMap<String, String> dataMap = new HashMap<>();dataMap.put("id", "No.1");dataMap.put("title", "畅购商城");dataMap.put("money", "520");String xml = WXPayUtil.mapToXml(dataMap);System.out.println(xml);// (1)生成签名System.out.println("带签名的字符串" + WXPayUtil.generateSignedXml(dataMap, "secret"));// 将 XML 转化成 MapSystem.out.println(WXPayUtil.xmlToMap(xml));}
}

运行结果:
在这里插入图片描述
     注意到,(1)处,WXPayUtil.generateSignedXml 方法,传 Map 和 密钥 作为参数,可以生成带 签名 的字符串,签名是这么生成的。

2、HttpClient 工具类

     HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。HttpClient 已经应用在很多的项目中,比如 Apache Jakarta 上很著名的另外两个开源项目 Cactus 和 HTMLUnit 都使用了 HttpClient。
    HttpClient 通俗讲就是模拟了浏览器的行为,如果我们需要在后端向某一地址提交数据获取结果,就可以使用 HttpClient.

使用 HttpClient 需要先导入依赖:

   <!--httpclient支持--><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId></dependency>

     关于HttpClient(原生)具体的使用不属于我们本章的学习内容,为了简化 HttpClient 的使用,提供了工具类 HttpClient(对原生 HttpClient 进行了封装)。

HttpClient工具类代码:

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() {// 信任所有@Overridepublic 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;}
}

进行测试:

public class HttpClientClass {/*** 测试 HttpClient 工具类的使用*/@Testpublic void test() throws IOException {// 发送 Http 请求String url = "https://api.mch.weixin.qq.com/pay/orderquery";HttpClient httpClient = new HttpClient(url);// 发送指定参数String xml = "<xml><name>用户</name></xml>";httpClient.setXmlParam(xml);// 使用 Https 协议httpClient.setHttps(true);// 发送请求httpClient.post();// 获取响应数据String result = httpClient.getContent();System.out.println(result);}
}

运行结果:
在这里插入图片描述

3、支付微服务搭建

     需要支付微服务与微信支付服务器进行对接。
     创建 changgou-service-pay 工程。

创建application.yml,配置文件如下:

server:port: 18092
spring:application:name: paymain:allow-bean-definition-overriding: true
eureka:client:service-url:defaultZone: http://127.0.0.1:7001/eurekainstance:prefer-ip-address: true
feign:hystrix:enabled: true
#hystrix 配置
hystrix:command:default:execution:timeout:#如果enabled设置为false,则请求超时交给ribbon控制enabled: trueisolation:strategy: SEMAPHORE#微信支付信息配置
weixin:appid: wx8397f8696b538317partner: 1473426802partnerkey: T6m9iK73b0kn9g5v426MKfHQH7X8rKwbnotifyurl: http://www.itcast.cn

参数说明:

  • appid:微信公众账号或开放平台 APP 的唯一标识
  • partner:财付通平台的商户账号
  • partnerkey:财付通平台的商户密钥
  • notifyurl::回调地址

提供启动类:

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableEurekaClient
public class PayApplication {public static void main(String[] args) {SpringApplication.run(PayApplication.class, args);}
}

二、微信支付二维码生成

     在支付页面上生成支付二维码,并显示订单号和金额,用户拿出手机,打开微信扫描页面上的二维码,然后在微信中完成支付。
在这里插入图片描述
实现思路:
     通过 HttpClient 工具类实现对远程支付接口的调用。
接口链接:https://api.mch.weixin.qq.com/pay/unifiedorder

     具体参数参见 “统一下单” API, 构建参数发送给统一下单的 url ,返回的信息中有支付 url,根据 url 生成二维码,显示的订单号和金额也在返回的信息中。
    
代码实现:

  • 业务层
    提供接口:
public interface WeixinPayService {Map createNative(Map<String,String> parameterMap) throws Exception;
}

实现:

@Service
public class WeiXinPayServiceImpl implements WeixinPayService {// 应用 ID@Value("${weixin.appid}")private String appid;// 商户 ID@Value("${weixin.partner}")private String partner;// 密钥@Value("${weixin.partnerkey}")private String partnerkey;// 支付回调地址@Value("${weixin.notifyurl}")private String notifyurl;/*** 创建二维码** @param parameterMap* @return*/@Overridepublic Map createNative(Map<String, String> parameterMap) throws Exception {/*** 封装参数*/Map<String, String> paramMap = new HashMap<>();// 公众账号 IDparamMap.put("appid", appid);// 商户号paramMap.put("mch_id", partner.trim());// 随机字符串paramMap.put("nonce_str", WXPayUtil.generateNonceStr());// 商品描述paramMap.put("body", "畅购商城");// 商品订单号paramMap.put("out_trade_no", parameterMap.get("outtradeno"));// 标价金额paramMap.put("total_fee", parameterMap.get("totalfee"));// 终端 IPparamMap.put("spbill_create_ip", "127.0.0.1");// 通知地址paramMap.put("notify_url", notifyurl);// 交易类型paramMap.put("trade_type", "NATIVE");// 传入密钥,生成的 xml 中就会带有签名 sign 信息String xmlParameters = WXPayUtil.generateSignedXml(paramMap, partnerkey);System.out.println("xml:"+xmlParameters);/*** URL*/String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";/*** 提交方式*/HttpClient httpClient = new HttpClient(url);httpClient.setHttps(true);/*** 提交参数*/httpClient.setXmlParam(xmlParameters);/*** 执行请求*/httpClient.post();// 返回数据String content = httpClient.getContent();Map<String,String> resultMap=WXPayUtil.xmlToMap(content);return resultMap;}
}
  • 控制层
@RestController
@RequestMapping(value = "/weixin/pay")
@CrossOrigin
public class WeiXinPayController {@Autowiredprivate WeixinPayService weixinPayService;@GetMapping(value = "/create/native")Result createNative(@RequestParam Map<String, String> parameterMap) throws Exception {Map resultMap = weixinPayService.createNative(parameterMap);return new Result(true, StatusCode.OK,"创建二维码预付订单成功!",resultMap);}
}

测试如下:
在这里插入图片描述

打开支付页面 /pay.html,修改 value 路径,改为响应的 code_url 的值:
1558476420961
这时访问 pay.html 页面,会出现付款码,金额为 0.1 元。

三、检测支付状态

     当用户支付成功后跳转到成功页面:

在这里插入图片描述

当返回异常时跳转到错误页面:

在这里插入图片描述

     本身服务器会给商户后台系统发状态信息,但是可能有差错,导致商户后台系统没收到,所以需要主动向服务器查询状态信息。

实现思路:通过 HttpClient 工具类实现对远程接口的调用。
接口链接:https://api.mch.weixin.qq.com/pay/orderquery

具体参数参见 “查询订单” API:
在这里插入图片描述

代码实现:

  • 业务层
         在 com.changgou.service.WeixinPayService 提供方法:
/**** 查询订单状态* @param out_trade_no : 客户端自定义订单编号* @return*/
public Map queryPayStatus(String out_trade_no);

实现:

/**** 查询订单状态* @param out_trade_no : 客户端自定义订单编号* @return*/
@Override
public Map queryPayStatus(String out_trade_no) {/*** 查询订单状态* @param out_trade_no* @return*/@Overridepublic Map queryPayStatus(String out_trade_no) {try {//1.封装参数Map param = new HashMap();param.put("appid",appid);                            //应用IDparam.put("mch_id",partner);                         //商户号param.put("out_trade_no",out_trade_no);              //商户订单编号param.put("nonce_str",WXPayUtil.generateNonceStr()); //随机字符//2、将参数转成 xml 字符,并携带签名String paramXml = WXPayUtil.generateSignedXml(param,partnerkey);//3、发送请求HttpClient httpClient = new HttpClient("https://api.mch.weixin.qq.com/pay/orderquery");httpClient.setHttps(true);httpClient.setXmlParam(paramXml);httpClient.post();//4、获取返回值,并将返回值转成 MapString content = httpClient.getContent();return WXPayUtil.xmlToMap(content);} catch (Exception e) {e.printStackTrace();}return null;}
}
  • 控制层
/**** 查询支付状态* @param outtradeno* @return*/@GetMapping(value = "/status/query")public Result queryStatus(String outtradeno) {Map<String, String> resultMap = weixinPayService.queryPayStatus(outtradeno);return new Result(true, StatusCode.OK, "查询状态成功!", resultMap);}

运行结果:
在这里插入图片描述

四、内网穿透

     现在系统还有个问题需要解决:微信支付服务器需要访问本地服务器的,让外网访问到本地服务器,就需要用到内网穿透技术 NAT 。使用动态域名解析工具 花生壳 。
新建映射:
在这里插入图片描述

    其中,内网主机即本地 IP,外网域名用的是花生壳赠送的,在 “域名列表” 中可以看到。
在这里插入图片描述
    可以看到,提供了一个访问地址。

要在诊断无误的情况下使用:
在这里插入图片描述

测试访问地址:
在这里插入图片描述
    

五、支付结果通知

1、支付结果回调通知

提供控制层:

  @RequestMapping(value = "/notify/url")public String notifyUrl(HttpServletRequest request) throws Exception {// 获取网络输入流ServletInputStream inputStream = request.getInputStream();ByteArrayOutputStream baos = new ByteArrayOutputStream();byte[] buffer = new byte[1024];int len = 0;while ((len = inputStream.read(buffer)) != -1) {baos.write(buffer, 0, len);}// 微信支付结果的字节数据byte[] bytes = baos.toByteArray();String xmlResult = new String(bytes, "utf-8");System.out.println("微信支付结果的 xml" + xmlResult);Map<String, String> resultMap = WXPayUtil.xmlToMap(xmlResult);System.out.println("微信支付结果的 Map" + resultMap);String result = "<xml>\n" +"  <return_code><![CDATA[SUCCESS]]></return_code>\n" +"  <return_msg><![CDATA[OK]]></return_msg>\n" +"</xml>";return result;}

需要在 application.yml 中配置访问地址,将该方法对应的路径作为配置文件中 weixin:notifyurl 的属性值。:
在这里插入图片描述
进行测试:
首先需要创建订单:
在这里插入图片描述
     把 “code_url” 放到 pay.html 中,生成付款码,用微信扫描后付款,这时,会在控制台看到输出:
在这里插入图片描述
    注意:方法是 @RequestMapping ,如果误写成 @GetMapping ,控制台输出:
在这里插入图片描述
它说不支持 @PostMapping,那我就改成 @PostMapping:
在这里插入图片描述
     可以看到,和使用 @RequestMapping 的效果是一样的,说明调用这个 notifyurl 的地方,是使用 Post 方法。

     至此,在 pay 项目中就可以获取到支付结果,但是 order 项目并没有获取到结果。需要让 order 项目监听 MQ,当监听到支付成功后,需要把订单状态改为 支付成功。

2、Rabbit MQ 配置

需要先在 pay 工程中导入依赖:

	<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency>
(1)发送支付状态

    
在 application.yml 中进行配置:

mq:pay:exchange:order: exchange.orderqueue:order: queue.orderrouting:key: queue.order

在这里插入图片描述

    
在这里插入图片描述
     需要清空 Exchanges 和 Queues。
     本项目中使用代码生成交换机和队列(实际企业开发应该是去 MQ 中生成的),在 pay 工程中 提供一个配置类:

@Configuration
public class MQConfig {// 读取配置文件中的内容对象@Autowiredprivate Environment environment;// 创建队列@Beanpublic Queue orderQueue() {return new Queue(environment.getProperty("mq.pay.queue.order"));   }// 创建交换机@Beanpublic Exchange orderExchange() {// 持久化,不自动删除return new DirectExchange(environment.getProperty("mq.pay.exchange.order"), true, false);}// 绑定public Binding binding(Queue queue, Exchange exchange) {return BindingBuilder.bind(queue).to(exchange).with(environment.getProperty("mq.pay.routing.key")).noargs();}
}

在 WeiXinPayController 的 notifyUrl 方法中,加入 把支付结果发送给 MQ 的逻辑:
在这里插入图片描述
还需要在 order 工程中,同样需要配置 RabbitMQ 参数,导入 spring-boot-starter-amqp 依赖。

(2)监听

提供监听类:

@Component
@RabbitListener(queues = "${mq.pay.queue.order}")
public class OrderMessageListener {/*** 支付结果监听*/@RabbitHandlerpublic void getMessage(String message){// 支付结果Map<String,String> resultMap = JSON.parseObject(message, Map.class);// 输出监听到的消息System.out.println(resultMap);// 通信标识String returnCode=resultMap.get("return_code");if(returnCode.equals("SUCCESS")){// 业务结果String resultCode = resultMap.get("result_code");// 订单号String outTradeNo = resultMap.get("out_trade_no");// 支付成功if(resultCode.equals("SUCCESS")){}else {// 如果支付失败,需要关闭订单,回滚库存}}}
}

     注意:需要启动 pay 工程,生成付款码,然后用户进行支付,创建队列 queue.order,启动 order 工程,监听队列。不可以同时启动两个工程,否则 生产者模块所在的 pay 工程可以正常运行,但是消费者模块所在的 order 会报错误:

org.springframework.amqp.rabbit.listener.BlockingQueueConsumer$DeclarationException: Failed to declare queue(s):[topic.man] at org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.attemptPassiveDeclarations(BlockingQueueConsumer.java:700) [spring-rabbit-2.2.7.RELEASE.jar:2.2.7.RELEASE]at 			   	org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.passiveDeclarations(Blocki	ngQueueConsumer.java:584) [spring-rabbit-2.2.7.RELEASE.jar:2.2.7.RELEASE] 

     因为此时还未付款,就不会在 rabbitmq 服务器里面创建还不存在的交换机和队列。仅限于第一次启动的时候,以后 rabbitmq 里面以及有对应的交换机和队列存在了,就不用这样做了。
    
付款后,可以看到 RabbitMQ 中新建队列和交换机成功了:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

六、修改订单状态

     接下来,需要根据支付的结果修改订单信息:
在这里插入图片描述
    
在 OrderService 中提供方法:

    /*** 修改订单状态** @param outradeno* @param paytime* @param transcationid*/void updateStatus(String outradeno, String paytime, String transcationid) throws ParseException;/*** 删除【逻辑删除,其实是修改订单状态】订单信息,回滚库存** @param outradeno*/void deleteOrder(String outradeno);

实现:

    /*** 修改订单状态** @param outradeno* @param paytime* @param transcationid*/@Overridepublic void updateStatus(String outradeno, String paytime, String transcationid) throws ParseException {// 查询订单Order order = orderMapper.selectByPrimaryKey(outradeno);// 时间转换SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmss");// 获取支付时间Date payTime = simpleDateFormat.parse(paytime);// 修改订单信息order.setPayTime(payTime);order.setPayStatus("1");order.setTransactionId(transcationid);orderMapper.updateByPrimaryKey(order);}/*** 删除订单* @param outradeno*/@Overridepublic void deleteOrder(String outradeno) {// 查询订单Order order = orderMapper.selectByPrimaryKey(outradeno);// 支付失败order.setOrderStatus("2");orderMapper.updateByPrimaryKey(order);// 回滚库存,需要调用 goods 微服务,未实现// 微信支付服务器关闭订单,未实现}

如果用户订单支付失败了,或者支付超时了,我们需要删除用户订单,删除订单的同时需要取消订单、回滚库存:
在这里插入图片描述

七、超时订单处理

    现在还有个问题,如果用户下单 30 分钟后还没有支付,需要把订单取消。(超过 5 分钟取消订单,是可以实现的)
在这里插入图片描述
     那么怎么让系统知道 30 分钟后用户是否支付成功了呢?可以轮询,但是轮询非常浪费资源,而且轮询的间隔不好控制;可以用延时队列。
     Rabbit MQ 本身不支持延时队列,不过可以自己实现,有两种方式:
(1)利用 2 个特性:Time To Live( TTL)、Dead Letter Exchange(DLX)、 [A 队列过期-> 转发给 B 队列]
(2)利用 RabbitMQ 中的插件 x-delay-message
     采用第一种方式来实现延时队列,实际上是用队列的超时特性:
在这里插入图片描述
     监听 Queue2 即可。
代码实现:

@Configuration
public class QueueConfig {// 创建 queue1@Beanpublic Queue orderDelayQueue(){return QueueBuilder.durable("orderDelayQueue")// queue1 消息过期,进入到死信【没被读取的消息】队列,需要绑定交换机.withArgument("x-dead-letter-exchange","orderListenerExchange")// queue1 消息过期,会路由到 queue2.withArgument("x-dead-letter-routing-key","orderListenerQueue").build();}// 创建 queue2@Beanpublic Queue orderListenerQueue(){// 持久化return new Queue("orderListenerQueue",true);}@Beanpublic Exchange orderListenerExchange(){return new DirectExchange("orderListenerExchange");}// 绑定@Beanpublic Binding binding(Queue orderListenerQueue,Exchange orderListenerExchange){return BindingBuilder.bind(orderListenerQueue).to(orderListenerExchange).with("orderListenerQueue").noargs();}
}

下单时发送消息:
在这里插入图片描述
提供监听:

@Component
@RabbitListener(queues = "orderListenerQueue")
public class DelayMessageListener {@RabbitHandlerpublic void delayMessage(String message) {System.out.println("监听到的消息" + message);SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");System.out.println("监听到消息时间" + simpleDateFormat.format(new Date()));}
}

这时进行测试,启动 order 工程,可以看到 RabbitMQ 中生成了队列和交换机:
在这里插入图片描述
在这里插入图片描述
接下来进行测试。
先下单:
在这里插入图片描述
可以看到,在控制台输出:
在这里插入图片描述

八、关闭订单与回滚库存

    在 (五)支付结果通知,队列监听方法中,当监听到消息的 result_code 不为 SUCCESS 时,说明支付失败,需要关闭订单、回滚库存。回滚库存的逻辑可以参考上一篇文章中,下单-库存变更 的逻辑,在 Redis 中,查询用户对应的订单信息,然后根据 订单 order 查询出订单明细 orderItem,就能得到订单中商品的 num 数量,最后在 sku 商品表中需要把数量加回来:
在这里插入图片描述
需要在 skuController 中提供库存递增的方法:

 @GetMapping(value = "/asc")public Result ascCount(@RequestParam Map<String,String> ascMap){skuService.ascCount(ascMap);return new Result(true,StatusCode.OK,"库存递增成功");}

在 SkuService 接口中提供方法:

   void ascCount(Map<String,String> ascMap);

实现:

 @Overridepublic void ascCount(Map<String, String> ascMap) {for (Map.Entry<String, String> entry : ascMap.entrySet()) {// 商品 IDLong id = Long.parseLong(entry.getKey());// 数量Integer num = Integer.parseInt(entry.getValue());/*** 使用行级锁防止超卖,通过数据库的事务特性保证数据原子性*/int row = skuMapper.ascCount(id, num);}}

在 SkuMapper 中使用 SQL 语句行级锁:

@Update("update tb_sku set num=num+#{num} where id=#{id}")int ascCount(@Param(value = "id") Long id,  @Param(value = "num")Integer num);

提供 feign 调用:
在这里插入图片描述
    关闭订单用微信支付开发文档中提供的 ”关闭订单“ API。
    之前都是使用 HttpClient 工具,这次试着直接用 sdk,可以看到,在 WxPay 中,有相应方法:
在这里插入图片描述
    可以看到,需要传 reqData ,即 向 wxpay post 的数据(和接口文档中是对应的:
     必须有 appid、mch_id、out_trade_no、nonce_str、sign 参数,WeiXinPayServiceImpl 类有从配置文件中读取参数作为属性,所以可以在 WeiXinPayServiceImpl 类中提供方法,把这些参数传进去)。

/*** 构建 WXPay 对象*/public WXPay wxPay(){return new WXPay(new WXPayConfig() {@Overridepublic String getAppID() {return getAppid();}@Overridepublic String getMchID() {return getMchID();}@Overridepublic String getKey() {return getPartnerkey();}@Overridepublic InputStream getCertStream() {return null;}@Overridepublic int getHttpConnectTimeoutMs() {return 8000;}@Overridepublic int getHttpReadTimeoutMs() {return 10000;}});}@Overridepublic Map cancelOrder(String outTradeNo) throws Exception {// 封装参数Map<String,String> reqDate=new HashMap<>();reqDate.put("out_trade_no", outTradeNo);return wxPay().closeOrder(reqDate);}

     可以看到,参数齐活了(不需要手动传 nonce_str、sign 是因为在 closeOrder 中,会调用 fillRequestData 方法,使用了 WXPayUtil.generateNonceStr() 作为 “nonce_str” 的值、 使用了 WXPayUtil.generateSignature(…)作为 “sign” 的值)。

需要在 WeiXinPayController 中再提供一个 取消订单的方法以供调用:

@RequestMapping(value = "/cancel/order")public Result cancelOrder(@RequestParam String outTradeNo) throws Exception {Map resultMap = weixinPayService.cancelOrder(outTradeNo);return new Result(true, StatusCode.OK, "取消订单成功", resultMap);}

先测试取消订单的方法:
在这里插入图片描述
返回这样的结果是因为订单已支付:
在这里插入图片描述
根据这个错误码,我们知道,关闭订单也是有可能出现错误信息的,不是全部的订单都可以成关闭,所以修改 WeiXinPayController 层的方法:
在这里插入图片描述
想让监听支付结果的 getMessage 方法里调用取消订单的方法,可以使用 Feign 调用:

@FeignClient("weixinpay")
@RequestMapping(value = "/weixin/pay")
public interface WeiXinPayFeign {/*** 取消订单* @param outTradeNo* @return*/@RequestMapping(value = "/cancel/order")public Result cancelOrder(@RequestParam String outTradeNo);
}

在这里插入图片描述
     同样地,在 (七) 超时处理中,需要把逻辑改为,监听队列的 DelayMessageListener 的 delayMessage 方法,在监听到消息时,需要判断订单是否支付成功,如果没有支付成功,需要关闭订单、回滚库存。

进行测试,然后发现 order 和 pay 工程,循环依赖了😓😓😓。
     问题是这样的,order 工程中,监听订单的 DelayMessageListener 类用到了 WeiXinPayFeign :
在这里插入图片描述
     需要在 order 工程中去掉 changgou-service-pay 的依赖,换成 changgou-service-weixinpay-api 依赖,并在启动类上声明。(所以说,如果没有导入 feign 所在工程的依赖,而且启动类上也没声明 feign 所在包的话,导入 feign 调用的路径对应的代码的工程依赖就行啦???)
     这样 order 工程就没什么问题了。
    
     现在不循环依赖了,试着启动 order 工程,是没有问题的,再来启动 pay 工程,报了个错:
在这里插入图片描述
主要是在 pay 工程里,用了 order 的服务层:
在这里插入图片描述
    就是这个关闭订单的方法里,用到了逻辑删除订单的方法,会设置订单状态为支付失败,并回滚库存。
    这里,pay 的 controller 层里调用了 order 的服务层,又没有专门配置,所以报错了,解决过程见 https://blog.csdn.net/weixin_41750142/article/details/116277095。
    试着启动 pay 工程,报错:
在这里插入图片描述
    它说… … order 工程里的 CartServiceImpl 里 用到了 SkuFeign,但是找不到这样的 feign。奇了怪了… … order 工程运行起来都没有报错,为什么在 pay 工程里,并没有用到 CartService,反而报错了呢?(破案了,因为在启动类上写了 @ComponentScan(basePackages = {"com.changgou.order.service"}) ,这样就会扫描到这个包)
    还有 pay 的 controller 层里调用了 order 的服务层,毕竟也是不合理的,如果业务交叉了,可以考虑调用 feign,但是现在又没有现成的控制层逻辑可以用来进行逻辑删除,因为 orderService.deleteOrder 方法,是我直接写在服务层的… …本来就没打算让控制层调 😰。
    整个过程是这样的。在 order 工程中,一旦监听队列变化的方法,支付结果为支付失败时,就会 /cancel/order 的 feign,这个 feign 对应的是 pay 工程里的 cancelOrder 方法(为什么要放在 pay 工程里呢?是因为调用了微信支付提供的 “关闭订单” API ,在关闭订单后,才改变我们数据库里的订单状态,和回滚库存的),然后这个 pay 工程中的 cancelOrder 方法,改变订单状态和回滚库存,用的是 order 的服务层了。绕来绕去,order 用 feign 调 pay,然后 pay 的控制层 调 order 的服务层,结果又是 order 的另一个服务层报错了 🥶 。
    所以想要改 bug 很简单,不要在 pay 的控制层 调 order 的服务层!! 监听到队列的支付结果是支付失败时,调 /cancel/order 的 feign,但是 cancelOrder 方法里不要 改变订单状态和回滚库存,而是继续在监听队列的方法里,改变订单状态和回滚库存,这样就是 order 工程里调 order 的代码了。
原先:
在这里插入图片描述

@RequestMapping(value = "/cancel/order")public Result cancelOrder(@RequestParam String outTradeNo) throws Exception {Map resultMap = weixinPayService.cancelOrder(outTradeNo);if (resultMap.get("result_code").equals("FAIL")) {// 取消订单失败return new Result(true, StatusCode.ERROR, "取消订单失败", resultMap);} else {// 回滚库存orderService.deleteOrder(outTradeNo);return new Result(true, StatusCode.OK, "取消订单成功", resultMap);}}

这个方法是测试过的,没有问题。
修改为:
在这里插入图片描述
这样,pay 的 控制层不再调 order 的服务层了。
    
修改监听队列的方法:
在这里插入图片描述
     这样 pay 工程里也就不需要 order 依赖了,否则还牵扯认证的问题。

补充:发现原先的逻辑有误,原先的代码:

@Component
@RabbitListener(queues = "${mq.pay.queue.order}")
public class OrderMessageListener {@AutowiredOrderService orderService;@AutowiredWeiXinPayFeign weiXinPayFeign;/*** 支付结果监听*/@RabbitHandlerpublic void getMessage(String message) throws Exception {// 支付结果Map<String, String> resultMap = JSON.parseObject(message, Map.class);// 输出监听到的消息System.out.println(resultMap);// 通信标识String returnCode = resultMap.get("return_code");if (returnCode.equals("SUCCESS")) {// 业务结果String resultCode = resultMap.get("result_code");// 订单号String outTradeNo = resultMap.get("out_trade_no");// 支付成功if (resultCode.equals("SUCCESS")) {// 更改订单信息orderService.updateStatus(outTradeNo, resultMap.get("time_end"), resultMap.get("transaction_id"));} else {// 如果支付失败,需要关闭支付,取消订单,回滚库存weiXinPayFeign.cancelOrder(outTradeNo);}}}
}

    这还分支付成功和支付失败的情况呢是🥶🥶🥶,实际上,只有支付成功的时候才会进行结果通知回调的,支付失败的话根本就不会到这个方法里来,所以,需要修改为:

 @RabbitHandlerpublic void getMessage(String message) throws Exception {// 支付结果Map<String, String> resultMap = JSON.parseObject(message, Map.class);// 输出监听到的消息System.out.println(resultMap);// 订单号String outTradeNo = resultMap.get("out_trade_no");// 更改订单信息orderService.updateStatus(outTradeNo, resultMap.get("time_end"), resultMap.get("transaction_id"));}

再有,原先的代码认为当 returnCode、resultCode 都为 SUCCESS 时,表示支付成功,需要修改支付状态为“已支付”,否则表示支付失败。然而,未支付的时候,returnCode、resultCode 也是为 SUCCESS 的: (returnCode、resultCode 只是说明可以正常返回响应结果,但不是说明支付成功)
在这里插入图片描述
在这里插入图片描述
所以如果要分支付成功和支付失败的情况的话,应该以交易状态作为判断依据:

       // 交易状态String tradeState=resultMap.get("trade_state");// 订单号String outTradeNo = resultMap.get("out_trade_no");if (tradeState.equals("SUCCESS")) {// 支付成功// 更改订单信息orderService.updateStatus(outTradeNo, resultMap.get("time_end"), resultMap.get("transaction_id"));} else {// 如果支付失败,需要关闭支付,取消订单,回滚库存weiXinPayFeign.cancelOrder(outTradeNo);}

九、总结

(1)导入微信支付的依赖,就可以使用 wxPayUtil 类 的一些方法,比如 获取随机字符串、Map 和 XML 的互相转换、通过密钥生成签名。
    
(2)使用封装了 HttpClient 的工具类访问微信支付服务器(其实直接用 WeiXinPay 类里的方法也行🤗)。
    
(3)调用 “统一下单" API,提供 支付订单号 out_trade_no 和 支付总金额 total_fee 参数,会得到用于支付扫描的二维码链接。
    
(4)调用 ”查询订单“ API,通过商品订单号进行查询。因为可能存在 像网络原因,导致我们本地服务器没有及时拿到,所以需要手动查询。
    
(5)至此,都是本地服务访问微信支付服务器,接下来,需要让微信支付服务器【外网】访问本地,使用内网穿透技术 NAT。提供外网域名、内网主机(IP地址)、内网端口,即可生成一个访问地址,通过这个访问地址,就可以让外网访问本地的服务了。
    
(6)在 pay 工程中提供获取支付结果的方法,同时,还应该让 order 工程也获取到,思路是使用消息队列,监听消息队列,当监听队列有支付成功的消息时,把订单状态改为支付成功。
    创建 名为 queue.order 队列,和 名为 exchange.order 的交换机,并进行绑定。
    交换机是生产者和队列之间的抽象,使得生产者与队列无直接联系,用于指定消息按什么规则,路由到哪个队列。生产者通过路由键 routingkey 将交换机和队列绑定起来。
    
    获取支付结果,调用 “支付结果通知” API,需要先将对应的路径(内网穿透地址)作为配置文件中 weixin:notifyurl 的属性值。这样,用户扫描付款码支付成功后,会调用到这个方法(这·就是回调?)并把支付结果发送给 queue.order 队列。
    OrderMessageListener 类实现对 queue.order 队列 的监听,通过消息的 “result_code” 参数判断是否支付成功。如果支付成功,需要修改订单状态,逻辑是 先通过商品订单号 查询到订单信息,设置订单支付时间为当前时间,设置支付状态为 “1” 已支付,设置事务 ID 为”支付结果通知“里的事务 ID,并把订单对应的记录更新到数据库 order 表中;如果支付失败,需要关闭订单,调用 ”关闭订单“ API,如果返回的结果中,result_code 为 SUCCESS,说明关闭订单成功,还需要回滚库存(因为之前下单的时候减少了库存),否则,说明可能遇到了 订单已支付、订单已关闭、系统错误等错误,只是将错误码放在响应结果中返回,并不会回滚库存。
    
(7)如果用户下单 30 分钟后还没有支付,也需要把订单取消。使用 Rabbit MQ 的 Time To Live、Dead Letter Exchange 属性,队列 orderDelayQueue 一过期,就把消息转发给队列 orderListenerQueue。在下单的 addOrder 方法中,添加逻辑:将订单消息发送给 orderDelayQueue 队列,并设置过期时间。DelayMessageListener 类实现对 orderListenerQueue 队列的监听。
    

http://www.lbrq.cn/news/2471923.html

相关文章:

  • 如何做专题网站seo公司推荐推广平台
  • 网站建设与管理技术发展白度指数
  • 网站域名.xin杭州seo首页优化软件
  • 团购营销型网站制作网站交换链接的常见形式
  • 北京建设局网站首页上海百度推广排名
  • 网站备份 ftp百度网址大全官网旧版
  • 全国住房与城乡建设部网站株洲seo优化
  • 手机怎么安装网站程序凤山网站seo
  • 哪网站建设技术培训
  • 学校网站制作金戈枸橼酸西地那非片
  • 汕头网页网站制作全网推广平台
  • 权威发布型舆情回应以事实性seo网站推广方式
  • 怎么学习动态网站开发网络舆情监测系统
  • 网站名称怎么变更石家庄seo扣费
  • 网站选项怎么做微信社群营销怎么做
  • 广东手机网站建设如何给公司做网络推广
  • GMC中网站建设对订单有影响吗搜索引擎优化结果
  • 专业网站优化报价seo技术教程
  • 网站备案要多少钱如何免费引流推广
  • h5网站如何建设域名注册商
  • 网站是通过超链接在线一键生成网页
  • 网站建设管理员阿里云盘资源搜索引擎
  • 网站服务器操作系统seo诊断方案
  • 厦门快速建网站网络推广教程
  • wordpress鼠标轨迹成都网站seo技巧
  • 网站建设与网页制作基础入门教程广东seo点击排名软件哪家好
  • 玉溪网站开发软文写手
  • 建设企业网站报价百度流量推广项目
  • 电影网站做淘客广告公司名称
  • 二维码生成器在线制作二维码sem优化和seo的区别
  • 栈----3.字符串解码
  • 秋招Day20 - 微服务 - 概念
  • mysql查找数据库表中某几个连续的编号中中断的编号
  • 去中心化时代的通信革命:briefing与cpolar技术融合带来的安全范式革新
  • 机器学习(一)KNN,K近邻算法(K-Nearest Neighbors)
  • 论文复现-windows电脑在pycharm中运行.sh文件