feat: 添加用户优惠券列表接口及相关数据结构
This commit is contained in:
124
api-spec.yaml
124
api-spec.yaml
@@ -286,6 +286,32 @@ components:
|
|||||||
realname:
|
realname:
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
|
UserCouponItem:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
coupon_id:
|
||||||
|
type: string
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
enum: [fix_amount, discount]
|
||||||
|
value:
|
||||||
|
type: integer
|
||||||
|
min_order_amount:
|
||||||
|
type: integer
|
||||||
|
start_at:
|
||||||
|
type: string
|
||||||
|
end_at:
|
||||||
|
type: string
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
enum: [unused, used, expired]
|
||||||
|
|
||||||
# --- Upload ---
|
# --- Upload ---
|
||||||
UploadResult:
|
UploadResult:
|
||||||
type: object
|
type: object
|
||||||
@@ -787,6 +813,84 @@ paths:
|
|||||||
time:
|
time:
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
|
/me/coupons:
|
||||||
|
get:
|
||||||
|
summary: List user coupons
|
||||||
|
parameters:
|
||||||
|
- name: status
|
||||||
|
in: query
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
enum: [unused, used, expired]
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/UserCouponItem'
|
||||||
|
|
||||||
|
# ============================
|
||||||
|
# Storage (Presigned)
|
||||||
|
# ============================
|
||||||
|
/storage/{key}:
|
||||||
|
put:
|
||||||
|
summary: Upload file (Presigned)
|
||||||
|
tags: [Storage]
|
||||||
|
parameters:
|
||||||
|
- name: key
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: expires
|
||||||
|
in: query
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: sign
|
||||||
|
in: query
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/octet-stream:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Upload successful
|
||||||
|
get:
|
||||||
|
summary: Download file (Presigned)
|
||||||
|
tags: [Storage]
|
||||||
|
parameters:
|
||||||
|
- name: key
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: expires
|
||||||
|
in: query
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: sign
|
||||||
|
in: query
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Download file
|
||||||
|
content:
|
||||||
|
application/octet-stream:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
|
|
||||||
# ============================
|
# ============================
|
||||||
# Transaction
|
# Transaction
|
||||||
# ============================
|
# ============================
|
||||||
@@ -806,6 +910,8 @@ paths:
|
|||||||
quantity:
|
quantity:
|
||||||
type: integer
|
type: integer
|
||||||
default: 1
|
default: 1
|
||||||
|
user_coupon_id:
|
||||||
|
type: string
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
content:
|
content:
|
||||||
@@ -864,6 +970,24 @@ paths:
|
|||||||
type: string
|
type: string
|
||||||
enum: [unpaid, paid, completed]
|
enum: [unpaid, paid, completed]
|
||||||
|
|
||||||
|
/webhook/payment/notify:
|
||||||
|
post:
|
||||||
|
summary: Payment Webhook
|
||||||
|
tags: [Transaction]
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
order_id:
|
||||||
|
type: string
|
||||||
|
external_id:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Success
|
||||||
|
|
||||||
# ============================
|
# ============================
|
||||||
# Creator Center
|
# Creator Center
|
||||||
# ============================
|
# ============================
|
||||||
|
|||||||
14
backend/app/http/v1/dto/coupon.go
Normal file
14
backend/app/http/v1/dto/coupon.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
type UserCouponItem struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
CouponID string `json:"coupon_id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Value int64 `json:"value"`
|
||||||
|
MinOrderAmount int64 `json:"min_order_amount"`
|
||||||
|
StartAt string `json:"start_at"`
|
||||||
|
EndAt string `json:"end_at"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
@@ -237,6 +237,11 @@ func (r *Routes) Register(router fiber.Router) {
|
|||||||
router.Get("/v1/me"[len(r.Path()):], DataFunc0(
|
router.Get("/v1/me"[len(r.Path()):], DataFunc0(
|
||||||
r.user.Me,
|
r.user.Me,
|
||||||
))
|
))
|
||||||
|
r.log.Debugf("Registering route: Get /v1/me/coupons -> user.MyCoupons")
|
||||||
|
router.Get("/v1/me/coupons"[len(r.Path()):], DataFunc1(
|
||||||
|
r.user.MyCoupons,
|
||||||
|
QueryParam[string]("status"),
|
||||||
|
))
|
||||||
r.log.Debugf("Registering route: Get /v1/me/favorites -> user.Favorites")
|
r.log.Debugf("Registering route: Get /v1/me/favorites -> user.Favorites")
|
||||||
router.Get("/v1/me/favorites"[len(r.Path()):], DataFunc0(
|
router.Get("/v1/me/favorites"[len(r.Path()):], DataFunc0(
|
||||||
r.user.Favorites,
|
r.user.Favorites,
|
||||||
|
|||||||
@@ -241,3 +241,18 @@ func (u *User) Following(ctx fiber.Ctx) ([]dto.TenantProfile, error) {
|
|||||||
func (u *User) Notifications(ctx fiber.Ctx, typeArg string, page int) (*requests.Pager, error) {
|
func (u *User) Notifications(ctx fiber.Ctx, typeArg string, page int) (*requests.Pager, error) {
|
||||||
return services.Notification.List(ctx.Context(), page, typeArg)
|
return services.Notification.List(ctx.Context(), page, typeArg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// List my coupons
|
||||||
|
//
|
||||||
|
// @Router /v1/me/coupons [get]
|
||||||
|
// @Summary List coupons
|
||||||
|
// @Description List my coupons
|
||||||
|
// @Tags UserCenter
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param status query string false "Status (unused, used, expired)"
|
||||||
|
// @Success 200 {array} dto.UserCouponItem
|
||||||
|
// @Bind status query
|
||||||
|
func (u *User) MyCoupons(ctx fiber.Ctx, status string) ([]dto.UserCouponItem, error) {
|
||||||
|
return services.Coupon.ListUserCoupons(ctx.Context(), status)
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,12 +5,61 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"quyun/v2/app/errorx"
|
"quyun/v2/app/errorx"
|
||||||
|
coupon_dto "quyun/v2/app/http/v1/dto"
|
||||||
"quyun/v2/database/models"
|
"quyun/v2/database/models"
|
||||||
|
"quyun/v2/pkg/consts"
|
||||||
|
|
||||||
|
"github.com/spf13/cast"
|
||||||
)
|
)
|
||||||
|
|
||||||
// @provider
|
// @provider
|
||||||
type coupon struct{}
|
type coupon struct{}
|
||||||
|
|
||||||
|
func (s *coupon) ListUserCoupons(ctx context.Context, status string) ([]coupon_dto.UserCouponItem, error) {
|
||||||
|
userID := ctx.Value(consts.CtxKeyUser)
|
||||||
|
if userID == nil {
|
||||||
|
return nil, errorx.ErrUnauthorized
|
||||||
|
}
|
||||||
|
uid := cast.ToInt64(userID)
|
||||||
|
|
||||||
|
tbl, q := models.UserCouponQuery.QueryContext(ctx)
|
||||||
|
q = q.Where(tbl.UserID.Eq(uid))
|
||||||
|
if status != "" {
|
||||||
|
q = q.Where(tbl.Status.Eq(status))
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := q.Order(tbl.CreatedAt.Desc()).Find()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []coupon_dto.UserCouponItem
|
||||||
|
for _, v := range list {
|
||||||
|
c, _ := models.CouponQuery.WithContext(ctx).Where(models.CouponQuery.ID.Eq(v.CouponID)).First()
|
||||||
|
|
||||||
|
item := coupon_dto.UserCouponItem{
|
||||||
|
ID: cast.ToString(v.ID),
|
||||||
|
CouponID: cast.ToString(v.CouponID),
|
||||||
|
Status: v.Status,
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
item.Title = c.Title
|
||||||
|
item.Description = c.Description
|
||||||
|
item.Type = c.Type
|
||||||
|
item.Value = c.Value
|
||||||
|
item.MinOrderAmount = c.MinOrderAmount
|
||||||
|
if !c.StartAt.IsZero() {
|
||||||
|
item.StartAt = c.StartAt.Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
if !c.EndAt.IsZero() {
|
||||||
|
item.EndAt = c.EndAt.Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res = append(res, item)
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Validate checks if a coupon can be used for an order and returns the discount amount
|
// Validate checks if a coupon can be used for an order and returns the discount amount
|
||||||
func (s *coupon) Validate(ctx context.Context, userID, userCouponID, amount int64) (int64, error) {
|
func (s *coupon) Validate(ctx context.Context, userID, userCouponID, amount int64) (int64, error) {
|
||||||
uc, err := models.UserCouponQuery.WithContext(ctx).Where(models.UserCouponQuery.ID.Eq(userCouponID)).First()
|
uc, err := models.UserCouponQuery.WithContext(ctx).Where(models.UserCouponQuery.ID.Eq(userCouponID)).First()
|
||||||
|
|||||||
@@ -157,7 +157,6 @@ func (s *order) Create(ctx context.Context, form *transaction_dto.OrderCreateFor
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, ok := err.(*errorx.AppError); ok {
|
if _, ok := err.(*errorx.AppError); ok {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
Reference in New Issue
Block a user