feat: add order idempotency
This commit is contained in:
@@ -9,6 +9,8 @@ type OrderCreateForm struct {
|
||||
Quantity int `json:"quantity"`
|
||||
// UserCouponID 用户券ID(可选)。
|
||||
UserCouponID int64 `json:"user_coupon_id"`
|
||||
// IdempotencyKey 幂等键(同一业务请求需保持一致)。
|
||||
IdempotencyKey *string `json:"idempotency_key"`
|
||||
}
|
||||
|
||||
type OrderCreateResponse struct {
|
||||
|
||||
@@ -3,6 +3,7 @@ package services
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"quyun/v2/app/errorx"
|
||||
@@ -78,6 +79,22 @@ func (s *order) Create(
|
||||
uid := userID
|
||||
cid := form.ContentID
|
||||
|
||||
// 幂等控制:相同幂等键直接返回已创建的订单。
|
||||
idempotencyKey := ""
|
||||
if form.IdempotencyKey != nil {
|
||||
idempotencyKey = strings.TrimSpace(*form.IdempotencyKey)
|
||||
}
|
||||
if idempotencyKey != "" {
|
||||
tbl, q := models.OrderQuery.QueryContext(ctx)
|
||||
existing, err := q.Where(tbl.UserID.Eq(uid), tbl.IdempotencyKey.Eq(idempotencyKey)).First()
|
||||
if err == nil {
|
||||
return &transaction_dto.OrderCreateResponse{OrderID: existing.ID}, nil
|
||||
}
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Fetch Content & Price
|
||||
content, err := models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.ID.Eq(cid)).First()
|
||||
if err != nil {
|
||||
@@ -123,9 +140,12 @@ func (s *order) Create(
|
||||
AmountDiscount: amountDiscount,
|
||||
AmountPaid: amountPaid,
|
||||
CouponID: couponID,
|
||||
IdempotencyKey: uuid.NewString(),
|
||||
IdempotencyKey: idempotencyKey,
|
||||
Snapshot: types.NewJSONType(fields.OrdersSnapshot{}),
|
||||
}
|
||||
if order.IdempotencyKey == "" {
|
||||
order.IdempotencyKey = uuid.NewString()
|
||||
}
|
||||
|
||||
err = models.Q.Transaction(func(tx *models.Query) error {
|
||||
if err := tx.Order.WithContext(ctx).Create(order); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user