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: {}, }, }