132 lines
4.2 KiB
Go
132 lines
4.2 KiB
Go
package service
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"time"
|
|
|
|
"affiliate_dash/internal/model"
|
|
)
|
|
|
|
type fulfillmentTransitionKind string
|
|
|
|
const (
|
|
fulfillmentTransitionUpdate fulfillmentTransitionKind = "update"
|
|
fulfillmentTransitionShipNotify fulfillmentTransitionKind = "ship_notify"
|
|
fulfillmentTransitionTimeout fulfillmentTransitionKind = "timeout"
|
|
)
|
|
|
|
type orderRequestFingerprintPayload struct {
|
|
ClientOrderNo string `json:"client_order_no"`
|
|
SKU string `json:"sku"`
|
|
Quantity int64 `json:"quantity"`
|
|
BuyerReference string `json:"buyer_reference"`
|
|
RequestData string `json:"request_data"`
|
|
}
|
|
|
|
func orderRequestFingerprint(in CreateFulfillmentOrderInput, requestData string) string {
|
|
raw, _ := json.Marshal(orderRequestFingerprintPayload{
|
|
ClientOrderNo: in.ClientOrderNo,
|
|
SKU: in.SKU,
|
|
Quantity: in.Quantity,
|
|
BuyerReference: in.BuyerReference,
|
|
RequestData: requestData,
|
|
})
|
|
sum := sha256.Sum256(raw)
|
|
return hex.EncodeToString(sum[:])
|
|
}
|
|
|
|
func storedOrderRequestFingerprint(order *model.FulfillmentOrder) string {
|
|
if order == nil {
|
|
return ""
|
|
}
|
|
if order.RequestFingerprint != "" {
|
|
return order.RequestFingerprint
|
|
}
|
|
return orderRequestFingerprint(CreateFulfillmentOrderInput{
|
|
ClientOrderNo: order.ClientOrderNo,
|
|
SKU: order.ProductSKU,
|
|
Quantity: order.Quantity,
|
|
BuyerReference: order.BuyerReference,
|
|
}, order.RequestData)
|
|
}
|
|
|
|
func ensureSameIdempotentOrder(existing *model.FulfillmentOrder, fingerprint string) error {
|
|
if existing == nil {
|
|
return errors.New("订单不存在")
|
|
}
|
|
if storedOrderRequestFingerprint(existing) != fingerprint {
|
|
return errors.New("client_order_no 已存在,但本次请求参数与原订单不一致")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateFulfillmentTransition(order *model.FulfillmentOrder, next string, kind fulfillmentTransitionKind) error {
|
|
if order == nil {
|
|
return errors.New("订单不存在")
|
|
}
|
|
if order.PaymentStatus != model.PaymentStatusPaid {
|
|
return errors.New("订单未支付,不能履约")
|
|
}
|
|
if order.FulfillmentStatus == next {
|
|
return nil
|
|
}
|
|
switch order.FulfillmentStatus {
|
|
case model.FulfillmentStatusCancelled:
|
|
return errors.New("订单已取消,不能更新履约状态")
|
|
case model.FulfillmentStatusSucceeded:
|
|
return errors.New("订单已履约成功,不能回退状态")
|
|
}
|
|
switch next {
|
|
case model.FulfillmentStatusProcessing:
|
|
if order.FulfillmentStatus == model.FulfillmentStatusPending || order.FulfillmentStatus == model.FulfillmentStatusFailed {
|
|
return nil
|
|
}
|
|
case model.FulfillmentStatusSucceeded:
|
|
if order.FulfillmentStatus == model.FulfillmentStatusPending ||
|
|
order.FulfillmentStatus == model.FulfillmentStatusFailed ||
|
|
order.FulfillmentStatus == model.FulfillmentStatusProcessing {
|
|
return nil
|
|
}
|
|
case model.FulfillmentStatusFailed:
|
|
if kind == fulfillmentTransitionTimeout && order.FulfillmentStatus != model.FulfillmentStatusProcessing {
|
|
return errors.New("只有履约中的订单可以标记超时")
|
|
}
|
|
if order.FulfillmentStatus == model.FulfillmentStatusPending ||
|
|
order.FulfillmentStatus == model.FulfillmentStatusFailed ||
|
|
order.FulfillmentStatus == model.FulfillmentStatusProcessing {
|
|
return nil
|
|
}
|
|
}
|
|
return errors.New("订单当前状态不允许该履约变更")
|
|
}
|
|
|
|
func canCancelOrder(order *model.FulfillmentOrder) error {
|
|
if order == nil {
|
|
return errors.New("订单不存在")
|
|
}
|
|
if order.FulfillmentStatus == model.FulfillmentStatusCancelled {
|
|
return nil
|
|
}
|
|
if order.PaymentStatus != model.PaymentStatusPaid {
|
|
return errors.New("订单未支付或已退款,不能取消")
|
|
}
|
|
switch order.FulfillmentStatus {
|
|
case model.FulfillmentStatusPending, model.FulfillmentStatusFailed:
|
|
return nil
|
|
case model.FulfillmentStatusProcessing, model.FulfillmentStatusSucceeded:
|
|
return errors.New("订单已进入履约流程,不能取消")
|
|
default:
|
|
return errors.New("订单当前状态不能取消")
|
|
}
|
|
}
|
|
|
|
func processingTimedOut(order *model.FulfillmentOrder, timeout time.Duration, now time.Time) bool {
|
|
if order == nil || timeout <= 0 {
|
|
return false
|
|
}
|
|
return order.FulfillmentStatus == model.FulfillmentStatusProcessing && order.UpdatedAt.Before(now.Add(-timeout))
|
|
}
|