75 lines
2.3 KiB
Go
75 lines
2.3 KiB
Go
package domain
|
|
|
|
import (
|
|
"errors"
|
|
"time"
|
|
)
|
|
|
|
var ErrInvalidAttemptTransition = errors.New("invalid purchase attempt status transition")
|
|
|
|
// AttemptStatus 只描述单趟领取的可恢复执行。真实提交结果独立由唯一围栏记录调和。
|
|
type AttemptStatus string
|
|
|
|
const (
|
|
AttemptStatusClaimed AttemptStatus = "CLAIMED"
|
|
AttemptStatusOrdering AttemptStatus = "ORDERING"
|
|
AttemptStatusFailed AttemptStatus = "FAILED"
|
|
AttemptStatusFenced AttemptStatus = "FENCED"
|
|
AttemptStatusAbandoned AttemptStatus = "ABANDONED"
|
|
)
|
|
|
|
// AttemptFailureCode 是服务端可审计的固定失败摘要,不能承载页面正文或其他自由文本。
|
|
type AttemptFailureCode string
|
|
|
|
const (
|
|
AttemptFailureAuthorizationExpired AttemptFailureCode = "AUTHORIZATION_EXPIRED"
|
|
AttemptFailureLeaseLost AttemptFailureCode = "LEASE_LOST"
|
|
AttemptFailureGate1Rejected AttemptFailureCode = "GATE_1_REJECTED"
|
|
AttemptFailureQuantityMismatch AttemptFailureCode = "QUANTITY_MISMATCH"
|
|
AttemptFailureGate2Rejected AttemptFailureCode = "GATE_2_REJECTED"
|
|
AttemptFailureGate3Rejected AttemptFailureCode = "GATE_3_REJECTED"
|
|
AttemptFailureFenceRejected AttemptFailureCode = "FENCE_REJECTED"
|
|
AttemptFailureSafeAborted AttemptFailureCode = "SAFE_ABORTED"
|
|
)
|
|
|
|
type PurchaseAttempt struct {
|
|
ID string
|
|
TaskID string
|
|
AuthorizationID string
|
|
ClaimGeneration int
|
|
Status AttemptStatus
|
|
Gate1UnitPrice *string
|
|
Gate2UnitPrice *string
|
|
QuantityRead *int
|
|
ConfirmAmount *string
|
|
FailureCode *AttemptFailureCode
|
|
StartedAt time.Time
|
|
FinishedAt *time.Time
|
|
}
|
|
|
|
// CanTransitionTo 只允许围栏前的领取恢复为安全失败;围栏后不再提供回退或重试路径。
|
|
func (status AttemptStatus) CanTransitionTo(next AttemptStatus) bool {
|
|
_, allowed := attemptTransitions[status][next]
|
|
return allowed
|
|
}
|
|
|
|
func TransitionAttempt(current, next AttemptStatus) (AttemptStatus, error) {
|
|
if !current.CanTransitionTo(next) {
|
|
return current, ErrInvalidAttemptTransition
|
|
}
|
|
return next, nil
|
|
}
|
|
|
|
var attemptTransitions = map[AttemptStatus]map[AttemptStatus]struct{}{
|
|
AttemptStatusClaimed: {
|
|
AttemptStatusOrdering: {},
|
|
AttemptStatusFailed: {},
|
|
AttemptStatusAbandoned: {},
|
|
},
|
|
AttemptStatusOrdering: {
|
|
AttemptStatusFenced: {},
|
|
AttemptStatusFailed: {},
|
|
AttemptStatusAbandoned: {},
|
|
},
|
|
}
|