bindbox-game/internal/api/admin/pay_orders_admin.go
邹方成 e2782a69d3 feat: 添加对对碰游戏功能与Redis支持
refactor: 重构抽奖逻辑以支持可验证凭据
feat(redis): 集成Redis客户端并添加配置支持
fix: 修复订单取消时的优惠券和库存处理逻辑
docs: 添加对对碰游戏前端对接指南和示例JSON
test: 添加对对碰游戏模拟测试和验证逻辑
2025-12-21 17:31:32 +08:00

789 lines
26 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"
"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"`
PageSize int `form:"page_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.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.SourceType == nil && req.ExcludeSourceType == nil {
q = q.Not(h.readDB.Orders.SourceType.Eq(3))
}
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
}
}
out := make([]map[string]any, 0, len(rows))
for _, o := range rows {
ledgers, _ := h.readDB.UserPointsLedger.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.UserPointsLedger.RefTable.Eq("orders"), h.readDB.UserPointsLedger.RefID.Eq(o.OrderNo)).Find()
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
}
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.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,
}
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"`
}
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: order.PaidAt.Format("2006-01-02 15:04:05"),
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
}
}
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})
}
}