bindbox-game/internal/api/admin/pay_orders_admin.go
邹方成 a7a0f639e1 feat: 新增取消发货功能并优化任务中心
fix: 修复微信通知字段截断导致的编码错误
feat: 添加有效邀请相关字段和任务中心常量
refactor: 重构一番赏奖品格位逻辑
perf: 优化道具卡列表聚合显示
docs: 更新项目说明文档和API文档
test: 添加字符串截断工具测试
2025-12-23 22:26:07 +08:00

945 lines
32 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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})
}
}