电商支付系统设计与实现
2024/1/1
# 电商支付系统设计与实现
# 系统概述
支付系统是电商平台的核心组件,负责处理用户支付请求、对接第三方支付渠道、处理支付回调、管理支付流水等功能。一个完善的支付系统需要保证资金安全、支持多种支付方式、具备高可用性和强一致性。
# 支付架构设计
# 整体架构
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 用户端应用 │ │ 商户管理后台 │ │ 财务管理系统 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
└───────────────────────┼───────────────────────┘
│
┌─────────────────────────────────────────────────┐
│ 支付网关层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 路由选择 │ │ 参数验证 │ │ 签名验证 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────┐
│ 支付核心服务 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 订单管理 │ │ 支付处理 │ │ 对账服务 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────┘
│
┌─────────────────────────────────────────────────┐
│ 第三方支付渠道 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 微信支付 │ │ 支付宝支付 │ │ 银联支付 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────┘
# 核心实体设计
# 1. 支付订单
@Entity
@Table(name = "payment_orders")
public class PaymentOrder {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false, length = 32)
private String paymentOrderNumber;
@Column(nullable = false, length = 32)
private String businessOrderNumber;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private BusinessType businessType;
@Column(nullable = false)
private Long userId;
@Column(nullable = false, precision = 10, scale = 2)
private BigDecimal amount;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private PaymentMethod paymentMethod;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private PaymentChannel paymentChannel;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private PaymentStatus status;
@Column(length = 64)
private String thirdPartyTransactionId;
@Column(length = 500)
private String subject;
@Column(length = 1000)
private String body;
@Column(length = 200)
private String notifyUrl;
@Column(length = 200)
private String returnUrl;
@Column(columnDefinition = "TEXT")
private String extendInfo;
@CreationTimestamp
private LocalDateTime createdAt;
@UpdateTimestamp
private LocalDateTime updatedAt;
private LocalDateTime paidAt;
private LocalDateTime expiredAt;
}
public enum PaymentStatus {
PENDING, // 待支付
PAYING, // 支付中
SUCCESS, // 支付成功
FAILED, // 支付失败
CANCELLED, // 已取消
EXPIRED // 已过期
}
public enum PaymentMethod {
WECHAT_PAY, // 微信支付
ALIPAY, // 支付宝
UNION_PAY, // 银联支付
BALANCE, // 余额支付
CREDIT_CARD, // 信用卡
DEBIT_CARD // 借记卡
}
public enum PaymentChannel {
WECHAT_JSAPI, // 微信公众号支付
WECHAT_APP, // 微信APP支付
WECHAT_H5, // 微信H5支付
WECHAT_NATIVE, // 微信扫码支付
ALIPAY_APP, // 支付宝APP支付
ALIPAY_WEB, // 支付宝网页支付
ALIPAY_WAP // 支付宝手机网站支付
}
public enum BusinessType {
ORDER_PAYMENT, // 订单支付
RECHARGE, // 账户充值
REFUND // 退款
}
# 2. 支付流水
@Entity
@Table(name = "payment_transactions")
public class PaymentTransaction {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false, length = 32)
private String transactionNumber;
@Column(nullable = false)
private Long paymentOrderId;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private TransactionType type;
@Column(nullable = false, precision = 10, scale = 2)
private BigDecimal amount;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private TransactionStatus status;
@Column(length = 64)
private String thirdPartyTransactionId;
@Column(columnDefinition = "TEXT")
private String requestData;
@Column(columnDefinition = "TEXT")
private String responseData;
@Column(length = 500)
private String remark;
@CreationTimestamp
private LocalDateTime createdAt;
@UpdateTimestamp
private LocalDateTime updatedAt;
}
public enum TransactionType {
PAYMENT, // 支付
REFUND, // 退款
TRANSFER // 转账
}
public enum TransactionStatus {
PROCESSING, // 处理中
SUCCESS, // 成功
FAILED, // 失败
CANCELLED // 已取消
}
# 支付服务实现
# 1. 支付网关服务
@Service
public class PaymentGatewayService {
@Autowired
private PaymentOrderService paymentOrderService;
@Autowired
private PaymentChannelFactory paymentChannelFactory;
@Autowired
private PaymentRouterService paymentRouterService;
/**
* 创建支付订单
*/
public PaymentResult createPayment(PaymentRequest request) {
// 1. 参数验证
validatePaymentRequest(request);
// 2. 选择支付渠道
PaymentChannel channel = paymentRouterService.selectChannel(request);
// 3. 创建支付订单
PaymentOrder paymentOrder = paymentOrderService.createPaymentOrder(request, channel);
// 4. 调用第三方支付接口
PaymentChannelHandler handler = paymentChannelFactory.getHandler(channel);
PaymentResult result = handler.createPayment(paymentOrder);
// 5. 更新支付订单状态
if (result.isSuccess()) {
paymentOrderService.updateStatus(paymentOrder.getId(), PaymentStatus.PAYING);
} else {
paymentOrderService.updateStatus(paymentOrder.getId(), PaymentStatus.FAILED);
}
return result;
}
/**
* 查询支付状态
*/
public PaymentQueryResult queryPayment(String paymentOrderNumber) {
PaymentOrder paymentOrder = paymentOrderService.findByOrderNumber(paymentOrderNumber);
if (paymentOrder == null) {
throw new BusinessException("支付订单不存在");
}
// 如果订单已经是最终状态,直接返回
if (isFinalStatus(paymentOrder.getStatus())) {
return PaymentQueryResult.of(paymentOrder);
}
// 查询第三方支付状态
PaymentChannelHandler handler = paymentChannelFactory.getHandler(paymentOrder.getPaymentChannel());
PaymentQueryResult result = handler.queryPayment(paymentOrder);
// 更新本地状态
if (result.getStatus() != paymentOrder.getStatus()) {
paymentOrderService.updateStatus(paymentOrder.getId(), result.getStatus());
}
return result;
}
}
# 2. 支付渠道路由
@Service
public class PaymentRouterService {
@Autowired
private PaymentChannelConfigService channelConfigService;
/**
* 选择支付渠道
*/
public PaymentChannel selectChannel(PaymentRequest request) {
PaymentMethod method = request.getPaymentMethod();
String clientType = request.getClientType();
BigDecimal amount = request.getAmount();
// 获取可用的支付渠道
List<PaymentChannelConfig> availableChannels = channelConfigService
.getAvailableChannels(method, clientType);
if (availableChannels.isEmpty()) {
throw new BusinessException("暂无可用的支付渠道");
}
// 根据路由策略选择渠道
return selectByStrategy(availableChannels, amount);
}
/**
* 根据策略选择渠道
*/
private PaymentChannel selectByStrategy(List<PaymentChannelConfig> channels, BigDecimal amount) {
// 1. 按费率排序(费率低的优先)
channels.sort(Comparator.comparing(PaymentChannelConfig::getFeeRate));
// 2. 检查限额
for (PaymentChannelConfig config : channels) {
if (amount.compareTo(config.getMinAmount()) >= 0 &&
amount.compareTo(config.getMaxAmount()) <= 0) {
return config.getChannel();
}
}
throw new BusinessException("支付金额超出限制");
}
}
# 3. 微信支付实现
@Component
public class WechatPayHandler implements PaymentChannelHandler {
@Autowired
private WechatPayConfig wechatPayConfig;
@Autowired
private WechatPayClient wechatPayClient;
@Override
public PaymentResult createPayment(PaymentOrder paymentOrder) {
try {
WechatPayRequest request = buildWechatPayRequest(paymentOrder);
WechatPayResponse response = wechatPayClient.unifiedOrder(request);
if (response.isSuccess()) {
return PaymentResult.success(response.getPrepayId(), response.getPayInfo());
} else {
return PaymentResult.failure(response.getErrorCode(), response.getErrorMsg());
}
} catch (Exception e) {
log.error("微信支付创建失败", e);
return PaymentResult.failure("SYSTEM_ERROR", "系统异常");
}
}
@Override
public PaymentQueryResult queryPayment(PaymentOrder paymentOrder) {
try {
WechatPayQueryRequest request = new WechatPayQueryRequest();
request.setOutTradeNo(paymentOrder.getPaymentOrderNumber());
WechatPayQueryResponse response = wechatPayClient.orderQuery(request);
if (response.isSuccess()) {
PaymentStatus status = convertWechatStatus(response.getTradeState());
return PaymentQueryResult.success(status, response.getTransactionId());
} else {
return PaymentQueryResult.failure(response.getErrorCode(), response.getErrorMsg());
}
} catch (Exception e) {
log.error("微信支付查询失败", e);
return PaymentQueryResult.failure("SYSTEM_ERROR", "系统异常");
}
}
/**
* 构建微信支付请求
*/
private WechatPayRequest buildWechatPayRequest(PaymentOrder paymentOrder) {
WechatPayRequest request = new WechatPayRequest();
request.setAppId(wechatPayConfig.getAppId());
request.setMchId(wechatPayConfig.getMchId());
request.setOutTradeNo(paymentOrder.getPaymentOrderNumber());
request.setBody(paymentOrder.getSubject());
request.setTotalFee(paymentOrder.getAmount().multiply(BigDecimal.valueOf(100)).intValue());
request.setSpbillCreateIp(getClientIp());
request.setNotifyUrl(paymentOrder.getNotifyUrl());
request.setTradeType(getTradeType(paymentOrder.getPaymentChannel()));
// 设置签名
String sign = WechatPaySignUtil.sign(request, wechatPayConfig.getKey());
request.setSign(sign);
return request;
}
/**
* 转换微信支付状态
*/
private PaymentStatus convertWechatStatus(String tradeState) {
switch (tradeState) {
case "SUCCESS":
return PaymentStatus.SUCCESS;
case "REFUND":
return PaymentStatus.SUCCESS; // 已退款但支付成功
case "NOTPAY":
return PaymentStatus.PENDING;
case "CLOSED":
return PaymentStatus.CANCELLED;
case "REVOKED":
return PaymentStatus.CANCELLED;
case "USERPAYING":
return PaymentStatus.PAYING;
case "PAYERROR":
return PaymentStatus.FAILED;
default:
return PaymentStatus.PENDING;
}
}
}
# 4. 支付回调处理
@RestController
@RequestMapping("/payment/notify")
public class PaymentNotifyController {
@Autowired
private PaymentNotifyService paymentNotifyService;
/**
* 微信支付回调
*/
@PostMapping("/wechat")
public String wechatNotify(HttpServletRequest request) {
try {
// 获取回调数据
String xmlData = getRequestBody(request);
// 验证签名
if (!paymentNotifyService.verifyWechatSign(xmlData)) {
log.warn("微信支付回调签名验证失败");
return buildWechatFailResponse("签名验证失败");
}
// 解析回调数据
WechatNotifyData notifyData = parseWechatNotifyData(xmlData);
// 处理回调
boolean success = paymentNotifyService.handleWechatNotify(notifyData);
if (success) {
return buildWechatSuccessResponse();
} else {
return buildWechatFailResponse("处理失败");
}
} catch (Exception e) {
log.error("微信支付回调处理异常", e);
return buildWechatFailResponse("系统异常");
}
}
/**
* 支付宝回调
*/
@PostMapping("/alipay")
public String alipayNotify(HttpServletRequest request) {
try {
// 获取回调参数
Map<String, String> params = getRequestParams(request);
// 验证签名
if (!paymentNotifyService.verifyAlipaySign(params)) {
log.warn("支付宝回调签名验证失败");
return "failure";
}
// 处理回调
boolean success = paymentNotifyService.handleAlipayNotify(params);
return success ? "success" : "failure";
} catch (Exception e) {
log.error("支付宝回调处理异常", e);
return "failure";
}
}
}
# 5. 回调处理服务
@Service
public class PaymentNotifyService {
@Autowired
private PaymentOrderService paymentOrderService;
@Autowired
private PaymentTransactionService transactionService;
@Autowired
private ApplicationEventPublisher eventPublisher;
/**
* 处理微信支付回调
*/
@Transactional
public boolean handleWechatNotify(WechatNotifyData notifyData) {
String outTradeNo = notifyData.getOutTradeNo();
String transactionId = notifyData.getTransactionId();
String resultCode = notifyData.getResultCode();
// 查找支付订单
PaymentOrder paymentOrder = paymentOrderService.findByOrderNumber(outTradeNo);
if (paymentOrder == null) {
log.warn("微信支付回调:订单不存在,订单号:{}", outTradeNo);
return false;
}
// 防重复处理
if (paymentOrder.getStatus() == PaymentStatus.SUCCESS) {
log.info("微信支付回调:订单已处理,订单号:{}", outTradeNo);
return true;
}
// 记录交易流水
PaymentTransaction transaction = new PaymentTransaction();
transaction.setPaymentOrderId(paymentOrder.getId());
transaction.setTransactionNumber(generateTransactionNumber());
transaction.setType(TransactionType.PAYMENT);
transaction.setAmount(paymentOrder.getAmount());
transaction.setThirdPartyTransactionId(transactionId);
transaction.setRequestData(JsonUtil.toJson(notifyData));
if ("SUCCESS".equals(resultCode)) {
// 支付成功
paymentOrder.setStatus(PaymentStatus.SUCCESS);
paymentOrder.setThirdPartyTransactionId(transactionId);
paymentOrder.setPaidAt(LocalDateTime.now());
transaction.setStatus(TransactionStatus.SUCCESS);
// 发布支付成功事件
eventPublisher.publishEvent(new PaymentSuccessEvent(this, paymentOrder));
} else {
// 支付失败
paymentOrder.setStatus(PaymentStatus.FAILED);
transaction.setStatus(TransactionStatus.FAILED);
transaction.setRemark(notifyData.getErrCodeDes());
// 发布支付失败事件
eventPublisher.publishEvent(new PaymentFailureEvent(this, paymentOrder));
}
paymentOrderService.save(paymentOrder);
transactionService.save(transaction);
return true;
}
/**
* 验证微信签名
*/
public boolean verifyWechatSign(String xmlData) {
try {
Map<String, String> dataMap = XmlUtil.xmlToMap(xmlData);
String sign = dataMap.remove("sign");
String calculatedSign = WechatPaySignUtil.sign(dataMap, wechatPayConfig.getKey());
return sign.equals(calculatedSign);
} catch (Exception e) {
log.error("微信签名验证异常", e);
return false;
}
}
}
# 退款处理
# 1. 退款服务
@Service
public class RefundService {
@Autowired
private PaymentOrderService paymentOrderService;
@Autowired
private RefundOrderService refundOrderService;
@Autowired
private PaymentChannelFactory paymentChannelFactory;
/**
* 申请退款
*/
@Transactional
public RefundResult applyRefund(RefundRequest request) {
// 1. 查找原支付订单
PaymentOrder paymentOrder = paymentOrderService.findByOrderNumber(request.getPaymentOrderNumber());
if (paymentOrder == null || paymentOrder.getStatus() != PaymentStatus.SUCCESS) {
throw new BusinessException("原支付订单不存在或状态异常");
}
// 2. 验证退款金额
if (request.getRefundAmount().compareTo(paymentOrder.getAmount()) > 0) {
throw new BusinessException("退款金额不能大于支付金额");
}
// 3. 创建退款订单
RefundOrder refundOrder = refundOrderService.createRefundOrder(request, paymentOrder);
// 4. 调用第三方退款接口
PaymentChannelHandler handler = paymentChannelFactory.getHandler(paymentOrder.getPaymentChannel());
RefundResult result = handler.refund(refundOrder);
// 5. 更新退款订单状态
if (result.isSuccess()) {
refundOrderService.updateStatus(refundOrder.getId(), RefundStatus.SUCCESS);
} else {
refundOrderService.updateStatus(refundOrder.getId(), RefundStatus.FAILED);
}
return result;
}
}
# 对账服务
# 1. 对账实现
@Service
public class ReconciliationService {
@Autowired
private PaymentOrderService paymentOrderService;
@Autowired
private PaymentChannelFactory paymentChannelFactory;
/**
* 执行对账
*/
@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行
public void executeReconciliation() {
LocalDate yesterday = LocalDate.now().minusDays(1);
// 获取所有支付渠道
List<PaymentChannel> channels = Arrays.asList(PaymentChannel.values());
for (PaymentChannel channel : channels) {
try {
reconcileChannel(channel, yesterday);
} catch (Exception e) {
log.error("渠道{}对账失败", channel, e);
}
}
}
/**
* 渠道对账
*/
private void reconcileChannel(PaymentChannel channel, LocalDate date) {
// 1. 下载第三方对账单
PaymentChannelHandler handler = paymentChannelFactory.getHandler(channel);
List<ThirdPartyTransaction> thirdPartyTransactions = handler.downloadBill(date);
// 2. 获取本地交易记录
List<PaymentOrder> localOrders = paymentOrderService.findByChannelAndDate(channel, date);
// 3. 执行对账
ReconciliationResult result = doReconciliation(localOrders, thirdPartyTransactions);
// 4. 处理差异
handleReconciliationDifferences(result.getDifferences());
// 5. 保存对账结果
saveReconciliationResult(channel, date, result);
}
/**
* 执行对账逻辑
*/
private ReconciliationResult doReconciliation(List<PaymentOrder> localOrders,
List<ThirdPartyTransaction> thirdPartyTransactions) {
Map<String, PaymentOrder> localMap = localOrders.stream()
.collect(Collectors.toMap(PaymentOrder::getPaymentOrderNumber, Function.identity()));
Map<String, ThirdPartyTransaction> thirdPartyMap = thirdPartyTransactions.stream()
.collect(Collectors.toMap(ThirdPartyTransaction::getOutTradeNo, Function.identity()));
List<ReconciliationDifference> differences = new ArrayList<>();
// 检查本地有但第三方没有的记录
for (PaymentOrder localOrder : localOrders) {
String orderNumber = localOrder.getPaymentOrderNumber();
ThirdPartyTransaction thirdPartyTx = thirdPartyMap.get(orderNumber);
if (thirdPartyTx == null) {
differences.add(new ReconciliationDifference(
DifferenceType.LOCAL_ONLY, orderNumber, localOrder.getAmount(), null));
} else {
// 检查金额是否一致
if (!localOrder.getAmount().equals(thirdPartyTx.getAmount())) {
differences.add(new ReconciliationDifference(
DifferenceType.AMOUNT_MISMATCH, orderNumber,
localOrder.getAmount(), thirdPartyTx.getAmount()));
}
}
}
// 检查第三方有但本地没有的记录
for (ThirdPartyTransaction thirdPartyTx : thirdPartyTransactions) {
String orderNumber = thirdPartyTx.getOutTradeNo();
if (!localMap.containsKey(orderNumber)) {
differences.add(new ReconciliationDifference(
DifferenceType.THIRD_PARTY_ONLY, orderNumber, null, thirdPartyTx.getAmount()));
}
}
return new ReconciliationResult(differences);
}
}
# 安全机制
# 1. 签名验证
public class PaymentSignUtil {
/**
* 生成签名
*/
public static String generateSign(Map<String, String> params, String key, String signType) {
// 1. 过滤空值参数
Map<String, String> filteredParams = params.entrySet().stream()
.filter(entry -> StringUtils.hasText(entry.getValue()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
// 2. 按key排序
String sortedParams = filteredParams.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.map(entry -> entry.getKey() + "=" + entry.getValue())
.collect(Collectors.joining("&"));
// 3. 拼接密钥
String stringToSign = sortedParams + "&key=" + key;
// 4. 计算签名
if ("MD5".equals(signType)) {
return DigestUtils.md5Hex(stringToSign).toUpperCase();
} else if ("HMAC-SHA256".equals(signType)) {
return HmacUtils.hmacSha256Hex(key, stringToSign).toUpperCase();
} else {
throw new IllegalArgumentException("不支持的签名类型:" + signType);
}
}
/**
* 验证签名
*/
public static boolean verifySign(Map<String, String> params, String key, String signType) {
String sign = params.remove("sign");
if (!StringUtils.hasText(sign)) {
return false;
}
String calculatedSign = generateSign(params, key, signType);
return sign.equals(calculatedSign);
}
}
# 2. 幂等性保证
@Component
public class PaymentIdempotentHandler {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String IDEMPOTENT_KEY_PREFIX = "payment:idempotent:";
private static final int EXPIRE_SECONDS = 300; // 5分钟
/**
* 检查幂等性
*/
public boolean checkIdempotent(String idempotentKey) {
String key = IDEMPOTENT_KEY_PREFIX + idempotentKey;
Boolean success = redisTemplate.opsForValue().setIfAbsent(key, "1", EXPIRE_SECONDS, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
}
/**
* 释放幂等锁
*/
public void releaseIdempotent(String idempotentKey) {
String key = IDEMPOTENT_KEY_PREFIX + idempotentKey;
redisTemplate.delete(key);
}
}
# 监控与告警
# 1. 支付监控
@Component
public class PaymentMonitor {
@Autowired
private MeterRegistry meterRegistry;
/**
* 记录支付指标
*/
public void recordPaymentMetrics(PaymentOrder paymentOrder, PaymentStatus status) {
// 支付次数
Counter.builder("payment.count")
.tag("channel", paymentOrder.getPaymentChannel().name())
.tag("status", status.name())
.register(meterRegistry)
.increment();
// 支付金额
if (status == PaymentStatus.SUCCESS) {
Gauge.builder("payment.amount")
.tag("channel", paymentOrder.getPaymentChannel().name())
.register(meterRegistry, paymentOrder.getAmount().doubleValue());
}
// 支付耗时
if (paymentOrder.getPaidAt() != null) {
Duration duration = Duration.between(paymentOrder.getCreatedAt(), paymentOrder.getPaidAt());
Timer.builder("payment.duration")
.tag("channel", paymentOrder.getPaymentChannel().name())
.register(meterRegistry)
.record(duration);
}
}
}
# 总结
支付系统的关键设计要点:
- 安全性:签名验证、HTTPS传输、敏感信息加密存储
- 可靠性:幂等性保证、事务一致性、异常处理和重试机制
- 扩展性:支持多种支付渠道、灵活的路由策略
- 监控性:完善的日志记录、指标监控、告警机制
- 对账机制:定时对账、差异处理、数据一致性保证
- 性能优化:异步处理、缓存策略、数据库优化
通过以上设计,可以构建一个安全、可靠、高性能的支付系统,满足电商平台的支付需求。