fix: 修复微信通知字段截断导致的编码错误 feat: 添加有效邀请相关字段和任务中心常量 refactor: 重构一番赏奖品格位逻辑 perf: 优化道具卡列表聚合显示 docs: 更新项目说明文档和API文档 test: 添加字符串截断工具测试
945 lines
32 KiB
Go
945 lines
32 KiB
Go
package admin
|
||
|
||
import (
|
||
"fmt"
|
||
"net/http"
|
||
"time"
|
||
|
||
"go.uber.org/zap"
|
||
|
||
"bindbox-game/configs"
|
||
"bindbox-game/internal/code"
|
||
"bindbox-game/internal/pkg/core"
|
||
"bindbox-game/internal/pkg/validation"
|
||
"bindbox-game/internal/pkg/wechat"
|
||
"bindbox-game/internal/repository/mysql/model"
|
||
usersvc "bindbox-game/internal/service/user"
|
||
)
|
||
|
||
type listPayOrdersRequest struct {
|
||
Page int `form:"page"`
|
||
Current int `form:"current"`
|
||
PageSize int `form:"page_size"`
|
||
Size int `form:"size"`
|
||
Status *int32 `form:"status"`
|
||
SourceType *int32 `form:"source_type"`
|
||
ExcludeSourceType *int32 `form:"exclude_source_type"`
|
||
UserID *int64 `form:"user_id"`
|
||
OrderNo string `form:"order_no"`
|
||
IsConsumed *int32 `form:"is_consumed"`
|
||
StartDate string `form:"start_date"`
|
||
EndDate string `form:"end_date"`
|
||
PayStart string `form:"pay_start"`
|
||
PayEnd string `form:"pay_end"`
|
||
}
|
||
|
||
type listPayOrdersResponse struct {
|
||
Page int `json:"page"`
|
||
PageSize int `json:"page_size"`
|
||
Total int64 `json:"total"`
|
||
List []map[string]any `json:"list"`
|
||
}
|
||
|
||
func (h *handler) ListPayOrders() core.HandlerFunc {
|
||
return func(ctx core.Context) {
|
||
req := new(listPayOrdersRequest)
|
||
rsp := new(listPayOrdersResponse)
|
||
if err := ctx.ShouldBindForm(req); err != nil {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
|
||
return
|
||
}
|
||
if req.Page <= 0 && req.Current > 0 {
|
||
req.Page = req.Current
|
||
}
|
||
if req.PageSize <= 0 && req.Size > 0 {
|
||
req.PageSize = req.Size
|
||
}
|
||
if req.Page <= 0 {
|
||
req.Page = 1
|
||
}
|
||
if req.PageSize <= 0 {
|
||
req.PageSize = 20
|
||
}
|
||
if req.PageSize > 100 {
|
||
req.PageSize = 100
|
||
}
|
||
q := h.readDB.Orders.WithContext(ctx.RequestContext()).ReadDB()
|
||
if req.Status != nil {
|
||
q = q.Where(h.readDB.Orders.Status.Eq(*req.Status))
|
||
}
|
||
if req.SourceType != nil {
|
||
q = q.Where(h.readDB.Orders.SourceType.Eq(*req.SourceType))
|
||
}
|
||
if req.ExcludeSourceType != nil {
|
||
q = q.Not(h.readDB.Orders.SourceType.Eq(*req.ExcludeSourceType))
|
||
}
|
||
if req.UserID != nil {
|
||
q = q.Where(h.readDB.Orders.UserID.Eq(*req.UserID))
|
||
}
|
||
if req.OrderNo != "" {
|
||
q = q.Where(h.readDB.Orders.OrderNo.Eq(req.OrderNo))
|
||
}
|
||
if req.IsConsumed != nil {
|
||
q = q.Where(h.readDB.Orders.IsConsumed.Eq(*req.IsConsumed))
|
||
}
|
||
if req.StartDate != "" {
|
||
if t, err := time.Parse("2006-01-02", req.StartDate); err == nil {
|
||
q = q.Where(h.readDB.Orders.CreatedAt.Gte(t))
|
||
}
|
||
}
|
||
if req.EndDate != "" {
|
||
if t, err := time.Parse("2006-01-02", req.EndDate); err == nil {
|
||
t = t.Add(24 * time.Hour).Add(-time.Second)
|
||
q = q.Where(h.readDB.Orders.CreatedAt.Lte(t))
|
||
}
|
||
}
|
||
if req.PayStart != "" {
|
||
if t, err := time.Parse(time.RFC3339, req.PayStart); err == nil {
|
||
q = q.Where(h.readDB.Orders.PaidAt.Gte(t))
|
||
}
|
||
}
|
||
if req.PayEnd != "" {
|
||
if t, err := time.Parse(time.RFC3339, req.PayEnd); err == nil {
|
||
q = q.Where(h.readDB.Orders.PaidAt.Lte(t))
|
||
}
|
||
}
|
||
total, err := q.Count()
|
||
if err != nil {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, 21001, err.Error()))
|
||
return
|
||
}
|
||
rows, err := q.Order(h.readDB.Orders.ID.Desc()).Offset((req.Page - 1) * req.PageSize).Limit(req.PageSize).Find()
|
||
if err != nil {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, 21002, err.Error()))
|
||
return
|
||
}
|
||
var pointsRate int64 = 1
|
||
if cfgRate, _ := h.readDB.SystemConfigs.WithContext(ctx.RequestContext()).Where(h.readDB.SystemConfigs.ConfigKey.Eq("points_exchange_per_cent")).First(); cfgRate != nil {
|
||
var r int64
|
||
_, _ = fmt.Sscanf(cfgRate.ConfigValue, "%d", &r)
|
||
if r > 0 {
|
||
pointsRate = r
|
||
}
|
||
}
|
||
|
||
// 批量查询优惠券和道具卡信息
|
||
userCouponIDs := make([]int64, 0)
|
||
userItemCardIDs := make([]int64, 0)
|
||
// 收集订单ID用于查询 OrderCoupons
|
||
orderIDs := make([]int64, 0, len(rows))
|
||
for _, o := range rows {
|
||
orderIDs = append(orderIDs, o.ID)
|
||
if o.CouponID > 0 {
|
||
userCouponIDs = append(userCouponIDs, o.CouponID)
|
||
}
|
||
if o.ItemCardID > 0 {
|
||
userItemCardIDs = append(userItemCardIDs, o.ItemCardID)
|
||
}
|
||
}
|
||
|
||
couponMap := make(map[int64]map[string]any)
|
||
// orderID -> userCouponID -> appliedAmount
|
||
appliedAmountMap := make(map[int64]map[int64]int64)
|
||
|
||
if len(userCouponIDs) > 0 {
|
||
userCoupons, _ := h.readDB.UserCoupons.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.UserCoupons.ID.In(userCouponIDs...)).Find()
|
||
var sysCouponIDs []int64
|
||
ucMap := make(map[int64]*model.UserCoupons)
|
||
for _, uc := range userCoupons {
|
||
sysCouponIDs = append(sysCouponIDs, uc.CouponID)
|
||
ucMap[uc.ID] = uc
|
||
}
|
||
if len(sysCouponIDs) > 0 {
|
||
sysCoupons, _ := h.readDB.SystemCoupons.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.SystemCoupons.ID.In(sysCouponIDs...)).Find()
|
||
scMap := make(map[int64]*model.SystemCoupons)
|
||
for _, sc := range sysCoupons {
|
||
scMap[sc.ID] = sc
|
||
}
|
||
for id, uc := range ucMap {
|
||
if sc, ok := scMap[uc.CouponID]; ok {
|
||
couponMap[id] = map[string]any{
|
||
"user_coupon_id": uc.ID,
|
||
"name": sc.Name,
|
||
"type": sc.DiscountType,
|
||
"value": sc.DiscountValue,
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 查询 OrderCoupons 获取实际抵扣金额
|
||
if len(orderIDs) > 0 {
|
||
ocs, _ := h.readDB.OrderCoupons.WithContext(ctx.RequestContext()).ReadDB().Where(
|
||
h.readDB.OrderCoupons.OrderID.In(orderIDs...),
|
||
h.readDB.OrderCoupons.UserCouponID.In(userCouponIDs...),
|
||
).Find()
|
||
for _, oc := range ocs {
|
||
if _, ok := appliedAmountMap[oc.OrderID]; !ok {
|
||
appliedAmountMap[oc.OrderID] = make(map[int64]int64)
|
||
}
|
||
appliedAmountMap[oc.OrderID][oc.UserCouponID] = oc.AppliedAmount
|
||
}
|
||
}
|
||
}
|
||
|
||
itemCardMap := make(map[int64]map[string]any)
|
||
if len(userItemCardIDs) > 0 {
|
||
userCards, _ := h.readDB.UserItemCards.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.UserItemCards.ID.In(userItemCardIDs...)).Find()
|
||
var sysCardIDs []int64
|
||
ucMap := make(map[int64]*model.UserItemCards)
|
||
for _, uc := range userCards {
|
||
sysCardIDs = append(sysCardIDs, uc.CardID)
|
||
ucMap[uc.ID] = uc
|
||
}
|
||
if len(sysCardIDs) > 0 {
|
||
sysCards, _ := h.readDB.SystemItemCards.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.SystemItemCards.ID.In(sysCardIDs...)).Find()
|
||
scMap := make(map[int64]*model.SystemItemCards)
|
||
for _, sc := range sysCards {
|
||
scMap[sc.ID] = sc
|
||
}
|
||
for id, uc := range ucMap {
|
||
if sc, ok := scMap[uc.CardID]; ok {
|
||
itemCardMap[id] = map[string]any{
|
||
"user_card_id": uc.ID,
|
||
"name": sc.Name,
|
||
"effect_type": sc.EffectType,
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
out := make([]map[string]any, 0, len(rows))
|
||
for _, o := range rows {
|
||
ledgers, err := h.readDB.UserPointsLedger.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.UserPointsLedger.RefTable.Eq("orders"), h.readDB.UserPointsLedger.RefID.Eq(o.OrderNo)).Find()
|
||
if err != nil {
|
||
h.logger.Error("ListPayOrders fetch ledgers error", zap.Error(err), zap.String("order_no", o.OrderNo))
|
||
}
|
||
var consumePointsSum int64
|
||
for _, lg := range ledgers {
|
||
if lg.Points < 0 {
|
||
consumePointsSum += -lg.Points
|
||
}
|
||
}
|
||
pa := o.PointsAmount
|
||
if pa == 0 && consumePointsSum > 0 {
|
||
pa = consumePointsSum / pointsRate
|
||
}
|
||
pu := int64(0)
|
||
if consumePointsSum > 0 {
|
||
pu = consumePointsSum
|
||
} else if pa > 0 {
|
||
pu = pa * pointsRate
|
||
}
|
||
var cInfo map[string]any
|
||
if baseInfo, ok := couponMap[o.CouponID]; ok {
|
||
cInfo = make(map[string]any)
|
||
for k, v := range baseInfo {
|
||
cInfo[k] = v
|
||
}
|
||
// 尝试使用实际抵扣金额
|
||
if amMap, ok := appliedAmountMap[o.ID]; ok {
|
||
if amount, ok2 := amMap[o.CouponID]; ok2 {
|
||
cInfo["value"] = amount
|
||
}
|
||
}
|
||
}
|
||
|
||
item := map[string]any{
|
||
"id": o.ID,
|
||
"order_no": o.OrderNo,
|
||
"user_id": o.UserID,
|
||
"source_type": o.SourceType,
|
||
"actual_amount": o.ActualAmount,
|
||
"status": o.Status,
|
||
"paid_at": func() string {
|
||
if o.PaidAt != nil && !o.PaidAt.IsZero() {
|
||
return o.PaidAt.Format("2006-01-02 15:04:05")
|
||
}
|
||
return ""
|
||
}(),
|
||
"created_at": o.CreatedAt,
|
||
"is_consumed": o.IsConsumed,
|
||
"remark": o.Remark,
|
||
"points_amount": pa,
|
||
"points_used": pu,
|
||
"total_amount": o.TotalAmount,
|
||
"coupon_info": cInfo,
|
||
"item_card_info": itemCardMap[o.ItemCardID],
|
||
}
|
||
out = append(out, item)
|
||
}
|
||
rsp.Page = req.Page
|
||
rsp.PageSize = req.PageSize
|
||
rsp.Total = total
|
||
rsp.List = out
|
||
ctx.Payload(rsp)
|
||
}
|
||
}
|
||
|
||
type getPayOrderResponse struct {
|
||
Order *model.Orders `json:"order"`
|
||
Items []*model.OrderItems `json:"items"`
|
||
Shipments []*model.ShippingRecords `json:"shipments"`
|
||
Ledgers []*model.UserPointsLedger `json:"ledgers"`
|
||
User *model.Users `json:"user"`
|
||
Coupons []*struct {
|
||
UserCouponID int64 `json:"user_coupon_id"`
|
||
AppliedAmount int64 `json:"applied_amount"`
|
||
} `json:"coupons"`
|
||
Activity *struct {
|
||
ActivityID int64 `json:"activity_id"`
|
||
ActivityName string `json:"activity_name"`
|
||
IssueID int64 `json:"issue_id"`
|
||
IssueNumber string `json:"issue_number"`
|
||
IsWinner int32 `json:"is_winner"`
|
||
Level int32 `json:"level"`
|
||
RewardID int64 `json:"reward_id"`
|
||
RewardName string `json:"reward_name"`
|
||
ProductID int64 `json:"product_id"`
|
||
Count int64 `json:"count"`
|
||
ProductPrice int64 `json:"product_price"`
|
||
PriceDraw int64 `json:"price_draw"`
|
||
PriceDrawPts int64 `json:"price_draw_points"`
|
||
} `json:"activity"`
|
||
Payment *struct {
|
||
Status int32 `json:"status"`
|
||
ActualAmount int64 `json:"actual_amount"`
|
||
PaidAt string `json:"paid_at"`
|
||
PayPreorderID int64 `json:"pay_preorder_id"`
|
||
TransactionID string `json:"transaction_id"`
|
||
PointsAmount int64 `json:"points_amount"`
|
||
PointsUsed int64 `json:"points_used"`
|
||
CouponAppliedAmount int64 `json:"coupon_applied_amount"`
|
||
TotalAmount int64 `json:"total_amount"`
|
||
} `json:"payment"`
|
||
Refunds []*struct {
|
||
RefundNo string `json:"refund_no"`
|
||
Amount int64 `json:"amount"`
|
||
Status string `json:"status"`
|
||
Channel string `json:"channel"`
|
||
Reason string `json:"reason"`
|
||
CreatedAt string `json:"created_at"`
|
||
} `json:"refunds"`
|
||
RefundableAmount int64 `json:"refundable_amount"`
|
||
ComputedItems []*struct {
|
||
Name string `json:"name"`
|
||
Quantity int64 `json:"quantity"`
|
||
UnitPrice int64 `json:"unit_price"`
|
||
Amount int64 `json:"amount"`
|
||
} `json:"computed_items"`
|
||
RewardOrder *struct {
|
||
OrderNo string `json:"order_no"`
|
||
Status int32 `json:"status"`
|
||
CreatedAt string `json:"created_at"`
|
||
} `json:"reward_order"`
|
||
RewardItems []*struct {
|
||
Title string `json:"title"`
|
||
Quantity int64 `json:"quantity"`
|
||
UnitPrice int64 `json:"unit_price"`
|
||
Amount int64 `json:"amount"`
|
||
} `json:"reward_items"`
|
||
RewardShipments []*model.ShippingRecords `json:"reward_shipments"`
|
||
DrawReceipts []*usersvc.DrawReceiptInfo `json:"draw_receipts"`
|
||
CouponInfo map[string]any `json:"coupon_info"`
|
||
ItemCardInfo map[string]any `json:"item_card_info"`
|
||
}
|
||
|
||
func (h *handler) GetPayOrderDetail() core.HandlerFunc {
|
||
return func(ctx core.Context) {
|
||
rsp := new(getPayOrderResponse)
|
||
orderNo := ctx.Param("order_no")
|
||
if orderNo == "" {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "缺少订单号"))
|
||
return
|
||
}
|
||
order, err := h.readDB.Orders.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.Orders.OrderNo.Eq(orderNo)).First()
|
||
if err != nil || order == nil {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, 21003, "订单不存在"))
|
||
return
|
||
}
|
||
items, _ := h.readDB.OrderItems.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.OrderItems.OrderID.Eq(order.ID)).Find()
|
||
shipments, _ := h.readDB.ShippingRecords.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.ShippingRecords.OrderID.Eq(order.ID)).Find()
|
||
ledgers, _ := h.readDB.UserPointsLedger.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.UserPointsLedger.RefTable.Eq("orders"), h.readDB.UserPointsLedger.RefID.Eq(orderNo)).Find()
|
||
ocRows, _ := h.readDB.OrderCoupons.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.OrderCoupons.OrderID.Eq(order.ID)).Find()
|
||
var couponList []*struct {
|
||
UserCouponID int64 `json:"user_coupon_id"`
|
||
AppliedAmount int64 `json:"applied_amount"`
|
||
}
|
||
var couponAppliedSum int64
|
||
for _, r := range ocRows {
|
||
couponList = append(couponList, &struct {
|
||
UserCouponID int64 `json:"user_coupon_id"`
|
||
AppliedAmount int64 `json:"applied_amount"`
|
||
}{UserCouponID: r.UserCouponID, AppliedAmount: r.AppliedAmount})
|
||
couponAppliedSum += r.AppliedAmount
|
||
}
|
||
var consumePointsSum int64
|
||
for _, lg := range ledgers {
|
||
if lg.Action == "consume_order" && lg.Points < 0 {
|
||
consumePointsSum += -lg.Points
|
||
}
|
||
}
|
||
// 每分对应多少积分(默认1),用于展示积分抵扣与活动价格
|
||
var pointsRate int64 = 1
|
||
if cfgRate, _ := h.readDB.SystemConfigs.WithContext(ctx.RequestContext()).Where(h.readDB.SystemConfigs.ConfigKey.Eq("points_exchange_per_cent")).First(); cfgRate != nil {
|
||
var r int64
|
||
_, _ = fmt.Sscanf(cfgRate.ConfigValue, "%d", &r)
|
||
if r > 0 {
|
||
pointsRate = r
|
||
}
|
||
}
|
||
var pointsAmountCents int64 = order.PointsAmount
|
||
if pointsAmountCents == 0 && consumePointsSum > 0 {
|
||
pointsAmountCents = consumePointsSum / pointsRate
|
||
}
|
||
user, _ := h.readDB.Users.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.Users.ID.Eq(order.UserID)).First()
|
||
var activityID int64
|
||
var activityName string
|
||
var issueID int64
|
||
var issueNumber string
|
||
var isWinner int32
|
||
var level int32
|
||
var rewardID int64
|
||
var rewardName string
|
||
var rewardProductID int64
|
||
var count int64
|
||
if order.SourceType == 2 {
|
||
drawLog, _ := h.readDB.ActivityDrawLogs.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.ActivityDrawLogs.OrderID.Eq(order.ID)).First()
|
||
if drawLog != nil {
|
||
isWinner = drawLog.IsWinner
|
||
level = drawLog.Level
|
||
rewardID = drawLog.RewardID
|
||
issueID = drawLog.IssueID
|
||
issue, _ := h.readDB.ActivityIssues.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.ActivityIssues.ID.Eq(issueID)).First()
|
||
if issue != nil {
|
||
issueNumber = issue.IssueNumber
|
||
activityID = issue.ActivityID
|
||
activity, _ := h.readDB.Activities.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.Activities.ID.Eq(activityID)).First()
|
||
if activity != nil {
|
||
activityName = activity.Name
|
||
}
|
||
}
|
||
if rewardID > 0 {
|
||
reward, _ := h.readDB.ActivityRewardSettings.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.ActivityRewardSettings.ID.Eq(rewardID)).First()
|
||
if reward != nil {
|
||
rewardName = reward.Name
|
||
rewardProductID = reward.ProductID
|
||
}
|
||
}
|
||
} else {
|
||
remark := order.Remark
|
||
var aid, iss, cnt int64
|
||
p := 0
|
||
for i := 0; i <= len(remark); i++ {
|
||
if i == len(remark) || remark[i] == '|' {
|
||
seg := remark[p:i]
|
||
if len(seg) > len("lottery:activity:") && seg[:len("lottery:activity:")] == "lottery:activity:" {
|
||
var n int64
|
||
for j := len("lottery:activity:"); j < len(seg); j++ {
|
||
c := seg[j]
|
||
if c < '0' || c > '9' {
|
||
break
|
||
}
|
||
n = n*10 + int64(c-'0')
|
||
}
|
||
aid = n
|
||
}
|
||
if len(seg) > 6 && seg[:6] == "issue:" {
|
||
var n2 int64
|
||
for j := 6; j < len(seg); j++ {
|
||
c := seg[j]
|
||
if c < '0' || c > '9' {
|
||
break
|
||
}
|
||
n2 = n2*10 + int64(c-'0')
|
||
}
|
||
iss = n2
|
||
}
|
||
if len(seg) > 6 && seg[:6] == "count:" {
|
||
var n3 int64
|
||
for j := 6; j < len(seg); j++ {
|
||
c := seg[j]
|
||
if c < '0' || c > '9' {
|
||
break
|
||
}
|
||
n3 = n3*10 + int64(c-'0')
|
||
}
|
||
if n3 <= 0 {
|
||
n3 = 1
|
||
}
|
||
cnt = n3
|
||
}
|
||
p = i + 1
|
||
}
|
||
}
|
||
if aid > 0 {
|
||
activityID = aid
|
||
}
|
||
if iss > 0 {
|
||
issueID = iss
|
||
}
|
||
count = cnt
|
||
if issueID > 0 {
|
||
issue, _ := h.readDB.ActivityIssues.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.ActivityIssues.ID.Eq(issueID)).First()
|
||
if issue != nil {
|
||
issueNumber = issue.IssueNumber
|
||
activityID = issue.ActivityID
|
||
}
|
||
}
|
||
if activityID > 0 {
|
||
activity, _ := h.readDB.Activities.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.Activities.ID.Eq(activityID)).First()
|
||
if activity != nil {
|
||
activityName = activity.Name
|
||
}
|
||
}
|
||
}
|
||
}
|
||
var productPrice int64
|
||
if rewardProductID > 0 {
|
||
prod, _ := h.readDB.Products.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.Products.ID.Eq(rewardProductID)).First()
|
||
if prod != nil {
|
||
productPrice = prod.Price
|
||
}
|
||
}
|
||
if count <= 0 {
|
||
count = func() int64 {
|
||
remark := order.Remark
|
||
var n int64
|
||
p := 0
|
||
for i := 0; i <= len(remark); i++ {
|
||
if i == len(remark) || remark[i] == '|' {
|
||
seg := remark[p:i]
|
||
if len(seg) > 6 && seg[:6] == "count:" {
|
||
for j := 6; j < len(seg); j++ {
|
||
c := seg[j]
|
||
if c < '0' || c > '9' {
|
||
break
|
||
}
|
||
n = n*10 + int64(c-'0')
|
||
}
|
||
}
|
||
p = i + 1
|
||
}
|
||
}
|
||
if n <= 0 {
|
||
n = 1
|
||
}
|
||
return n
|
||
}()
|
||
}
|
||
// 使用 pointsRate 展示活动价格(积分)
|
||
rsp.Activity = &struct {
|
||
ActivityID int64 `json:"activity_id"`
|
||
ActivityName string `json:"activity_name"`
|
||
IssueID int64 `json:"issue_id"`
|
||
IssueNumber string `json:"issue_number"`
|
||
IsWinner int32 `json:"is_winner"`
|
||
Level int32 `json:"level"`
|
||
RewardID int64 `json:"reward_id"`
|
||
RewardName string `json:"reward_name"`
|
||
ProductID int64 `json:"product_id"`
|
||
Count int64 `json:"count"`
|
||
ProductPrice int64 `json:"product_price"`
|
||
PriceDraw int64 `json:"price_draw"`
|
||
PriceDrawPts int64 `json:"price_draw_points"`
|
||
}{
|
||
ActivityID: activityID,
|
||
ActivityName: activityName,
|
||
IssueID: issueID,
|
||
IssueNumber: issueNumber,
|
||
IsWinner: isWinner,
|
||
Level: level,
|
||
RewardID: rewardID,
|
||
RewardName: rewardName,
|
||
ProductID: rewardProductID,
|
||
Count: count,
|
||
ProductPrice: productPrice,
|
||
PriceDraw: order.TotalAmount / func() int64 {
|
||
if count > 0 {
|
||
return count
|
||
}
|
||
return 1
|
||
}(),
|
||
PriceDrawPts: (order.TotalAmount / func() int64 {
|
||
if count > 0 {
|
||
return count
|
||
}
|
||
return 1
|
||
}()) * pointsRate,
|
||
}
|
||
if user != nil {
|
||
rsp.User = user
|
||
}
|
||
rsp.Payment = &struct {
|
||
Status int32 `json:"status"`
|
||
ActualAmount int64 `json:"actual_amount"`
|
||
PaidAt string `json:"paid_at"`
|
||
PayPreorderID int64 `json:"pay_preorder_id"`
|
||
TransactionID string `json:"transaction_id"`
|
||
PointsAmount int64 `json:"points_amount"`
|
||
PointsUsed int64 `json:"points_used"`
|
||
CouponAppliedAmount int64 `json:"coupon_applied_amount"`
|
||
TotalAmount int64 `json:"total_amount"`
|
||
}{
|
||
Status: order.Status,
|
||
ActualAmount: order.ActualAmount,
|
||
PaidAt: func() string {
|
||
if order.PaidAt != nil && !order.PaidAt.IsZero() {
|
||
return order.PaidAt.Format("2006-01-02 15:04:05")
|
||
}
|
||
return ""
|
||
}(),
|
||
PayPreorderID: order.PayPreorderID,
|
||
TransactionID: "",
|
||
PointsAmount: pointsAmountCents,
|
||
PointsUsed: func() int64 {
|
||
if consumePointsSum > 0 {
|
||
return consumePointsSum
|
||
}
|
||
if pointsAmountCents > 0 {
|
||
return pointsAmountCents * pointsRate
|
||
}
|
||
return 0
|
||
}(),
|
||
CouponAppliedAmount: couponAppliedSum,
|
||
TotalAmount: order.TotalAmount,
|
||
}
|
||
tx, _ := h.readDB.PaymentTransactions.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.PaymentTransactions.OrderNo.Eq(order.OrderNo)).Order(h.readDB.PaymentTransactions.ID.Desc()).First()
|
||
if tx != nil {
|
||
rsp.Payment.PaidAt = tx.SuccessTime.Format("2006-01-02 15:04:05")
|
||
rsp.Payment.TransactionID = tx.TransactionID
|
||
}
|
||
var refundedSum int64
|
||
var refunds []*struct {
|
||
RefundNo string `json:"refund_no"`
|
||
Amount int64 `json:"amount"`
|
||
Status string `json:"status"`
|
||
Channel string `json:"channel"`
|
||
Reason string `json:"reason"`
|
||
CreatedAt string `json:"created_at"`
|
||
}
|
||
prList, _ := h.readDB.PaymentRefunds.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.PaymentRefunds.OrderNo.Eq(order.OrderNo)).Order(h.readDB.PaymentRefunds.ID.Desc()).Find()
|
||
for _, r := range prList {
|
||
refundedSum += r.AmountRefund
|
||
refunds = append(refunds, &struct {
|
||
RefundNo string `json:"refund_no"`
|
||
Amount int64 `json:"amount"`
|
||
Status string `json:"status"`
|
||
Channel string `json:"channel"`
|
||
Reason string `json:"reason"`
|
||
CreatedAt string `json:"created_at"`
|
||
}{RefundNo: r.RefundNo, Amount: r.AmountRefund, Status: r.Status, Channel: r.Channel, Reason: r.Reason, CreatedAt: r.CreatedAt.Format("2006-01-02 15:04:05")})
|
||
}
|
||
var refundable int64 = order.ActualAmount - refundedSum
|
||
if refundable < 0 {
|
||
refundable = 0
|
||
}
|
||
rsp.Refunds = refunds
|
||
rsp.RefundableAmount = refundable
|
||
rsp.Order = order
|
||
rsp.Items = items
|
||
rsp.Coupons = couponList
|
||
rsp.Shipments = shipments
|
||
rsp.Ledgers = ledgers
|
||
if order.SourceType == 2 {
|
||
unit := int64(0)
|
||
if count > 0 {
|
||
unit = order.TotalAmount / count
|
||
}
|
||
name := rewardName
|
||
if name == "" {
|
||
name = "抽奖参与"
|
||
}
|
||
rsp.ComputedItems = []*struct {
|
||
Name string `json:"name"`
|
||
Quantity int64 `json:"quantity"`
|
||
UnitPrice int64 `json:"unit_price"`
|
||
Amount int64 `json:"amount"`
|
||
}{
|
||
{Name: name, Quantity: count, UnitPrice: unit, Amount: order.TotalAmount},
|
||
}
|
||
// 合并中奖发放信息:按该订单的全部抽奖日志汇总发放订单项与物流
|
||
logsAll, _ := h.readDB.ActivityDrawLogs.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.ActivityDrawLogs.OrderID.Eq(order.ID)).Find()
|
||
var risAll []*struct {
|
||
Title string `json:"title"`
|
||
Quantity int64 `json:"quantity"`
|
||
UnitPrice int64 `json:"unit_price"`
|
||
Amount int64 `json:"amount"`
|
||
}
|
||
var shipsAll []*model.ShippingRecords
|
||
for _, lg := range logsAll {
|
||
if lg.RewardID <= 0 {
|
||
continue
|
||
}
|
||
inv, _ := h.readDB.UserInventory.WithContext(ctx.RequestContext()).ReadDB().Where(
|
||
h.readDB.UserInventory.UserID.Eq(order.UserID),
|
||
h.readDB.UserInventory.RewardID.Eq(lg.RewardID),
|
||
).Order(h.readDB.UserInventory.ID.Desc()).First()
|
||
if inv == nil || inv.OrderID <= 0 {
|
||
continue
|
||
}
|
||
rg, _ := h.readDB.Orders.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.Orders.ID.Eq(inv.OrderID)).First()
|
||
if rg == nil {
|
||
continue
|
||
}
|
||
if rsp.RewardOrder == nil {
|
||
rsp.RewardOrder = &struct {
|
||
OrderNo string `json:"order_no"`
|
||
Status int32 `json:"status"`
|
||
CreatedAt string `json:"created_at"`
|
||
}{
|
||
OrderNo: rg.OrderNo,
|
||
Status: rg.Status,
|
||
CreatedAt: rg.CreatedAt.Format("2006-01-02 15:04:05"),
|
||
}
|
||
}
|
||
ritems, _ := h.readDB.OrderItems.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.OrderItems.OrderID.Eq(rg.ID)).Find()
|
||
for _, it := range ritems {
|
||
risAll = append(risAll, &struct {
|
||
Title string `json:"title"`
|
||
Quantity int64 `json:"quantity"`
|
||
UnitPrice int64 `json:"unit_price"`
|
||
Amount int64 `json:"amount"`
|
||
}{
|
||
Title: it.Title,
|
||
Quantity: it.Quantity,
|
||
UnitPrice: it.Price,
|
||
Amount: it.TotalAmount,
|
||
})
|
||
}
|
||
rships, _ := h.readDB.ShippingRecords.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.ShippingRecords.OrderID.Eq(rg.ID)).Find()
|
||
shipsAll = append(shipsAll, rships...)
|
||
}
|
||
if len(risAll) > 0 {
|
||
rsp.RewardItems = risAll
|
||
}
|
||
if len(shipsAll) > 0 {
|
||
rsp.RewardShipments = shipsAll
|
||
}
|
||
}
|
||
|
||
// 填充抽奖凭据 Verify Seed Data
|
||
if order.SourceType == 2 { // Buffer/Lottery orders
|
||
drawLogs, _ := h.readDB.ActivityDrawLogs.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.ActivityDrawLogs.OrderID.Eq(order.ID)).Find()
|
||
var logIDs []int64
|
||
for _, l := range drawLogs {
|
||
logIDs = append(logIDs, l.ID)
|
||
}
|
||
if len(logIDs) > 0 {
|
||
receipts, _ := h.readDB.ActivityDrawReceipts.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.ActivityDrawReceipts.DrawLogID.In(logIDs...)).Find()
|
||
var drList []*usersvc.DrawReceiptInfo
|
||
for i, r := range receipts {
|
||
// Find rewardID from logs if possible, though receipt has DrawLogID
|
||
var rid int64
|
||
for _, l := range drawLogs {
|
||
if l.ID == r.DrawLogID {
|
||
rid = l.RewardID
|
||
break
|
||
}
|
||
}
|
||
drList = append(drList, &usersvc.DrawReceiptInfo{
|
||
DrawLogID: r.DrawLogID,
|
||
RewardID: rid,
|
||
DrawIndex: i + 1, // approximate
|
||
AlgoVersion: r.AlgoVersion,
|
||
RoundID: r.RoundID,
|
||
DrawID: r.DrawID,
|
||
ClientID: r.ClientID,
|
||
Timestamp: r.Timestamp,
|
||
ServerSeedHash: r.ServerSeedHash,
|
||
ServerSubSeed: r.ServerSubSeed,
|
||
ClientSeed: r.ClientSeed,
|
||
Nonce: r.Nonce,
|
||
ItemsRoot: r.ItemsRoot,
|
||
WeightsTotal: r.WeightsTotal,
|
||
SelectedIndex: r.SelectedIndex,
|
||
RandProof: r.RandProof,
|
||
Signature: r.Signature,
|
||
ItemsSnapshot: r.ItemsSnapshot,
|
||
})
|
||
}
|
||
rsp.DrawReceipts = drList
|
||
}
|
||
}
|
||
|
||
// 补充优惠券和道具卡信息
|
||
if order.CouponID > 0 {
|
||
if uc, _ := h.readDB.UserCoupons.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.UserCoupons.ID.Eq(order.CouponID)).First(); uc != nil {
|
||
if sc, _ := h.readDB.SystemCoupons.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.SystemCoupons.ID.Eq(uc.CouponID)).First(); sc != nil {
|
||
val := sc.DiscountValue
|
||
// 尝试查找实际抵扣金额
|
||
// 上面已经查询了 ocRows,直接遍历查找
|
||
for _, r := range ocRows {
|
||
if r.UserCouponID == order.CouponID {
|
||
val = r.AppliedAmount
|
||
break
|
||
}
|
||
}
|
||
|
||
rsp.CouponInfo = map[string]any{
|
||
"user_coupon_id": uc.ID,
|
||
"name": sc.Name,
|
||
"type": sc.DiscountType,
|
||
"value": val,
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if order.ItemCardID > 0 {
|
||
if uc, _ := h.readDB.UserItemCards.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.UserItemCards.ID.Eq(order.ItemCardID)).First(); uc != nil {
|
||
if sc, _ := h.readDB.SystemItemCards.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.SystemItemCards.ID.Eq(uc.CardID)).First(); sc != nil {
|
||
rsp.ItemCardInfo = map[string]any{
|
||
"user_card_id": uc.ID,
|
||
"name": sc.Name,
|
||
"effect_type": sc.EffectType,
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
ctx.Payload(rsp)
|
||
}
|
||
}
|
||
|
||
func (h *handler) UploadMiniAppVirtualShippingForOrder() core.HandlerFunc {
|
||
return func(ctx core.Context) {
|
||
orderNo := ctx.Param("order_no")
|
||
if orderNo == "" {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "缺少订单号"))
|
||
return
|
||
}
|
||
order, err := h.readDB.Orders.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.Orders.OrderNo.Eq(orderNo)).First()
|
||
if err != nil || order == nil {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, 21003, "订单不存在"))
|
||
return
|
||
}
|
||
if order.Status != 2 {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, 21006, "仅支持已支付订单发货"))
|
||
return
|
||
}
|
||
tx, _ := h.readDB.PaymentTransactions.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.PaymentTransactions.OrderNo.Eq(order.OrderNo)).Order(h.readDB.PaymentTransactions.ID.Desc()).First()
|
||
if tx == nil || tx.TransactionID == "" {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, 21004, "未找到支付交易"))
|
||
return
|
||
}
|
||
var itemDesc string
|
||
xs, _ := h.readDB.OrderItems.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.OrderItems.OrderID.Eq(order.ID)).Find()
|
||
if len(xs) == 0 {
|
||
itemDesc = "订单" + order.OrderNo
|
||
} else {
|
||
s := ""
|
||
for i, it := range xs {
|
||
seg := it.Title + "*" + func(q int64) string { return fmt.Sprintf("%d", q) }(it.Quantity)
|
||
if i == 0 {
|
||
s = seg
|
||
} else {
|
||
s = s + ", " + seg
|
||
}
|
||
}
|
||
if len(s) > 120 {
|
||
s = s[:120]
|
||
}
|
||
itemDesc = s
|
||
}
|
||
pre, _ := h.readDB.PaymentPreorders.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.PaymentPreorders.OrderID.Eq(order.ID)).Order(h.readDB.PaymentPreorders.ID.Desc()).First()
|
||
payerOpenid := ""
|
||
if pre != nil {
|
||
payerOpenid = pre.PayerOpenid
|
||
}
|
||
cfg := configs.Get()
|
||
wxc := &wechat.WechatConfig{AppID: cfg.Wechat.AppID, AppSecret: cfg.Wechat.AppSecret}
|
||
if err := wechat.UploadVirtualShippingWithFallback(ctx, wxc, tx.TransactionID, order.OrderNo, payerOpenid, itemDesc, time.Now()); err != nil {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, 21005, err.Error()))
|
||
return
|
||
}
|
||
ctx.Payload(map[string]any{"ok": true})
|
||
}
|
||
}
|
||
|
||
type updateOrderRemarkRequest struct {
|
||
Remark string `json:"remark"`
|
||
}
|
||
|
||
type simpleResponse struct {
|
||
Success bool `json:"success"`
|
||
}
|
||
|
||
func (h *handler) UpdateOrderRemark() core.HandlerFunc {
|
||
return func(ctx core.Context) {
|
||
req := new(updateOrderRemarkRequest)
|
||
rsp := new(simpleResponse)
|
||
if err := ctx.ShouldBindJSON(req); err != nil {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err)))
|
||
return
|
||
}
|
||
orderNo := ctx.Param("order_no")
|
||
if orderNo == "" {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "缺少订单号"))
|
||
return
|
||
}
|
||
order, err := h.readDB.Orders.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.Orders.OrderNo.Eq(orderNo)).First()
|
||
if err != nil || order == nil {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, 21004, "订单不存在"))
|
||
return
|
||
}
|
||
_, err = h.writeDB.Orders.WithContext(ctx.RequestContext()).Where(h.readDB.Orders.ID.Eq(order.ID)).Updates(map[string]any{
|
||
h.writeDB.Orders.Remark.ColumnName().String(): req.Remark,
|
||
h.writeDB.Orders.UpdatedAt.ColumnName().String(): time.Now(),
|
||
})
|
||
if err != nil {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, 21005, err.Error()))
|
||
return
|
||
}
|
||
rsp.Success = true
|
||
ctx.Payload(rsp)
|
||
}
|
||
}
|
||
|
||
func (h *handler) CancelOrder() core.HandlerFunc {
|
||
return func(ctx core.Context) {
|
||
orderNo := ctx.Param("order_no")
|
||
if orderNo == "" {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "缺少订单号"))
|
||
return
|
||
}
|
||
order, err := h.readDB.Orders.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.Orders.OrderNo.Eq(orderNo)).First()
|
||
if err != nil || order == nil {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, 21006, "订单不存在"))
|
||
return
|
||
}
|
||
_, err = h.writeDB.Orders.WithContext(ctx.RequestContext()).Where(h.readDB.Orders.ID.Eq(order.ID), h.readDB.Orders.Status.Eq(1)).Updates(map[string]any{
|
||
h.readDB.Orders.Status.ColumnName().String(): 3,
|
||
h.readDB.Orders.CancelledAt.ColumnName().String(): time.Now(),
|
||
})
|
||
if err != nil {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, 21007, err.Error()))
|
||
return
|
||
}
|
||
ctx.Payload(&simpleResponse{Success: true})
|
||
}
|
||
}
|
||
|
||
func (h *handler) ConsumeOrder() core.HandlerFunc {
|
||
return func(ctx core.Context) {
|
||
orderNo := ctx.Param("order_no")
|
||
if orderNo == "" {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "缺少订单号"))
|
||
return
|
||
}
|
||
order, err := h.readDB.Orders.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.Orders.OrderNo.Eq(orderNo)).First()
|
||
if err != nil || order == nil {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, 21008, "订单不存在"))
|
||
return
|
||
}
|
||
_, err = h.writeDB.Orders.WithContext(ctx.RequestContext()).Where(h.readDB.Orders.ID.Eq(order.ID), h.readDB.Orders.Status.Eq(2)).Updates(map[string]any{
|
||
h.readDB.Orders.IsConsumed.ColumnName().String(): 1,
|
||
})
|
||
if err != nil {
|
||
ctx.AbortWithError(core.Error(http.StatusBadRequest, 21009, err.Error()))
|
||
return
|
||
}
|
||
ctx.Payload(&simpleResponse{Success: true})
|
||
}
|
||
}
|