概述
在支付系统中,交易状态管理是核心业务逻辑之一。一个清晰、健壮的状态机设计能够确保支付流程的可靠性和可维护性。本文将介绍基于Spring Boot环境的轻量级状态机设计,实现支付交易的状态管理。
设计思考
在设计支付状态机时,我们需要考虑以下几个关键方面:
- 状态完整性:确保覆盖支付流程所有可能的状态,包括正向流程和逆向流程(退款)
- 转换可控性:只有合法的状态转换才能被执行,防止出现非法状态
- 可扩展性:设计应允许未来轻松添加新状态或事件
- 事务安全性:状态转换需要保证原子性,避免出现中间状态
- 可观测性:提供状态变更的审计和追踪能力
状态转换图设计
通过可靠的状态机系统,可以确保支付流程的正确性和一致性,我们可以设计以下状态转换规则:
(ps:请自动忽略掉图片水印O(∩_∩)O哈哈~)

核心组件实现
1. 状态转换规则配置
transitionRules :意为在当前支付状态下,通过某个支付交易事件,转换到另一个状态
scss
@Component
public class PaymentStateTransitionConfig {
private final Map<PaymentStatus, Map<PaymentEvent, PaymentStatus>> transitionRules = new EnumMap<>(PaymentStatus.class);
@PostConstruct
public void init() {
// CREATED 状态的转换规则
Map<PaymentEvent, PaymentStatus> createdTransitions = new EnumMap<>(PaymentEvent.class);
createdTransitions.put(PaymentEvent.PAY, PaymentStatus.PROCESSING);
createdTransitions.put(PaymentEvent.CLOSE, PaymentStatus.CLOSED);
transitionRules.put(PaymentStatus.CREATED, createdTransitions);
// PROCESSING 状态的转换规则
Map<PaymentEvent, PaymentStatus> processingTransitions = new EnumMap<>(PaymentEvent.class);
processingTransitions.put(PaymentEvent.PAYMENT_SUCCESS, PaymentStatus.SUCCESS);
processingTransitions.put(PaymentEvent.PAYMENT_FAILED, PaymentStatus.FAILED);
transitionRules.put(PaymentStatus.PROCESSING, processingTransitions);
// SUCCESS 状态的转换规则
Map<PaymentEvent, PaymentStatus> successTransitions = new EnumMap<>(PaymentEvent.class);
successTransitions.put(PaymentEvent.REQUEST_REFUND, PaymentStatus.REFUND_PROCESSING);
transitionRules.put(PaymentStatus.SUCCESS, successTransitions);
// FAILED 状态的转换规则
Map<PaymentEvent, PaymentStatus> failedTransitions = new EnumMap<>(PaymentEvent.class);
failedTransitions.put(PaymentEvent.CLOSE, PaymentStatus.CLOSED);
transitionRules.put(PaymentStatus.FAILED, failedTransitions);
// REFUND_PROCESSING 状态的转换规则
Map<PaymentEvent, PaymentStatus> refundProcessingTransitions = new EnumMap<>(PaymentEvent.class);
refundProcessingTransitions.put(PaymentEvent.REFUND_SUCCESS, PaymentStatus.REFUND_SUCCESS);
refundProcessingTransitions.put(PaymentEvent.REFUND_FAILED, PaymentStatus.REFUND_FAILED);
transitionRules.put(PaymentStatus.REFUND_PROCESSING, refundProcessingTransitions);
// 终端状态没有转换规则
transitionRules.put(PaymentStatus.CLOSED, Collections.emptyMap());
transitionRules.put(PaymentStatus.REFUND_SUCCESS, Collections.emptyMap());
transitionRules.put(PaymentStatus.REFUND_FAILED, Collections.emptyMap());
}
public Optional<PaymentStatus> getNextStatus(PaymentStatus currentStatus, PaymentEvent event) {
return Optional.ofNullable(transitionRules.get(currentStatus))
.map(transitions -> transitions.get(event));
}
public boolean isValidTransition(PaymentStatus currentStatus, PaymentEvent event) {
return getNextStatus(currentStatus, event).isPresent();
}
public Set<PaymentEvent> getAvailableEvents(PaymentStatus currentStatus) {
return Optional.ofNullable(transitionRules.get(currentStatus))
.map(Map::keySet)
.orElse(Collections.emptySet());
}
}
2. 状态管理器实现
typescript
@Component
@Transactional
public class PaymentStateManager {
private final PaymentStateTransitionConfig transitionConfig;
private final PaymentTransactionRepository transactionRepository;
private final ApplicationEventPublisher eventPublisher;
private final DistributedLockProvider lockProvider;
public PaymentStateManager(PaymentStateTransitionConfig transitionConfig,
PaymentTransactionRepository transactionRepository,
ApplicationEventPublisher eventPublisher,
DistributedLockProvider lockProvider) {
this.transitionConfig = transitionConfig;
this.transactionRepository = transactionRepository;
this.eventPublisher = eventPublisher;
this.lockProvider = lockProvider;
}
public PaymentTransaction transition(String transactionId, PaymentEvent event,
Map<String, Object> contextData) {
// 获取分布式锁,防止并发状态修改
Lock lock = lockProvider.getLock("payment_transition:" + transactionId);
try {
if (lock.tryLock(5, TimeUnit.SECONDS)) {
PaymentTransaction transaction = transactionRepository.findById(transactionId)
.orElseThrow(() -> new IllegalArgumentException("Transaction not found: " + transactionId));
PaymentStatus currentStatus = transaction.getStatus();
PaymentStatus nextStatus = transitionConfig.getNextStatus(currentStatus, event)
.orElseThrow(() -> new IllegalStateException(
"Invalid transition from " + currentStatus + " with event " + event));
// 更新状态
transaction.setStatus(nextStatus);
transaction.setLastUpdated(new Date());
// 保存上下文数据
if (contextData != null) {
transaction.getContextData().putAll(contextData);
}
// 发布状态变更事件
PaymentStatusChangeEvent statusChangeEvent = new PaymentStatusChangeEvent(
transactionId, currentStatus, nextStatus, event, contextData);
eventPublisher.publishEvent(statusChangeEvent);
// 记录状态变更历史
recordStatusHistory(transaction, currentStatus, nextStatus, event, contextData);
return transactionRepository.save(transaction);
} else {
throw new IllegalStateException("Could not acquire lock for transaction: " + transactionId);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Interrupted while acquiring lock", e);
} finally {
lock.unlock();
}
}
public boolean canTransition(String transactionId, PaymentEvent event) {
return transactionRepository.findById(transactionId)
.map(transaction -> transitionConfig.isValidTransition(transaction.getStatus(), event))
.orElse(false);
}
public Set<PaymentEvent> getAvailableEvents(String transactionId) {
return transactionRepository.findById(transactionId)
.map(transaction -> transitionConfig.getAvailableEvents(transaction.getStatus()))
.orElse(Collections.emptySet());
}
private void recordStatusHistory(PaymentTransaction transaction,
PaymentStatus fromStatus,
PaymentStatus toStatus,
PaymentEvent event,
Map<String, Object> contextData) {
// 实现状态历史记录逻辑
PaymentStatusHistory history = new PaymentStatusHistory();
history.setTransactionId(transaction.getId());
history.setFromStatus(fromStatus);
history.setToStatus(toStatus);
history.setEvent(event);
history.setContextData(contextData != null ? new HashMap<>(contextData) : Collections.emptyMap());
history.setChangeTime(new Date());
// 保存历史记录到数据库或发送到消息队列
// historyRepository.save(history);
}
}
// 状态变更事件
public class PaymentStatusChangeEvent {
private final String transactionId;
private final PaymentStatus fromStatus;
private final PaymentStatus toStatus;
private final PaymentEvent event;
private final Map<String, Object> contextData;
private final Date timestamp;
public PaymentStatusChangeEvent(String transactionId, PaymentStatus fromStatus,
PaymentStatus toStatus, PaymentEvent event,
Map<String, Object> contextData) {
this.transactionId = transactionId;
this.fromStatus = fromStatus;
this.toStatus = toStatus;
this.event = event;
this.contextData = contextData != null ? Collections.unmodifiableMap(new HashMap<>(contextData)) : Collections.emptyMap();
this.timestamp = new Date();
}
// getter方法
public String getTransactionId() { return transactionId; }
public PaymentStatus getFromStatus() { return fromStatus; }
public PaymentStatus getToStatus() { return toStatus; }
public PaymentEvent getEvent() { return event; }
public Map<String, Object> getContextData() { return contextData; }
public Date getTimestamp() { return timestamp; }
}
3. 支付交易实体类
typescript
@Entity
@Table(name = "payment_transaction")
public class PaymentTransaction {
@Id
private String id;
@Enumerated(EnumType.STRING)
private PaymentStatus status;
private BigDecimal amount;
private String currency;
private String paymentMethod;
@ElementCollection
@CollectionTable(name = "transaction_context_data",
joinColumns = @JoinColumn(name = "transaction_id"))
@MapKeyColumn(name = "data_key")
@Column(name = "data_value", length = 1000)
private Map<String, String> contextData = new HashMap<>();
@Version
private Long version;
@Temporal(TemporalType.TIMESTAMP)
private Date createdTime;
@Temporal(TemporalType.TIMESTAMP)
private Date lastUpdated;
// 构造函数
public PaymentTransaction() {
this.createdTime = new Date();
this.lastUpdated = new Date();
}
public PaymentTransaction(String id, BigDecimal amount, String currency, String paymentMethod) {
this();
this.id = id;
this.amount = amount;
this.currency = currency;
this.paymentMethod = paymentMethod;
this.status = PaymentStatus.CREATED;
}
// getter和setter方法
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public PaymentStatus getStatus() { return status; }
public void setStatus(PaymentStatus status) { this.status = status; }
public BigDecimal getAmount() { return amount; }
public void setAmount(BigDecimal amount) { this.amount = amount; }
public String getCurrency() { return currency; }
public void setCurrency(String currency) { this.currency = currency; }
public String getPaymentMethod() { return paymentMethod; }
public void setPaymentMethod(String paymentMethod) { this.paymentMethod = paymentMethod; }
public Map<String, String> getContextData() { return contextData; }
public void setContextData(Map<String, String> contextData) { this.contextData = contextData; }
public Long getVersion() { return version; }
public void setVersion(Long version) { this.version = version; }
public Date getCreatedTime() { return createdTime; }
public void setCreatedTime(Date createdTime) { this.createdTime = createdTime; }
public Date getLastUpdated() { return lastUpdated; }
public void setLastUpdated(Date lastUpdated) { this.lastUpdated = lastUpdated; }
}
4. 状态管理服务层
typescript
@Service
@Slf4j
public class PaymentStateService {
private final PaymentStateManager stateManager;
private final PaymentTransactionRepository transactionRepository;
public PaymentStateService(PaymentStateManager stateManager,
PaymentTransactionRepository transactionRepository) {
this.stateManager = stateManager;
this.transactionRepository = transactionRepository;
}
public PaymentTransaction createTransaction(BigDecimal amount, String currency,
String paymentMethod, Map<String, Object> context) {
String transactionId = generateTransactionId();
PaymentTransaction transaction = new PaymentTransaction(transactionId, amount, currency, paymentMethod);
if (context != null) {
// 将context中的值转换为String存储
context.forEach((k, v) -> transaction.getContextData().put(k, v != null ? v.toString() : ""));
}
log.info("Creating new payment transaction: {}", transactionId);
return transactionRepository.save(transaction);
}
public PaymentTransaction processEvent(String transactionId, PaymentEvent event,
Map<String, Object> contextData) {
log.info("Processing event {} for transaction {}", event, transactionId);
return stateManager.transition(transactionId, event, contextData);
}
public PaymentStatus getCurrentStatus(String transactionId) {
return transactionRepository.findById(transactionId)
.map(PaymentTransaction::getStatus)
.orElseThrow(() -> new IllegalArgumentException("Transaction not found: " + transactionId));
}
public boolean validateTransition(String transactionId, PaymentEvent event) {
return stateManager.canTransition(transactionId, event);
}
public Set<PaymentEvent> getAvailableEvents(String transactionId) {
return stateManager.getAvailableEvents(transactionId);
}
public PaymentTransaction getTransaction(String transactionId) {
return transactionRepository.findById(transactionId)
.orElseThrow(() -> new IllegalArgumentException("Transaction not found: " + transactionId));
}
private String generateTransactionId() {
return "TXN" + System.currentTimeMillis() + RandomStringUtils.randomNumeric(6);
}
}
5. 事件监听器
java
@Component
@Slf4j
public class PaymentStatusChangeListener {
private final NotificationService notificationService;
private final MetricsService metricsService;
public PaymentStatusChangeListener(NotificationService notificationService,
MetricsService metricsService) {
this.notificationService = notificationService;
this.metricsService = metricsService;
}
@EventListener
@Async
public void handlePaymentStatusChange(PaymentStatusChangeEvent event) {
log.info("Payment status changed: transactionId={}, from={}, to={}, event={}",
event.getTransactionId(), event.getFromStatus(), event.getToStatus(), event.getEvent());
// 记录指标
metricsService.recordPaymentStatusChange(
event.getTransactionId(),
event.getFromStatus(),
event.getToStatus(),
event.getEvent());
// 根据状态变更执行相应的业务逻辑
switch (event.getToStatus()) {
case SUCCESS:
notificationService.sendPaymentSuccessNotification(event.getTransactionId());
break;
case FAILED:
notificationService.sendPaymentFailedNotification(event.getTransactionId());
break;
case REFUND_SUCCESS:
notificationService.sendRefundSuccessNotification(event.getTransactionId());
break;
case REFUND_FAILED:
notificationService.sendRefundFailedNotification(event.getTransactionId());
break;
default:
// 其他状态不需要特殊处理
break;
}
}
@EventListener
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleAfterCommit(PaymentStatusChangeEvent event) {
// 事务提交后的处理逻辑,如发送消息到消息队列
log.debug("Transaction committed for status change: {}", event.getTransactionId());
}
@EventListener
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
public void handleAfterRollback(PaymentStatusChangeEvent event) {
// 事务回滚后的处理逻辑
log.warn("Transaction rolled back for status change: {}", event.getTransactionId());
}
}
6. 控制器层
less
@RestController
@RequestMapping("/api/payments")
@Validated
@Slf4j
public class PaymentController {
private final PaymentStateService paymentStateService;
public PaymentController(PaymentStateService paymentStateService) {
this.paymentStateService = paymentStateService;
}
@PostMapping
public ResponseEntity<ApiResponse<PaymentTransaction>> createTransaction(
@RequestBody @Valid CreatePaymentRequest request) {
try {
PaymentTransaction transaction = paymentStateService.createTransaction(
request.getAmount(), request.getCurrency(), request.getPaymentMethod(), request.getMetadata());
return ResponseEntity.ok(ApiResponse.success(transaction));
} catch (Exception e) {
log.error("Failed to create payment transaction", e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error("Failed to create payment transaction"));
}
}
@PostMapping("/{transactionId}/events")
public ResponseEntity<ApiResponse<PaymentTransaction>> processEvent(
@PathVariable String transactionId,
@RequestBody @Valid ProcessEventRequest request) {
try {
if (!paymentStateService.validateTransition(transactionId, request.getEvent())) {
return ResponseEntity.badRequest()
.body(ApiResponse.error("Invalid transition for current state"));
}
PaymentTransaction transaction = paymentStateService.processEvent(
transactionId, request.getEvent(), request.getContextData());
return ResponseEntity.ok(ApiResponse.success(transaction));
} catch (IllegalStateException e) {
log.warn("Invalid state transition attempted: {}", e.getMessage());
return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage()));
} catch (Exception e) {
log.error("Failed to process event for transaction: {}", transactionId, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error("Failed to process event"));
}
}
@GetMapping("/{transactionId}/status")
public ResponseEntity<ApiResponse<PaymentStatus>> getStatus(@PathVariable String transactionId) {
try {
PaymentStatus status = paymentStateService.getCurrentStatus(transactionId);
return ResponseEntity.ok(ApiResponse.success(status));
} catch (IllegalArgumentException e) {
return ResponseEntity.notFound().build();
} catch (Exception e) {
log.error("Failed to get status for transaction: {}", transactionId, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error("Failed to get status"));
}
}
@GetMapping("/{transactionId}/available-events")
public ResponseEntity<ApiResponse<Set<PaymentEvent>>> getAvailableEvents(@PathVariable String transactionId) {
try {
Set<PaymentEvent> availableEvents = paymentStateService.getAvailableEvents(transactionId);
return ResponseEntity.ok(ApiResponse.success(availableEvents));
} catch (IllegalArgumentException e) {
return ResponseEntity.notFound().build();
} catch (Exception e) {
log.error("Failed to get available events for transaction: {}", transactionId, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error("Failed to get available events"));
}
}
@GetMapping("/{transactionId}")
public ResponseEntity<ApiResponse<PaymentTransaction>> getTransaction(@PathVariable String transactionId) {
try {
PaymentTransaction transaction = paymentStateService.getTransaction(transactionId);
return ResponseEntity.ok(ApiResponse.success(transaction));
} catch (IllegalArgumentException e) {
return ResponseEntity.notFound().build();
} catch (Exception e) {
log.error("Failed to get transaction: {}", transactionId, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponse.error("Failed to get transaction"));
}
}
}
// 响应DTO类
public class ApiResponse<T> {
private boolean success;
private String message;
private T data;
private ApiResponse(boolean success, String message, T data) {
this.success = success;
this.message = message;
this.data = data;
}
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(true, "Success", data);
}
public static <T> ApiResponse<T> error(String message) {
return new ApiResponse<>(false, message, null);
}
// getter方法
public boolean isSuccess() { return success; }
public String getMessage() { return message; }
public T getData() { return data; }
}
public class CreatePaymentRequest {
@NotNull
@DecimalMin(value = "0.01", message = "Amount must be at least 0.01")
private BigDecimal amount;
@NotBlank
@Size(min = 3, max = 3, message = "Currency must be 3 characters")
private String currency;
@NotBlank
private String paymentMethod;
private Map<String, Object> metadata;
// getter和setter方法
public BigDecimal getAmount() { return amount; }
public void setAmount(BigDecimal amount) { this.amount = amount; }
public String getCurrency() { return currency; }
public void setCurrency(String currency) { this.currency = currency; }
public String getPaymentMethod() { return paymentMethod; }
public void setPaymentMethod(String paymentMethod) { this.paymentMethod = paymentMethod; }
public Map<String, Object> getMetadata() { return metadata; }
public void setMetadata(Map<String, Object> metadata) { this.metadata = metadata; }
}
public class ProcessEventRequest {
@NotNull
private PaymentEvent event;
private Map<String, Object> contextData;
// getter和setter方法
public PaymentEvent getEvent() { return event; }
public void setEvent(PaymentEvent event) { this.event = event; }
public Map<String, Object> getContextData() { return contextData; }
public void setContextData(Map<String, Object> contextData) { this.contextData = contextData; }
}
7.状态机转换配置化
将状态转换规则从硬编码改为配置文件管理可以大大提高系统的灵活性和可维护性。
为了将状态转换规则配置到properties文件中,我们可以将状态转换规则以字符串的形式配置,然后在代码中解析。 例如,我们可以在application.properties或自定义的配置文件中配置状态转换规则,格式可以设计为: payment.state.transition.CREATED=PAY:PROCESSING,CLOSE:CLOSED payment.state.transition.PROCESSING=PAYMENT_SUCCESS:SUCCESS,PAYMENT_FAILED:FAILED ...等等 然后,我们创建一个配置类,使用@ConfigurationProperties来读取这些配置,并解析成我们需要的Map结构。 但是,由于枚举类型在配置中直接映射可能比较复杂,我们可以先以字符串形式读取,再转换为枚举。 另外,我们也可以考虑使用YAML格式的配置文件,因为YAML支持更复杂的数据结构。如以下配置:
yaml
payment:
state:
transitions:
CREATED:
PAY: PROCESSING
CLOSE: CLOSED
PROCESSING:
PAYMENT_SUCCESS: SUCCESS
PAYMENT_FAILED: FAILED
SUCCESS:
REQUEST_REFUND: REFUND_PROCESSING
FAILED:
CLOSE: CLOSED
REFUND_PROCESSING:
REFUND_SUCCESS: REFUND_SUCCESS
REFUND_FAILED: REFUND_FAILED
CLOSED: {}
REFUND_SUCCESS: {}
REFUND_FAILED: {}
总结与优化建议
总结
本文设计并实现了一个轻量级的支付状态机系统,具有以下特点:
- 清晰的状态转换规则:通过配置化的方式定义状态转换规则,易于理解和维护
- 松耦合设计:状态管理器与业务逻辑分离,便于测试和扩展
- 事件驱动架构:通过Spring事件机制实现状态变更的监听和处理
- 事务安全:使用Spring的@Transactional确保状态变更的原子性
- 分布式锁支持:防止并发状态修改导致的数据不一致
- 完整的API设计:提供RESTful API供前端或其他服务调用
- 完善的异常处理:提供清晰的错误信息和适当的HTTP状态码
优化建议
-
引入状态机框架:对于复杂场景,可以考虑使用Spring StateMachine等专业框架
-
状态历史持久化:记录完整的状态变更历史,便于审计和问题排查
-
性能优化:
- 添加缓存层减少数据库访问
- 使用异步处理提高吞吐量
- 批量处理状态变更事件
-
监控与告警:
- 添加状态异常监控和告警机制
- 实现仪表盘可视化展示状态分布和转换情况
-
分布式支持:
- 使用分布式锁确保集群环境下的状态一致性
- 考虑使用消息队列实现跨服务状态同步
-
扩展性设计:
- 为插件化设计预留接口,支持自定义状态转换逻辑
- 实现策略模式,支持不同支付渠道的特殊处理逻辑
-
测试策略:
- 编写全面的单元测试覆盖所有状态转换路径
- 实现集成测试验证端到端流程
- 进行压力测试确保系统在高并发下的稳定性
-
文档与工具:
- 提供状态转换图的自动生成工具
- 实现API文档自动生成(如Swagger)
- 开发管理界面用于手动状态干预和查询
通过以上设计和优化,可以构建一个健壮、可扩展的支付状态管理系统,为支付业务提供可靠的基础支撑。