本文代码已提交至Github(版本号:99a5a21d8139a9d05eb91f1298aa5565f7d513d5
),有兴趣的同学可以下载来看看:https://github.com/ylw-github/taodong-shop
前面讲解了聚合支付的介绍、银联支付相关的源码分析、支付系统的表设计以及分布式系统的解决方案,有兴趣的同学可以参阅:
- 《淘东电商项目(52) -聚合支付开篇》
- 《淘东电商项目(53) -银联支付案例源码分析》
- 《淘东电商项目(54) -银联支付案例(同步与异步)》
- 《淘东电商项目(55) -支付系统核心表设计》
- 《淘东电商项目(56) -支付系统分布式事务的解决方案》
现在开始进入代码讲解,后续会逐步根据如下流程图,实现每一步骤的代码。本文主要讲解如下流程图的第1到第4个步骤:
本文目录结构: l____引言 l____ 1. 提交订单功能实现(第1、2个步骤)) l____ 2. token获取支付内容功能实现(第3、4个步骤) l____ 3. 测试
1. 提交订单功能实现(第1、2个步骤)提交订单,请求的url为:http://localhost:8600/cratePayToken?payAmount=9999&orderId=20200513141452&userId=27&productName=江西脐橙,这个url是在我们选好了要购买的商品后提交。测试效果图如下:
①来看看Controller的代码,它的作用是用来创建token令牌,并把订单与预插入到数据库,生成待支付订单,下面使用Redis来生成token,Redis的key值为token,对应的value为数据库中订单的唯一主键:
@RestController
public class PayMentTransacTokenServiceImpl extends BaseApiService implements PayMentTransacTokenService {
@Autowired
private PaymentTransactionMapper paymentTransactionMapper;
@Autowired
private GenerateToken generateToken;
@Override
public BaseResponse cratePayToken(PayCratePayTokenDto payCratePayTokenDto) {
String orderId = payCratePayTokenDto.getOrderId();
if (StringUtils.isEmpty(orderId)) {
return setResultError("订单号码不能为空!");
}
Long payAmount = payCratePayTokenDto.getPayAmount();
if (payAmount == null) {
return setResultError("金额不能为空!");
}
Long userId = payCratePayTokenDto.getUserId();
if (userId == null) {
return setResultError("userId不能为空!");
}
String productName = payCratePayTokenDto.getProductName();
if (productName == null) {
return setResultError("商品名称不能为空!");
}
// 2.将输入插入数据库中 待支付记录
PaymentTransactionEntity paymentTransactionEntity = new PaymentTransactionEntity();
paymentTransactionEntity.setOrderId(orderId);
paymentTransactionEntity.setPayAmount(payAmount);
paymentTransactionEntity.setUserId(userId);
// 使用雪花算法 生成全局id
paymentTransactionEntity.setPaymentId(SnowflakeIdUtils.nextId());
paymentTransactionEntity.setProductName(productName);
int result = paymentTransactionMapper.insertPaymentTransaction(paymentTransactionEntity);
if (!toDaoResult(result)) {
return setResultError("系统错误!");
}
// 获取主键id
Long payId = paymentTransactionEntity.getId();
if (payId == null) {
return setResultError("系统错误!");
}
// 3.生成对应支付令牌
String keyPrefix = "pay_";
String token = generateToken.createToken(keyPrefix, payId + "");
JSONObject dataResult = new JSONObject();
dataResult.put("token", token);
return setResultSuccess(dataResult);
}
}
②这里的订单id使用雪花算法生成,附录上雪花算法工具类:
package com.ylw.common.web.core.util.twitter;
/**
* Twitter_Snowflake
* SnowFlake的结构如下(每部分用-分开):
* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 -
* 000000000000
* 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0
* 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)
* 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。
* 41位的时间截,可以使用69年,年T = (1L
支付金额:
¥${(data.payAmount/100)?string('0.00')}
订单详情:
商品名称:${data.productName}
支付订单:${data.paymentId}
应付金额: ¥${(data.payAmount/100)?string('0.00')}
购买时间:${currentTime}
②首先请求获取订单详情,携带token参数,url为:http://localhost:8079/pay?payToken=第1、2步骤返回的token,看看Controller代码,它的流程主要是根据token获取订单详情,然后显示到index
页面。
/**
* description: 支付
* create by: YangLinWei
* create time: 2020/5/13 1:35 下午
*/
@Controller
public class PayController extends BaseWebController {
@Autowired
private PayMentTransacInfoFeign payMentTransacInfoFeign;
@Autowired
private PaymentChannelFeign paymentChannelFeign;
/**
* 跳转到index页面
*/
private static final String INDEX_FTL = "index";
@RequestMapping("/pay")
public String pay(HttpServletRequest request, String payToken, Model model) {
// 1.验证payToken参数
if (StringUtils.isEmpty(payToken)) {
setErrorMsg(model, "支付令牌不能为空!");
return ERROR_500_FTL;
}
// 2.使用payToken查询支付信息
BaseResponse tokenByPayMentTransac = payMentTransacInfoFeign.tokenByPayMentTransac(payToken);
if (!isSuccess(tokenByPayMentTransac)) {
setErrorMsg(model, tokenByPayMentTransac.getMsg());
return ERROR_500_FTL;
}
// 3.查询支付信息
PayMentTransacDTO data = tokenByPayMentTransac.getData();
model.addAttribute("data", data);
// 4.查询渠道信息
List paymentChanneList = paymentChannelFeign.selectAll();
model.addAttribute("paymentChanneList", paymentChanneList);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
model.addAttribute("currentTime",sdf.format(new Date()));
return INDEX_FTL;
}
}
3. 测试
测试前须知:因为之前聚合支付模块已经实现了单点登录,所以测试前,需要启动的模块有:Eureka
注册中心、xxlsso
单点登录系统、member
会员服务(如下图),下面开始来测试。
1.启动支付服务AppPay
和聚合支付门户服务AppPortalPayWeb
:
2.首先验证获取token令牌,浏览器请求:http://localhost:8600/cratePayToken?payAmount=9999&orderId=20200513141452&userId=27&productName=广东米酒 3.根据返回的token请求支付申请:http://localhost:8079/pay?payToken=pay_c013d23b039446c68f522517929cfa57
好了,到此为止,支付流程图的第1到第4个步骤已经完成,下一篇博客继续讲解使用设计模式(策略、工厂等)根据支付方式来进行支付。