1
[TOC]
支付宝支付
我们之前有专题课程讲解支付宝支付,在我们项目中,不再重复讲解支付宝支付知识点,我们来简单回顾下基本支付流程
为了保证交易双方(商户和支付宝)的身份和数据安全,开发者在调用接口前,需要配置双方密钥,对交易数据进行双方校验。密钥包含应用私钥(APP_PRIVATE_KEY)和应用公钥(APP_PUBLIC_KEY)。生成密钥后,开发者需要在开放平台开发者中心进行密钥配置,配置完成后可以获取支付宝公钥(ALIPAY_PUBLIC_KEY)。密钥的配置旨在对交易数据进行双方校验。具体流程如下图所示:
支付的完整流程如下:
这里需要重点强调下同步通知和异步通知,同步通知和异步通知发送的数据没有本质的区别。
同步通知有2个作用:
- 第一是从支付宝的页面上返回自己的网站继续后续操作;
- 第二是携带支付状态的get参数;让自己的网站用于验证;
异步通知作用:
同步通知后;还需要异步通知主要是为了防止出现意外情况;因为涉及到金钱;这是一个对安全和稳定要求比较严格的场景;如果同步通知的过程中;用户不小心关闭了浏览器;或者浏览器卡死了;异步也能收到通知;记录支付状态;即便是用户端没问题;万一自己的服务器网络异常了一下呢?如果自己的服务器没有正确返回接受到通知的状态;支付宝的服务器会在一段时间内持续的往自己的服务器发送异步通知;一直到成功,一下是官网文档解释的异步通知发送的频率
程序执行完后必须打印输出“success”(不包含引号)。如果商户反馈给支付宝的字符不是 success 这7个字符,支付宝服务器会不断重发通知,直到超过24小时22分钟。一般情况下,25小时以内完成8次通知(通知的间隔频率一般是:4m,10m,10m,1h,2h,6h,15h);
所以我们支付成功,修改订单状态,最好在异步通知中处理,以确保订单状态的更改
2 开发准备
2.1 支付宝配置
正式的支付功能上线,需要使用企业的信息,所以我们这里使用支付宝提供的沙箱功能实现测试,如何访问沙箱呢?下面看一下实现步骤:
访问支付宝首页,选择角色:"我是开发者"
使用手机上的支付宝,扫码登录到系统。登录后选择左上角“控制台”,然后在页面下方有“开发服务”
沙箱的使用步骤:
步骤1:查看信息
步骤2:设置秘钥
大家第一次使用时,文本框内是空的,点击“支付宝密钥生成器”,下载exe文件,并安装。
运行程序,生成密钥:
把这里的“应用公钥”复制到“加签管理”中的公钥位置。设置完毕后,密钥位置会变成“设置/查看”
步骤3:下载沙箱钱包进行测试,注意这里必须使用沙箱版钱包进行测试,账户信息已给定。此软件只支持安卓系统
步骤4:测试账户的信息
2.2 内网穿透
因为需要支付宝调用我们的地址,进行同步和异步通知,这里需要进行内网穿透配置,这里我们使用natapp进行内网穿透
启动natapp本地客户端:
使用上面authtoken启动本地natapp客户端
natapp -authtoken=token
2.3 安装RabbitMQ
使用docker安装rabbitmq非常简单,虚拟机中执行下面命令即可
# 拉取镜像
docker pull rabbitmq:management
# 创建容器
docker run -d --hostname my-rabbit --name rabbit -p 5672:5672 -p 15672:15672 rabbitmq:management
3 订单支付流程
如上图,步骤分析如下:
- 用户下单之后,订单数据会存入到MySQL中
- 用户下单后,进入支付页面,支付页面调用支付系统,并在页面生成支付二维码。
- 用户扫码支付后,支付宝服务器会通调用前预留的回调地址,并携带支付状态信息。
- 支付系统接到支付状态信息后,将支付状态信息发送给RabbitMQ
- 订单系统监听RabbitMQ中的消息获取支付状态,并根据支付状态修改订单状态
4 支付微服务
我们的支付微服务,直接使用支付宝demo基础上开发,也就是之前大家在学习支付专题课程中的使用的案例代码
4.1 搭建支付微服务
创建legou-pay微服务工程:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>legou-parent</artifactId>
<groupId>com.lxs</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>legou-pay</artifactId>
<dependencies>
<!-- 支付功能SDK -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.10.124.ALL</version>
</dependency>
<!-- amqp -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
legou-pay/src/main/resources/bootstrap.yml
spring:
application:
name: pay-service
config-repo/pay-service.yml
server:
port: 9010
logging:
#file: demo.log
pattern:
console: "%d - %msg%n"
level:
org.springframework.web: debug
com.lxs: debug
spring:
rabbitmq:
host: 192.168.220.110
port: 5672
mq:
pay:
exchange:
order: exchange.pay.order
queue:
order: queue.pay.order
routing:
key: routing.pay.order
legou-pay/src/main/java/com/lxs/legou/pay/PayApplication.java
package com.lxs.legou.pay;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableCircuitBreaker
public class PayApplication {
public static void main(String[] args) {
SpringApplication.run(PayApplication.class,args);
}
}
4.2 导入支付demo代码
这里我们直接拷贝支付宝提供的二维码支付demo案例代码。
4.2.1 支付宝配置类
修改以上4个地方,改成自己的配置,其他不变
4.2.2 支付对象
@Configuration
public class AlipayConfig {
/**
* 支付客户端对象
* @return
*/
@Bean
public AlipayClient alipayClient(){
return new DefaultAlipayClient(AlipayUtils.gatewayUrl, AlipayUtils.app_id, AlipayUtils.merchant_private_key,"json", AlipayUtils.charset,
AlipayUtils.alipay_public_key, AlipayUtils.sign_type);
}
/**
* 支付请求对象
* @return
*/
@Bean
public AlipayTradePagePayRequest alipayTradePagePayRequest(){
return new AlipayTradePagePayRequest();
}
}
4.2.3 异步通知
4.2.4 生成支付二维码
5 测试订单支付
使用postman添加商品到购物车,产生订单
把订单号,支付金额拷贝到支付页面
生成支付二维码,使用支付宝手机沙箱客户端,扫码支付
6 支付完成修改订单状态
6.1 RabbitMQ配置类
legou-pay/src/main/java/com/lxs/legou/pay/config/MqConfig.java
package com.lxs.legou.pay.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
@Configuration
public class MqConfig {
@Autowired
private Environment env;
/**
* 声明交换机
* @return
*/
@Bean
public Exchange orderExchange() {
return ExchangeBuilder.topicExchange(env.getProperty("mq.pay.exchange.order")).durable(true).build();
}
@Bean
public Queue orderQueue() {
return QueueBuilder.durable(env.getProperty("mq.pay.queue.order")).build();
}
@Bean
public Binding orderBinding(@Qualifier("orderQueue") Queue queue, @Qualifier("orderExchange") Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(env.getProperty("mq.pay.routing.key")).noargs();
}
}
6.2 发送支付完成消息
在支付异步通知中发送支付完成消息到MQ
6.2 监听支付完成消息
在订单微服务工程中监听支付完成消息,修改订单状态为“已支付”
6.2.1 添加依赖
<!-- amqp -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
6.2.2 service中修改订单状态
OrderServiceImpl
@Override
public void updateStatus(String out_trade_no, String transaction_id) {
//1.根据id 获取订单的数据
Order order = getBaseMapper().selectById(Long.parseLong(out_trade_no));
//2.更新
order.setUpdateTime(new Date());
// 支付的时间 从微信的参数中获取
order.setPayTime(new Date());
order.setOrderStatus("1");
order.setPayStatus("1");
order.setTransactionId(transaction_id);
//3.更新到数据库
getBaseMapper().updateById(order);
}
6.2.3 监听支付完成消息
legou-order/legou-order-service/src/main/java/com/lxs/legou/order/listener/OrderUpdateListener.java
package com.lxs.legou.order.listener;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lxs.legou.order.service.OrderService;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Map;
@Component
@RabbitListener(queues = "${mq.pay.queue.order}")
public class OrderUpdateListener {
@Autowired
private OrderService orderService;
@Autowired
private ObjectMapper objectMapper;
@RabbitHandler
public void handlerData(String msg) {
//1.接收消息(有订单的ID 有transaction_id )
Map<String, String> map = null;
try {
map = objectMapper.readValue(msg, Map.class);
} catch (IOException e) {
e.printStackTrace();
}
//2.更新对营的订单的状态
if (map != null) {
if (map.get("trade_status").equals("TRADE_SUCCESS")) {
orderService.updateStatus(map.get("out_trade_no"), map.get("trade_no"));
} else {
//删除订单 支付失败.....
}
}
}
}
7 延迟30分钟取消订单(作业)
商城下订单后30分钟后没有完成支付,取消订单,流程分析
7.1 RabbitMQ延迟队列
RabbitMQ实现延迟队列可以借助TTL+死信队列实现
死信队列,英文缩写:DLX 。Dead Letter Exchange(死信交换机),当消息成为Dead message后,可以被重新发送到另一个交换机,这个交换机就是DLX。
消息成为死信的三种情况:
队列消息长度到达限制;
消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;
原队列存在消息过期设置,消息到达超时时间未被消费;
队列绑定死信交换机:
给队列设置参数: x-dead-letter-exchange 和 x-dead-letter-routing-key
7.2 代码实现
配置文件
mq:
pay:
exchange:
order: exchange.pay.order
queue:
order: queue.pay.order
routing:
key: routing.pay.order
order:
exchange:
ttl: exchange.order.ttl
dlx: exchange.order.dlx
queue:
ttl: queue.order.ttl
dlx: queue.order.dlx
routing:
ttl: routing.order.ttl
dlx: routing.order.dlx
RabbitMQ配置类
legou-order/legou-order-service/src/main/java/com/lxs/legou/order/config/MqConfig.java
package com.lxs.legou.order.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
/**
* 延迟30分钟取消订单
* @Des 乐购商城项目
*/
@Configuration
public class MqConfig {
@Autowired
private Environment env;
/**
* 正常交换机
* @return
*/
@Bean
public Exchange ttlExchange() {
return ExchangeBuilder.directExchange(env.getProperty("mq.order.exchange.ttl")).durable(true).build();
}
/**
* 私信交换机
* @return
*/
@Bean
public Exchange dlxExchange() {
return ExchangeBuilder.directExchange(env.getProperty("mq.order.exchange.dlx")).durable(true).build();
}
/**
* 正常队列
* @return
*/
@Bean
public Queue ttlQueue() {
return QueueBuilder
.durable(env.getProperty("mq.order.queue.ttl"))
//死信交换机名称
.withArgument("x-dead-letter-exchange", env.getProperty("mq.order.exchange.dlx"))
//发送给私信交换机的routingkey
.withArgument("x-dead-letter-routing-key", env.getProperty("mq.order.routing.dlx"))
//队列过期时间,10秒过期
.withArgument("x-message-ttl", 10000)
.build();
}
/**
* 死信队列
* @return
*/
@Bean
public Queue dlxQueue() {
return QueueBuilder.durable(env.getProperty("mq.order.queue.dlx")).build();
}
/**
* 正常交换机,队列绑定
* @param queue
* @param exchange
* @return
*/
@Bean
public Binding ttlBinding(@Qualifier("ttlQueue") Queue queue, @Qualifier("ttlExchange") Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(env.getProperty("mq.order.routing.ttl")).noargs();
}
/**
* 死信交换机,队列绑定
* @param queue
* @param exchange
* @return
*/
@Bean
public Binding dlxBinding(@Qualifier("dlxQueue") Queue queue, @Qualifier("dlxExchange") Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(env.getProperty("mq.order.routing.dlx")).noargs();
}
}
在下单服务中发送延迟消息
//发送延迟30分钟取消订单消息
rabbitTemplate.convertAndSend(env.getProperty("mq.order.exchange.ttl"), env.getProperty("mq.order.routing.ttl"), order.getId().toString());
监听延迟消息
legou-order/legou-order-service/src/main/java/com/lxs/legou/order/listener/OrderLazyListener.java
package com.lxs.legou.order.listener;
import com.lxs.legou.order.po.Order;
import com.lxs.legou.order.service.OrderService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(queues = "${mq.order.queue.dlx}")
public class OrderLazyListener {
@Autowired
private OrderService orderService;
@RabbitHandler
public void handlerData(String msg) {
System.out.println(msg);
if (StringUtils.isNotEmpty(msg)) {
Long id = Long.parseLong(msg);
Order order = orderService.getById(id);
order.setOrderStatus("3");
orderService.updateById(order); //修改订单装填
//回滚库存(作业)
}
}
}