补充支付相关项目经验
图灵商城项目支付流程
全局概览
- 支付通道: 当前实现了支付宝当面付(F2F)二维码支付。
- 关键流程: 下单锁库存 → 生成支付二维码 → 支付宝异步回调验签 → 本地事务更新订单状态 → 事务消息提交 → 商品服务消费消息扣减真实库存(含幂等)。
- 一致性策略: RocketMQ事务消息 + 本地事务日志 + 定时回查/超时关单,确保“支付-订单-库存”最终一致。
- 幂等与安全: 使用事务ID落库做幂等校验;支付宝回调做签名验签。
端到端流程(按时间线)
- 下单并锁定库存(订单状态=待付款)
- 在生成订单时锁定库存,订单状态初始化为待付款,后续支付成功才扣减真实库存。
- 如果采用“异步下单+事务消息”,还会发一条“创建订单”的事务消息,用于后续“超时取消”。
- 生成支付宝支付二维码
- 控制器
/order/tradeQrCode调用交易服务生成当面付二维码,设置回调notifyUrl,并将二维码路径写回订单。
这里所需参数,以及如何封装,在demo中皆已提供
// ... 省略若干构建请求参数 ...
.setNotifyUrl(tradePayProp.getPaySuccessCallBack()+"/1")// 支付成功回调地址,/1 表示支付宝
.setGoodsDetailList(goodsDetailList);
log.info("alipay callback url:--->"+builder.getNotifyUrl());
AlipayF2FPrecreateResult result = tradeService.tradePrecreate(builder);
// 成功后生成二维码图片并返回其http访问路径
- 支付宝异步回调(验签 + 触发支付成功逻辑)
- 回调入口
/order/paySuccess/{payType};验签后取out_trade_no作为订单号,调用支付成功处理。
验证签名通过后,即开始正式扣减库存
// 1. 收集参数并验签
boolean isPassed = AlipaySignature.rsaCheckV2(param, Configs.getAlipayPublicKey(),"utf-8",Configs.getSignType());
// 2. 取出订单号并执行业务
if(isPassed){
Long orderId = Long.parseLong(param.get("out_trade_no"));
int count = portalOrderService.paySuccess(orderId,payType);
// 写回给支付宝 "success"/"unSuccess"
}
- 订单服务:支付成功的本地事务 + 事务消息
- 旧版是“直接改订单 + 远程扣库存”,目前核心实现改为“发事务消息 → 本地事务更新订单 → 提交消息由商品服务扣库存”,更安全。
// 使用事务消息机制发送扣减库存消息(包含 orderId/payType/明细列表/transactionId)
return reduceStockMsgSender.sendReduceStockMsg(orderId,payType,orderDetail)? 1 : -1;
- 事务消息发送封装(携带事务ID用于幂等)
String destination = "reduce-stock";
String transactionId = UUID.randomUUID().toString();
Message<StockChangeEvent> message = MessageBuilder.withPayload(stockChangeEvent)
//传递事务ID,用于回查事务是否执行
.setHeader(RocketMQHeaders.TRANSACTION_ID, transactionId)
.setHeader("orderId",orderId)
.setHeader("payType",payType)
.build();
// destination:目的地(主题),这里发送给reduce-stock这个topic
// message:发送给消费者的消息体,需要使用MessageBuilder.withPayload() 来构建消息
// RocketMQ事务监听器:修改订单状态、回查支付状态
TransactionSendResult sendResult = extRocketMQTemplate.sendMessageInTransaction(destination,message,orderId);
- 订单侧的“事务监听器”本地事务:更新订单状态=待发货、设置支付方式/支付时间,并记录事务日志,返回 COMMIT
Long orderId = Long.parseLong(String.valueOf(arg));
String transactionId = (String) message.getHeaders().get(RocketMQHeaders.TRANSACTION_ID);
Integer payType = Integer.valueOf((String)message.getHeaders().get("payType"));
// 修改订单状态 + 记录事务日志
portalOrderService.updateOrderStatus(orderId,payType,transactionId);
return RocketMQLocalTransactionState.COMMIT;
- 事务状态回查:靠“本地事务日志”判断是否已执行过
int existTx = omsOrderMapper.isExistTx(transactionId);
return existTx > 0 ? COMMIT : UNKNOWN;
- 商品服务:消费“扣库存”消息并落幂等日志
- 监听
reduce-stock主题,按订单明细扣减真实库存;使用transactionId做幂等。
@RocketMQMessageListener(consumerGroup = "${rocketmq.consumer.group}",topic = "${rocketmq.consumer.topic}")
public class ReduceStockMsgConsumer implements RocketMQListener<StockChangeEvent> {
public void onMessage(StockChangeEvent stockChangeEvent) {
stockManageService.reduceStock(stockChangeEvent);
}
}
// 幂等校验:存在事务记录直接返回
if(skuStockMapper.isExistTx(stockChangeEvent.getTransactionId())>0){ return; }
// 扣减冻结库存(真实库存)
skuStockMapper.updateSkuStock(stockChangesList);
// 记录事务日志用于幂等
skuStockMapper.addTx(stockChangeEvent.getTransactionId());
- 超时未支付的订单处理(订单创建事务消息 + 回查 + 取消)
- 下单时会发“创建订单”的事务消息,监听器会用 Redis 记录回查次数,循环查询支付宝交易状态;超过次数未支付则关单并发送取消消息,消费者释放锁定库存。
if(retryTimes >= maxTryMums){
// 订单置为无效,返回 COMMIT(触发取消订单消费者释放锁定库存)
} else {
// 查询支付宝支付状态,已支付则 ROLLBACK(丢弃消息),未支付则回查次数+1返回 UNKNOWN
}
@RocketMQMessageListener(... topic = "${rocketmq.tulingmall.asyncOrderTopic}")
public void onMessage(String message) {
// 取消订单 & 释放锁定库存
omsPortalOrderService.cancelOrder(orderId,memberId);
}
关键状态与类型
/*订单状态:0待付款;1待发货;2已发货;3已完成;4已关闭;5无效订单*/
public static final int ORDER_STATUS_UNPAY = 0;
public static final int ORDER_STATUS_UNDELIVERY = 1;
// ...
/*支付方式:0未支付;1支付宝;2微信*/
public static final int ORDER_PAY_TYPE_ALIPAY = 1;
安全与配置要点
- 支付宝验签: 使用平台公钥与一致的
sign_type验签;回调里剔除sign_type字段参与验签。 - 回调地址: 通过配置
trade.zhifu.qrcode.paySuccessCallBack注入,生成二维码时带上notifyUrl。 - 配置加载: 支付宝参数读取
zfbinfo.properties。
private static String alipayPublicKey; // 支付宝RSA公钥,用于验签支付宝应答
private static String signType; // 签名类型
public synchronized static void init(String filePath) { configs = new PropertiesConfiguration(filePath); ... }
面试亮点与可复述要点
- 支付对账与一致性: 采用“支付回调 + 事务消息 + 本地事务日志 + 幂等 + 超时回查”的组合拳,确保支付触发的后续库存扣减“最终一致”。
- 库存两阶段: 下单先“锁定库存”,支付成功后“扣减真实库存”,取消/超时释放锁定库存,避免超卖。
- 事务消息落地: 订单服务在本地事务内更新订单并记录
transactionId,提交消息后由商品服务消费;两端均用“事务日志”做幂等。 - 回查策略: 未支付场景通过消息事务回查 + Redis计数 + 最多重试 + 关单,使系统可在外部系统不可靠时自愈。
- 安全合规: 回调验签、过滤
sign_type、仅认out_trade_no作为业务订单号,回调响应“success”/“unSuccess”。
可优化建议(面试加分)
- 补齐微信支付实现与统一支付抽象(策略模式),兼容多通道。
- 回调参数签名验签后做参数完整性校验(金额、商户号等)与订单状态幂等检查,补齐退款回滚流程。
- 结合“事务外盒/本地消息表”进一步强化可靠投递与失败补偿。
- 使用延迟消息替代回查或优化回查间隔与次数策略。
要点小结
- 支付宝当面付二维码 → 回调验签 → 订单本地事务更新 + 事务消息 → 商品服务幂等扣库存。
- 订单创建事务消息 + 回查机制 → 超时关单并释放锁定库存。
- 事务ID日志做幂等;核心在“事务消息+最终一致性”的工程化实现。
面试中,支付问题点
支付相关的项目经验核心关注的是与支付渠道(如支付宝、微信支付等)的对接逻辑、订单与支付的联动流程、以及支付环节中的技术细节(如接口调用、签名验证、回调处理、异常场景应对等)。而支付宝订单流程的对接通常会涉及这些关键环节:
- 比如调用支付宝 SDK 生成支付参数、发起支付请求;
- 处理支付宝的同步 / 异步回调(验证签名、解析支付结果、更新订单状态);
- 可能涉及的支付超时处理、重复支付校验;
- 甚至可能包括与业务系统(如订单系统、库存系统)的联动逻辑等。
面试时,你可以重点阐述这部分经验的细节,比如:
- 具体负责了哪些环节(如支付参数组装、回调接口开发、订单状态同步等);
- 对接过程中遇到的技术问题(如签名失败、回调重复触发、网络超时等)及解决方案;
- 如何保证支付流程的安全性(如签名验证、参数加密)和数据一致性(如分布式事务、幂等性处理)。
这些细节能体现你对支付流程的理解和技术实现能力,足以证明你具备支付相关的项目经验。如果还有其他相关延伸(如退款流程、对账逻辑等),也可以补充说明,进一步丰富你的经验展示。