微服务商城系统(十四)微信支付
文章目录
- 一、支付微服务
- 1、微信支付 API
- 2、HttpClient 工具类
- 3、支付微服务搭建
- 二、微信支付二维码生成
- 三、检测支付状态
- 四、内网穿透
- 五、支付结果通知
- 1、支付结果回调通知
- 2、Rabbit MQ 配置
- (1)发送支付状态
- (2)监听
- 六、修改订单状态
- 七、超时订单处理
- 八、关闭订单与回滚库存
- 九、总结
代码链接: https://github.com/betterGa/ChangGou
一、支付微服务
1、微信支付 API
微信支付提供了 SDK 和 Demo(然而我并没有找到 sdk,可能微信团队已经把它上传到 Maven 中了):
使用微信支付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 的值:
这时访问 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 工程,循环依赖了
微服务商城系统(十四)微信支付相关推荐
- 微服务商城系统(四)商品管理
文章目录 一.SPU 与 SKU 1.SPU 与 SKU 的概念 2.tb_spu.tb_sku 表结构分析 二.商品管理 1.代码生成 2.查询分类品牌数据 3.新增商品 4.查询商品 5.修改商品 ...
- springcloud 整合 gateway_GitHub上最火的SpringCloud微服务商城系统项目,附全套教程
项目介绍 mall-swarm是一套微服务商城系统,采用了 Spring Cloud Greenwich.Spring Boot 2.MyBatis.Docker.Elasticsearch等核心技术 ...
- mall-swarm是一套微服务商城系统
介绍: mall-swarm是一套微服务商城系统,采用了 Spring Cloud Hoxton & Alibaba.Spring Boot 2.3.Oauth2.MyBatis.Elasti ...
- mall-swarm微服务商城系统
mall-swarm是一套微服务商城系统,采用了 Spring Cloud 2021 & Alibaba.Spring Boot 2.7.Oauth2.MyBatis.Docker.Elast ...
- 微服务商城系统(十六)秒杀核心
代码链接: https://github.com/betterGa/ChangGou 文章目录 一.防止秒杀重复排队 二. 并发超卖问题解决 三. 订单支付 1.实现根据不同类型订单识别不同操作队列 ...
- 微服务商城系统(十) Spring Security Oauth2 + JWT 用户认证
文章目录 一.用户认证分析 1.认证 与 授权 2.单点登录 3.第三方账号登录 4.第三方认证 5.认证技术方案 6.Security Oauth 2.0 入门 7. 资源服务授权 (1)资源服务授 ...
- 微服务商城系统(十五)秒杀基础
文章目录 一.秒杀业务分析 1.需求 2.表结构说明 3.秒杀需求分析 二.秒杀商品压入缓存 1.搭建秒杀服务工程 2.定时任务 3.秒杀商品压入缓存实现 三.秒杀频道页 四.下单实现 五.多线程抢单 ...
- 微服务商城系统(十三)订单、支付流程分析
文章目录 一.订单 1.登录页面配置 2.用户收件地址查询 3. 下单 (1)表结构介绍 (2)下单实现 (3)库存变更 (4)增加积分 二. 支付流程分析 1. 二维码创建 2.微信扫码支付简介 ( ...
- 微服务商城系统(六)商品搜索 SpringBoot 整合 Elasticsearch
文章目录 一.Elasticsearch 和 IK 分词器的安装 二.Kibana 使用 三.数据导入 Elasticsearch 1.SpringData Elasticsearch 介绍 2.搜索 ...
最新文章
- variant 字符串数组_VB数组部分核心知识总结
- 新型量子计算机首个基本元件问世,扩展性更强运算速度更快
- matlab计算一个长式子,matlab求积分,式子太长
- 【干货】mysql查询重复数据sql
- Linuxmint 美化之路
- mysql c#开发库_c# 开发+MySql数据库
- Retrofit2源码分析(一)
- Ubuntu 10不能通过改source.list装JDK 1.6
- Happy Necklace
- python imagedraw line_修复PIL.ImageDraw.Draw.宽线条线条线条
- 说说家乡的旅游景点吧...
- 蓝桥杯「鲁卡斯队列」
- JSPatch转换器:可直接将OC代码转换成JS代码
- IT 基础设施趋势合集 | 多云、超融合、SDS、容器之趋势解读与政策分析
- 电路中的过压保护和过流保护的区别
- 夫妻分居申办上海户口全攻略(zt)
- 计算图像的熵、编码效率、冗余度
- 夏日葵电商:开发一个微信商城系统多少钱
- 江苏省高等学校计算机一级成绩查询,江苏计算机等级考试成绩查询入口
- 一篇文章告诉你:SCI论文投稿流程到底是怎么一回事儿
热门文章
- 【开发教程14】AI语音人脸识别(会议记录仪/人脸打卡机)-AI人脸系统架构
- 【wpf】如果让Bingding 如何让后台数据强制更新界面
- 软件测试 | 测试开发 | Sikuli 基于图形识别的自动化测试技术
- Micro:Bit手柄试用之一MagicPad (解决蓝牙与gamePad包共存)
- 防止前端重复提交表单
- 如何编辑扫描的PDF文件?
- java spring+mybatis整合实现爬虫之《今日头条》搞笑动态图片爬取
- 结构化数据和非结构化数据
- 关机或重新启动电脑勾选再次登录时重新打开窗口导致无法上网
- 用什么方法可以将Word转换成PDF文档?