admin_backend/model/order_reissue.go
2026-05-14 14:35:06 +08:00

333 lines
11 KiB
Go

package model
import (
"backend/sdk/ship/model/base"
util "backend/util"
"fmt"
"strings"
)
const (
OrderReissueStatusPending = 1
OrderReissueStatusApproved = 2
OrderReissueStatusRejected = 3
)
type OrderReissueAudit struct {
AuditId int `json:"audit_id" db:"id"`
Uid int `json:"Uid" db:"uid"`
AppId int `json:"AppId" db:"app_id"`
ServerId int `json:"ServerId" db:"server_id"`
OrderId string `json:"OrderId" db:"order_id"`
ThirdPartyOrderId string `json:"ThirdPartyOrderId" db:"third_party_order_id"`
Reason string `json:"Reason" db:"reason"`
Price float64 `json:"Price" db:"price"`
ProductId int `json:"ProductId" db:"product_id"`
PayStatus int `json:"PayStatus" db:"pay_status"`
Applicant string `json:"applicant" db:"applicant"`
Reviewer string `json:"reviewer" db:"reviewer"`
ReviewRemark string `json:"review_remark" db:"review_remark"`
Status int `json:"status" db:"status"`
CreateTime int64 `json:"create_time" db:"create_time"`
ReviewTime int64 `json:"review_time" db:"review_time"`
OriginalPayTime int64 `json:"original_pay_time" db:"original_pay_time"`
OriginalChannelRef string `json:"original_channel_order_id" db:"original_channel_order_id"`
}
func (o *OrderReissueAudit) Apply() error {
o.OrderId = strings.TrimSpace(o.OrderId)
o.ThirdPartyOrderId = strings.TrimSpace(o.ThirdPartyOrderId)
o.Reason = strings.TrimSpace(o.Reason)
if o.Uid <= 0 {
return fmt.Errorf("Uid 不能为空")
}
if o.OrderId == "" {
return fmt.Errorf("订单号不能为空")
}
if o.ThirdPartyOrderId == "" {
return fmt.Errorf("第三方订单号不能为空")
}
if o.Reason == "" {
return fmt.Errorf("补单理由不能为空")
}
if o.AppId <= 0 {
o.AppId = o.Uid / 100000000
}
if o.ServerId <= 0 {
o.ServerId = 1
}
order, err := o.loadSourceOrder()
if err != nil {
return err
}
switch order.PayStatus {
case 0, 3:
// 允许以下两类补单:
// 1. 未支付状态但玩家实际已支付,需要先手动置为已支付再发货。
// 2. 已发货状态但玩家未实际收到,需要回退到已支付后重新发货。
case 1:
return fmt.Errorf("订单已支付,无需补单")
case 2:
return fmt.Errorf("订单支付失败,不能补单")
default:
return fmt.Errorf("当前订单状态不支持补单")
}
auditDb := util.MPool.GetGameDB()
if auditDb == nil {
return fmt.Errorf("failed to get audit db")
}
defer auditDb.Close()
if err := ensureOrderReissueAuditTable(auditDb); err != nil {
return err
}
var exists int
if err := auditDb.QueryRow("SELECT COUNT(*) FROM order_reissue_audit WHERE order_id = ?", o.OrderId).Scan(&exists); err != nil {
return fmt.Errorf("failed to query order reissue audit: %v", err)
}
if exists > 0 {
return fmt.Errorf("该订单已提交过补单申请,不能重复补单")
}
o.Price = order.Price
o.ProductId = order.ProductId
o.PayStatus = order.PayStatus
o.OriginalPayTime = int64(order.PayTime)
o.OriginalChannelRef = order.PayChannelOrderId
o.Status = OrderReissueStatusPending
o.CreateTime = util.Now()
_, err = auditDb.Exec(
"INSERT INTO order_reissue_audit (`uid`, `app_id`, `server_id`, `order_id`, `third_party_order_id`, `reason`, `price`, `product_id`, `pay_status`, `applicant`, `reviewer`, `review_remark`, `status`, `create_time`, `review_time`, `original_pay_time`, `original_channel_order_id`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '', '', ?, ?, 0, ?, ?)",
o.Uid,
o.AppId,
o.ServerId,
o.OrderId,
o.ThirdPartyOrderId,
o.Reason,
o.Price,
o.ProductId,
o.PayStatus,
o.Applicant,
o.Status,
o.CreateTime,
o.OriginalPayTime,
o.OriginalChannelRef,
)
if err != nil {
if strings.Contains(strings.ToLower(err.Error()), "duplicate") {
return fmt.Errorf("该订单已提交过补单申请,不能重复补单")
}
return fmt.Errorf("failed to create order reissue audit: %v", err)
}
return nil
}
func (o *OrderReissueAudit) Approve() error {
auditOrder, auditDb, err := o.getAuditForUpdate()
if err != nil {
return err
}
defer auditDb.Close()
order, err := auditOrder.loadSourceOrder()
if err != nil {
return err
}
if err := auditOrder.prepareSourceOrderForShipping(order); err != nil {
return err
}
if err := base.ShipOrder(base.Param{
Uid: auditOrder.Uid,
AppId: auditOrder.AppId,
ServerId: auditOrder.ServerId,
OrderId: auditOrder.OrderId,
ChannelOrderId: auditOrder.ThirdPartyOrderId,
}); err != nil {
return err
}
_, err = auditDb.Exec(
"UPDATE order_reissue_audit SET `status` = ?, `reviewer` = ?, `review_remark` = ?, `review_time` = ? WHERE `id` = ?",
OrderReissueStatusApproved,
o.Reviewer,
o.ReviewRemark,
util.Now(),
o.AuditId,
)
if err != nil {
return fmt.Errorf("failed to update order reissue audit: %v", err)
}
return nil
}
func (o *OrderReissueAudit) prepareSourceOrderForShipping(order *Order) error {
switch order.PayStatus {
case 0:
return o.updateSourceOrderPaidState(true)
case 1:
return o.updateSourceOrderPaidState(false)
case 3:
return o.updateSourceOrderPaidState(false)
case 2:
return fmt.Errorf("订单支付失败,不能补单")
default:
return fmt.Errorf("当前订单状态不支持补单")
}
}
func (o *OrderReissueAudit) Reject() error {
_, auditDb, err := o.getAuditForUpdate()
if err != nil {
return err
}
defer auditDb.Close()
_, err = auditDb.Exec(
"UPDATE order_reissue_audit SET `status` = ?, `reviewer` = ?, `review_remark` = ?, `review_time` = ? WHERE `id` = ?",
OrderReissueStatusRejected,
o.Reviewer,
o.ReviewRemark,
util.Now(),
o.AuditId,
)
if err != nil {
return fmt.Errorf("failed to reject order reissue audit: %v", err)
}
return nil
}
func loadOrderReissueAuditMap(uid int) (map[string]*OrderReissueAudit, error) {
auditDb := util.MPool.GetGameDB()
if auditDb == nil {
return nil, fmt.Errorf("failed to get audit db")
}
defer auditDb.Close()
if err := ensureOrderReissueAuditTable(auditDb); err != nil {
return nil, err
}
var auditOrders []*OrderReissueAudit
if err := auditDb.Select(&auditOrders, "SELECT `id`, `uid`, `app_id`, `server_id`, `order_id`, `third_party_order_id`, `reason`, `price`, `product_id`, `pay_status`, `applicant`, `reviewer`, `review_remark`, `status`, `create_time`, `review_time`, `original_pay_time`, `original_channel_order_id` FROM order_reissue_audit WHERE uid = ? ORDER BY create_time DESC, id DESC", uid); err != nil {
return nil, fmt.Errorf("failed to query order reissue audits: %v", err)
}
result := make(map[string]*OrderReissueAudit, len(auditOrders))
for _, auditOrder := range auditOrders {
result[auditOrder.OrderId] = auditOrder
}
return result, nil
}
func (o *OrderReissueAudit) loadSourceOrder() (*Order, error) {
if o.AppId <= 0 {
o.AppId = o.Uid / 100000000
}
if o.ServerId <= 0 {
o.ServerId = 1
}
appConfig, err := util.GetAppConfig(o.AppId)
if err != nil {
return nil, err
}
db := util.MPool.GetMysqlDB(appConfig, o.ServerId)
if db == nil {
return nil, fmt.Errorf("failed to get mysql database")
}
defer db.Close()
var order Order
err = db.Get(&order, "SELECT `id`, `Uid`, `ProductId`, `Price`, `CreateTime`, `PayTime`, `PayStatus`, `PayChannelOrderId`, `OrderId`, `PayChannelExtra` FROM t_player_charge WHERE Uid = ? AND OrderId = ? LIMIT 1", o.Uid, o.OrderId)
if err != nil {
return nil, fmt.Errorf("未找到订单信息: %v", err)
}
order.AppId = o.AppId
order.ServerId = o.ServerId
return &order, nil
}
func (o *OrderReissueAudit) updateSourceOrderPaidState(refreshPayTime bool) error {
appConfig, err := util.GetAppConfig(o.AppId)
if err != nil {
return err
}
db := util.MPool.GetMysqlDB(appConfig, o.ServerId)
if db == nil {
return fmt.Errorf("failed to get mysql database")
}
defer db.Close()
if refreshPayTime {
_, err = db.Exec("UPDATE t_player_charge SET `PayStatus` = 1, `PayTime` = ?, `PayChannelOrderId` = ? WHERE `Uid` = ? AND `OrderId` = ?", util.Now(), o.ThirdPartyOrderId, o.Uid, o.OrderId)
} else {
_, err = db.Exec("UPDATE t_player_charge SET `PayStatus` = 1, `PayChannelOrderId` = ? WHERE `Uid` = ? AND `OrderId` = ?", o.ThirdPartyOrderId, o.Uid, o.OrderId)
}
if err != nil {
return fmt.Errorf("failed to update order paid state: %v", err)
}
return nil
}
func (o *OrderReissueAudit) getAuditForUpdate() (*OrderReissueAudit, *util.Db, error) {
if o.AuditId <= 0 {
return nil, nil, fmt.Errorf("audit_id is required")
}
auditDb := util.MPool.GetGameDB()
if auditDb == nil {
return nil, nil, fmt.Errorf("failed to get audit db")
}
if err := ensureOrderReissueAuditTable(auditDb); err != nil {
auditDb.Close()
return nil, nil, err
}
var auditOrder OrderReissueAudit
err := auditDb.Get(&auditOrder, "SELECT `id`, `uid`, `app_id`, `server_id`, `order_id`, `third_party_order_id`, `reason`, `price`, `product_id`, `pay_status`, `applicant`, `reviewer`, `review_remark`, `status`, `create_time`, `review_time`, `original_pay_time`, `original_channel_order_id` FROM order_reissue_audit WHERE id = ?", o.AuditId)
if err != nil {
auditDb.Close()
return nil, nil, fmt.Errorf("failed to get order reissue audit: %v", err)
}
if auditOrder.Status != OrderReissueStatusPending {
auditDb.Close()
if auditOrder.Status == OrderReissueStatusApproved {
return nil, nil, fmt.Errorf("补单申请已审核通过")
}
return nil, nil, fmt.Errorf("补单申请已被驳回")
}
return &auditOrder, auditDb, nil
}
func ensureOrderReissueAuditTable(db *util.Db) error {
_, err := db.Exec(`CREATE TABLE IF NOT EXISTS order_reissue_audit (
id INT NOT NULL AUTO_INCREMENT,
uid BIGINT NOT NULL DEFAULT 0,
app_id INT NOT NULL DEFAULT 0,
server_id INT NOT NULL DEFAULT 0,
order_id VARCHAR(128) NOT NULL DEFAULT '',
third_party_order_id VARCHAR(128) NOT NULL DEFAULT '',
reason VARCHAR(500) NOT NULL DEFAULT '',
price DECIMAL(10, 2) NOT NULL DEFAULT 0,
product_id INT NOT NULL DEFAULT 0,
pay_status INT NOT NULL DEFAULT 0,
applicant VARCHAR(100) NOT NULL DEFAULT '',
reviewer VARCHAR(100) NOT NULL DEFAULT '',
review_remark VARCHAR(500) NOT NULL DEFAULT '',
status INT NOT NULL DEFAULT 1,
create_time BIGINT NOT NULL DEFAULT 0,
review_time BIGINT NOT NULL DEFAULT 0,
original_pay_time BIGINT NOT NULL DEFAULT 0,
original_channel_order_id VARCHAR(128) NOT NULL DEFAULT '',
PRIMARY KEY (id),
UNIQUE KEY uk_order_reissue_order_id (order_id),
KEY idx_order_reissue_uid (uid),
KEY idx_order_reissue_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`)
if err != nil {
return fmt.Errorf("failed to ensure order_reissue_audit table: %v", err)
}
return nil
}