接上一篇内容, 来实现一个简单的Payment Proxy + 有限状态机的DEMO。建议先查看上一篇内容再来看这篇文章。
状态机设计
State定义
public enum PaymentOrderStates {
CREATED(0,"订单已创建"),
WAIT_PAYMENT(1,"订单待支付"),
PAYMENT_SUCCESS(2,"支付成功"),
PAYMENT_CANCELED(3,"支付取消"),
PAYMENT_FAILED(4,"支付失败"),
WAIT_REFUND(5,"等待退款"),
REFUND_SUCCESS(6,"退款成功"),
REFUND_FAILED(7,"退款失败"),
FINISHED(8,"订单完成"),
EXCEPTION(9,"订单异常"),
CLOSED(10,"订单关闭");
private int code;
private String value;
PaymentOrderStates(int code, String value) {
this.code = code;
this.value = value;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
Event定义
State 由事件触发,引起变化。
public enum PaymentOrderEvents {
//创建订单
CREATE_ORDER,
//创建交易
CREATE_TRANSACTION,
//正在支付
PAYING,
//正在退款
REFUNDING,
//失败
FAIL,
//确认
CONFIRM,
//取消
CANCEL
}
大家一定要记住,这个是事件的定义。
状态机定义
代码中我已经详细的标注了不同事件对应的不同状态的变化。
@Configuration
public class StateMachineConfiguration {
@Bean
public StateMachine<PaymentOrderStates, PaymentOrderEvents, OrderContext> paymentOrderStateMachine(PaymentOrderStateMachineService orderService) {
StateMachineBuilder<PaymentOrderStates, PaymentOrderEvents, OrderContext> builder = StateMachineBuilderFactory.create();
//订单创建
builder.internalTransition()
.within(PaymentOrderStates.CREATED)
.on(PaymentOrderEvents.CREATE_ORDER)
.perform(((from, to, event, ctx) -> orderService.doActionCreate(from,to,event,ctx)));
//订单创建 =》 待支付
builder.externalTransition()
.from(PaymentOrderStates.CREATED)
.to(PaymentOrderStates.WAIT_PAYMENT)
.on(PaymentOrderEvents.CREATE_TRANSACTION)
.perform(((from, to, event, ctx) -> orderService.doActionUpdate(from,to,event,ctx)));
//待支付 =》 支付成功
builder.externalTransition()
.from(PaymentOrderStates.WAIT_PAYMENT)
.to(PaymentOrderStates.PAYMENT_SUCCESS)
.on(PaymentOrderEvents.PAYING)
.perform(((from, to, event, ctx) -> orderService.doActionUpdate(from,to,event,ctx)));
//待支付 =》 支付失败
builder.externalTransition()
.from(PaymentOrderStates.WAIT_PAYMENT)
.to(PaymentOrderStates.PAYMENT_FAILED)
.on(PaymentOrderEvents.FAIL)
.perform(((from, to, event, ctx) -> orderService.doActionUpdate(from,to,event,ctx)));
//待支付 =》 取消支付
builder.externalTransition()
.from(PaymentOrderStates.WAIT_PAYMENT)
.to(PaymentOrderStates.PAYMENT_CANCELED)
.on(PaymentOrderEvents.CANCEL)
.perform(((from, to, event, ctx) -> orderService.doActionUpdate(from,to,event,ctx)));
//支付成功 =》 订单完成
builder.externalTransition()
.from(PaymentOrderStates.PAYMENT_SUCCESS)
.to(PaymentOrderStates.FINISHED)
.on(PaymentOrderEvents.CONFIRM)
.perform(((from, to, event, ctx) -> orderService.doActionUpdate(from,to,event,ctx)));
//订单取消 =》 订单关闭
builder.externalTransition()
.from(PaymentOrderStates.PAYMENT_CANCELED)
.to(PaymentOrderStates.CLOSED)
.on(PaymentOrderEvents.CONFIRM)
.perform(((from, to, event, ctx) -> orderService.doActionUpdate(from,to,event,ctx)));
//支付失败 =》 订单异常
builder.externalTransition()
.from(PaymentOrderStates.PAYMENT_FAILED)
.to(PaymentOrderStates.EXCEPTION)
.on(PaymentOrderEvents.CONFIRM)
.perform(((from, to, event, ctx) -> orderService.doActionUpdate(from,to,event,ctx)));
StateMachine<PaymentOrderStates, PaymentOrderEvents, OrderContext> paymentOrderStateMachine = builder.build("paymentOrderStateMachine");
return paymentOrderStateMachine;
}
}
状态机上下文
这里为了省事,我直接继承了订单表的映射类。
状态上下文
public class OrderContext extends PaymentProxyOrder {
}
数据库订单表映射类
@TableName(value ="payment_proxy_order")
@Data
public class PaymentProxyOrder implements Serializable {
/**
* 自增主键ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 订单号
*/
private String payOrderNo;
/**
* 支付状态 与状态机匹配
*/
private Integer payStatus;
/**
* 金额
*/
private String amount;
/**
* 币种
*/
private Integer currency;
/**
* 交易类型
*/
private Integer transType;
/**
* 支付方法
*/
private Integer payMethod;
/**
* 支付来源 APP 或者H5
*/
private Integer paySource;
/**
* 交易业务号
*/
private String transactionNo;
/**
* 业务号 用于业务之间的绑定
*/
private String businessNo;
/**
* 第三方返回的number, 比如MPGS会返回一个字符串作为支付状态校验
*/
private String thirdPartyNo;
/**
* 传给第三方的字符串 用业务mapping
*/
private String thirdPartyCustomerNo;
/**
* 支付成功实践
*/
private Date payDate;
/**
* 业务删除 Is delete Y:yes N:no
*/
private String isDeleted;
/**
* Creator
*/
private String creator;
/**
* created time
*/
private Date gmtCreated;
/**
* Modifier
*/
private String modifier;
/**
* modified time
*/
private Date gmtModified;
/**
* 备注
*/
private String remark;
/**
* 扩展字段
*/
private String extraInfo;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
@Override
public boolean equals(Object that) {
if (this == that) {
return true;
}
if (that == null) {
return false;
}
if (getClass() != that.getClass()) {
return false;
}
PaymentProxyOrder other = (PaymentProxyOrder) that;
return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
&& (this.getPayOrderNo() == null ? other.getPayOrderNo() == null : this.getPayOrderNo().equals(other.getPayOrderNo()))
&& (this.getPayStatus() == null ? other.getPayStatus() == null : this.getPayStatus().equals(other.getPayStatus()))
&& (this.getAmount() == null ? other.getAmount() == null : this.getAmount().equals(other.getAmount()))
&& (this.getCurrency() == null ? other.getCurrency() == null : this.getCurrency().equals(other.getCurrency()))
&& (this.getTransType() == null ? other.getTransType() == null : this.getTransType().equals(other.getTransType()))
&& (this.getPayMethod() == null ? other.getPayMethod() == null : this.getPayMethod().equals(other.getPayMethod()))
&& (this.getPaySource() == null ? other.getPaySource() == null : this.getPaySource().equals(other.getPaySource()))
&& (this.getTransactionNo() == null ? other.getTransactionNo() == null : this.getTransactionNo().equals(other.getTransactionNo()))
&& (this.getBusinessNo() == null ? other.getBusinessNo() == null : this.getBusinessNo().equals(other.getBusinessNo()))
&& (this.getThirdPartyNo() == null ? other.getThirdPartyNo() == null : this.getThirdPartyNo().equals(other.getThirdPartyNo()))
&& (this.getThirdPartyCustomerNo() == null ? other.getThirdPartyCustomerNo() == null : this.getThirdPartyCustomerNo().equals(other.getThirdPartyCustomerNo()))
&& (this.getPayDate() == null ? other.getPayDate() == null : this.getPayDate().equals(other.getPayDate()))
&& (this.getIsDeleted() == null ? other.getIsDeleted() == null : this.getIsDeleted().equals(other.getIsDeleted()))
&& (this.getCreator() == null ? other.getCreator() == null : this.getCreator().equals(other.getCreator()))
&& (this.getGmtCreated() == null ? other.getGmtCreated() == null : this.getGmtCreated().equals(other.getGmtCreated()))
&& (this.getModifier() == null ? other.getModifier() == null : this.getModifier().equals(other.getModifier()))
&& (this.getGmtModified() == null ? other.getGmtModified() == null : this.getGmtModified().equals(other.getGmtModified()))
&& (this.getRemark() == null ? other.getRemark() == null : this.getRemark().equals(other.getRemark()))
&& (this.getExtraInfo() == null ? other.getExtraInfo() == null : this.getExtraInfo().equals(other.getExtraInfo()));
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
result = prime * result + ((getPayOrderNo() == null) ? 0 : getPayOrderNo().hashCode());
result = prime * result + ((getPayStatus() == null) ? 0 : getPayStatus().hashCode());
result = prime * result + ((getAmount() == null) ? 0 : getAmount().hashCode());
result = prime * result + ((getCurrency() == null) ? 0 : getCurrency().hashCode());
result = prime * result + ((getTransType() == null) ? 0 : getTransType().hashCode());
result = prime * result + ((getPayMethod() == null) ? 0 : getPayMethod().hashCode());
result = prime * result + ((getPaySource() == null) ? 0 : getPaySource().hashCode());
result = prime * result + ((getTransactionNo() == null) ? 0 : getTransactionNo().hashCode());
result = prime * result + ((getBusinessNo() == null) ? 0 : getBusinessNo().hashCode());
result = prime * result + ((getThirdPartyNo() == null) ? 0 : getThirdPartyNo().hashCode());
result = prime * result + ((getThirdPartyCustomerNo() == null) ? 0 : getThirdPartyCustomerNo().hashCode());
result = prime * result + ((getPayDate() == null) ? 0 : getPayDate().hashCode());
result = prime * result + ((getIsDeleted() == null) ? 0 : getIsDeleted().hashCode());
result = prime * result + ((getCreator() == null) ? 0 : getCreator().hashCode());
result = prime * result + ((getGmtCreated() == null) ? 0 : getGmtCreated().hashCode());
result = prime * result + ((getModifier() == null) ? 0 : getModifier().hashCode());
result = prime * result + ((getGmtModified() == null) ? 0 : getGmtModified().hashCode());
result = prime * result + ((getRemark() == null) ? 0 : getRemark().hashCode());
result = prime * result + ((getExtraInfo() == null) ? 0 : getExtraInfo().hashCode());
return result;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(" [");
sb.append("Hash = ").append(hashCode());
sb.append(", id=").append(id);
sb.append(", payOrderNo=").append(payOrderNo);
sb.append(", payStatus=").append(payStatus);
sb.append(", amount=").append(amount);
sb.append(", currency=").append(currency);
sb.append(", transType=").append(transType);
sb.append(", payMethod=").append(payMethod);
sb.append(", paySource=").append(paySource);
sb.append(", transactionNo=").append(transactionNo);
sb.append(", businessNo=").append(businessNo);
sb.append(", thirdPartyNo=").append(thirdPartyNo);
sb.append(", thirdPartyCustomerNo=").append(thirdPartyCustomerNo);
sb.append(", payDate=").append(payDate);
sb.append(", isDeleted=").append(isDeleted);
sb.append(", creator=").append(creator);
sb.append(", gmtCreated=").append(gmtCreated);
sb.append(", modifier=").append(modifier);
sb.append(", gmtModified=").append(gmtModified);
sb.append(", remark=").append(remark);
sb.append(", extraInfo=").append(extraInfo);
sb.append(", serialVersionUID=").append(serialVersionUID);
sb.append("]");
return sb.toString();
}
}
状态持久化层
@Service
public class PaymentOrderStateMachineService {
//订单表持久化层
private final PaymentProxyOrderService proxyOrderService;
//订单本地缓存
private final Cache<String, PaymentOrderStates> orderStateCache = CacheBuilder.newBuilder().expireAfterWrite(15, TimeUnit.MINUTES).build();
public PaymentOrderStateMachineService(PaymentProxyOrderService proxyOrderService) {
this.proxyOrderService = proxyOrderService;
}
//创建订单
public void doActionCreate(PaymentOrderStates from, PaymentOrderStates to, PaymentOrderEvents event, OrderContext ctx) {
//当订单创建成功 将下一个状态放入缓存
if(proxyOrderService.save(ctx)) {
orderStateCache.put(ctx.getPayOrderNo(),to);
}
}
public void doActionUpdate(PaymentOrderStates from, PaymentOrderStates to, PaymentOrderEvents event, OrderContext ctx) {
//如果 当前订单状态与 状态机from 状态一致, 则更新初始化表. 证明订单状态改变并且持久化到数据库中
if(null != orderStateCache.getIfPresent(ctx.getPayOrderNo())
&& from == orderStateCache.getIfPresent(ctx.getPayOrderNo())) {
proxyOrderService.updateById(ctx);
}
}
}
这里我先暂时不贴对应的Mybatis Plus 对应的代码, 简单的将判断逻辑写在了注释里。 这里重要关注一下, 由于我们可能会多次请求第三方接口查询订单状态进而改变我们自己维护的订单状态,为了避免频繁更新数据库, 我这里做了缓存处理,暂时跳过订单状态重复更新的情况。
第三方支付状态与状态机匹配
由于我们需要根据第三方的支付状态来改变我们自己的订单状态。因此需要一种思路去匹配。 这里我只暂时给出自己在项目中的实现思路。
假设请求第三方查询支付状态接口中 有个字段为Status 并且他们对应的值解释为:
Status | 对应状态 |
---|---|
1 | 正在支付 |
2 | 正在支付 |
3 | 支付成功 |
4 | 支付失败 |
5 | 支付取消 |
那么我们可以根据第三方查询接口来定义一个枚举类。 其中包括了第三方接口返回来的status值, 本地服务订单的状态和事件类别。这样可以更快的返回来实现第三方与本地的订单的状态匹配。
public enum ThirdPartyPayStatusCodeEnum {
CREATE(1,
"Pending,transaction created",
PaymentOrderStates.CREATED,
PaymentOrderStates.WAIT_PAYMENT,
PaymentOrderEvents.CREATE_TRANSACTION),
PENDING_PAYMENT(2,
"Pending,Waiting for user action",
PaymentOrderStates.CREATED,
PaymentOrderStates.WAIT_PAYMENT,
PaymentOrderEvents.CREATE_TRANSACTION),
SUCCESS(3,
"Success, payment success",
PaymentOrderStates.WAIT_PAYMENT,
PaymentOrderStates.PAYMENT_SUCCESS,
PaymentOrderEvents.PAYING),
FAIL(4, "Fail, general payment fail",
PaymentOrderStates.WAIT_PAYMENT,
PaymentOrderStates.PAYMENT_FAILED,
PaymentOrderEvents.FAIL),
CANCEL(5, "Cancel, cancel before pay or the end gateway support cancel paid transaction directly",
PaymentOrderStates.WAIT_PAYMENT,
PaymentOrderStates.PAYMENT_CANCELED,
PaymentOrderEvents.CANCEL);
//第三方Status 值
private int code;
//对应含义
private String meaning;
//对应状态机的From 状态
private PaymentOrderStates fromOrderStates;
//对应状态机要改变的状态
private PaymentOrderStates toOrderStates;
//该状态机需要改变的事件
private PaymentOrderEvents orderEvents;
ThirdPartyPayStatusCodeEnum(int code,
String meaning,
PaymentOrderStates fromOrderStates,
PaymentOrderStates toOrderStates,
PaymentOrderEvents orderEvents) {
this.code = code;
this.meaning = meaning;
this.fromOrderStates = fromOrderStates;
this.toOrderStates = toOrderStates;
this.orderEvents = orderEvents;
}
public int getCode() {
return code;
}
public String getMeaning() {
return meaning;
}
public PaymentOrderStates getFromOrderStates() {
return fromOrderStates;
}
public PaymentOrderStates getToOrderStates() {
return toOrderStates;
}
public PaymentOrderEvents getOrderEvents() {
return orderEvents;
}
/**
* @author Neal
* @description 根据第三方 status code返回枚举
* @date 2023/11/15
* @param code:
* @return ThirdPartyPayStatusCodeEnum
*/
public static ThirdPartyPayStatusCodeEnum getOrderStatus(int code) {
for (ThirdPartyPayStatusCodeEnum value : ThirdPartyPayStatusCodeEnum.values()) {
if(value.code == code) {
return value;
}
}
return null;
}
}
小结
今天简单阐述了订单状态机的demo 代码。 由于代码比较多, 可能比较乱,但是只要缕清思路,很好理解。现在我们自己的订单状态机已经初步成型,下一步就是基于Spring Cloud Gateway来实现第三方的支付对接和扩展。
网友评论