Files
observer/server/biz/dao/payment_order.go
T

167 lines
5.5 KiB
Go

package dao
import (
"context"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"observer-server/biz/consts"
"observer-server/biz/model/entity"
"observer-server/common"
)
// PaymentOrder 支付订单表 DAO:写频繁表不走查询缓存;
// 所有写链路(含读改写事务)必须经 common.Serial() 串行执行。
type paymentOrderDao struct{}
var PaymentOrder = &paymentOrderDao{}
func init() {
ctx := context.Background()
common.DropLegacyTableIfHasColumn(ctx, consts.TablePaymentOrder, "device_id")
_, err := g.DB().Exec(ctx, `CREATE TABLE IF NOT EXISTS payment_order (
order_id TEXT PRIMARY KEY,
phone_num TEXT NOT NULL,
plan_id TEXT NOT NULL,
channel TEXT NOT NULL,
amount_cents INTEGER NOT NULL,
status TEXT NOT NULL DEFAULT 'created',
wx_trade_no TEXT UNIQUE,
alipay_trade_no TEXT UNIQUE,
created_at TEXT NOT NULL,
paid_at TEXT
)`)
if err != nil {
panic(err)
}
_, err = g.DB().Exec(ctx, "CREATE INDEX IF NOT EXISTS idx_payment_order_phone ON payment_order(phone_num)")
if err != nil {
panic(err)
}
}
// InsertOneInTx 事务内插入订单
func (d *paymentOrderDao) InsertOneInTx(ctx context.Context, tx gdb.TX, m *entity.PaymentOrder) error {
_, err := g.DB().Model(consts.TablePaymentOrder).Ctx(ctx).TX(tx).Insert(m)
return err
}
// GetByPk 按订单号查询(普通读,不走缓存)
func (d *paymentOrderDao) GetByPk(ctx context.Context, orderId string) (*entity.PaymentOrder, error) {
return d.getByPk(ctx, nil, orderId)
}
// GetByPkInTx 事务内按订单号查询
func (d *paymentOrderDao) GetByPkInTx(ctx context.Context, tx gdb.TX, orderId string) (*entity.PaymentOrder, error) {
return d.getByPk(ctx, tx, orderId)
}
func (d *paymentOrderDao) getByPk(ctx context.Context, tx gdb.TX, orderId string) (*entity.PaymentOrder, error) {
m := g.DB().Model(consts.TablePaymentOrder).Ctx(ctx)
if tx != nil {
m = m.TX(tx)
}
var e entity.PaymentOrder
if err := m.Where("order_id", orderId).Scan(&e); err != nil {
if common.IsNoRows(err) {
return nil, nil
}
return nil, err
}
return &e, nil
}
// UpdatePaidWechatInTx 事务内标记微信订单已支付(状态机校验在 service 完成)
func (d *paymentOrderDao) UpdatePaidWechatInTx(ctx context.Context, tx gdb.TX, orderId, wxTradeNo string) error {
_, err := g.DB().Model(consts.TablePaymentOrder).Ctx(ctx).TX(tx).
Where("order_id", orderId).
Data(gdb.Map{"status": consts.OrderStatusPaid, "wx_trade_no": wxTradeNo, "paid_at": gtime.Now()}).
Update()
return err
}
// UpdatePaidAlipayInTx 事务内标记支付宝订单已支付
func (d *paymentOrderDao) UpdatePaidAlipayInTx(ctx context.Context, tx gdb.TX, orderId, alipayTradeNo string) error {
_, err := g.DB().Model(consts.TablePaymentOrder).Ctx(ctx).TX(tx).
Where("order_id", orderId).
Data(gdb.Map{"status": consts.OrderStatusPaid, "alipay_trade_no": alipayTradeNo, "paid_at": gtime.Now()}).
Update()
return err
}
// UpdateClosedInTx 事务内关闭订单(渠道下单失败回滚 / 惰性回收)
func (d *paymentOrderDao) UpdateClosedInTx(ctx context.Context, tx gdb.TX, orderId string) error {
_, err := g.DB().Model(consts.TablePaymentOrder).Ctx(ctx).TX(tx).
Where("order_id", orderId).
Data(gdb.Map{"status": consts.OrderStatusClosed}).
Update()
return err
}
// CloseStaleCreatedInTx 惰性关闭账号下超时未支付的 created 订单
func (d *paymentOrderDao) CloseStaleCreatedInTx(ctx context.Context, tx gdb.TX, phoneNum string, before *gtime.Time) error {
_, err := g.DB().Model(consts.TablePaymentOrder).Ctx(ctx).TX(tx).
Where("phone_num", phoneNum).
Where("status", consts.OrderStatusCreated).
WhereLT("created_at", before).
Data(gdb.Map{"status": consts.OrderStatusClosed}).
Update()
return err
}
// GetRecentCreatedInTx 事务内查账号在 since 之后创建的未支付订单(防重复扣款兜底,返回最早一条)
func (d *paymentOrderDao) GetRecentCreatedInTx(ctx context.Context, tx gdb.TX, phoneNum string, since *gtime.Time) (*entity.PaymentOrder, error) {
var e entity.PaymentOrder
err := g.DB().Model(consts.TablePaymentOrder).Ctx(ctx).TX(tx).
Where("phone_num", phoneNum).
Where("status", consts.OrderStatusCreated).
WhereGTE("created_at", since).
OrderAsc("created_at").
Limit(1).
Scan(&e)
if err != nil {
if common.IsNoRows(err) {
return nil, nil
}
return nil, err
}
return &e, nil
}
// PageByFilter 管理端订单分页查询:按条件精确筛选(phoneNum/status 精确、时间区间含界),
// 写频繁表不走缓存;返回同条件总数与当前页列表(按下单时间倒序)。
func (d *paymentOrderDao) PageByFilter(ctx context.Context, phoneNum, status string,
startAt, endAt *gtime.Time, page, size int) ([]*entity.PaymentOrder, int64, error) {
base := func() *gdb.Model {
m := g.DB().Model(consts.TablePaymentOrder).Ctx(ctx)
if phoneNum != "" {
m = m.Where("phone_num", phoneNum)
}
if status != "" {
m = m.Where("status", status)
}
if startAt != nil {
m = m.WhereGTE("created_at", startAt)
}
if endAt != nil {
m = m.WhereLTE("created_at", endAt)
}
return m
}
total, err := base().Count()
if err != nil {
return nil, 0, err
}
var list []*entity.PaymentOrder
err = base().OrderDesc("created_at").Limit((page-1)*size, size).Scan(&list)
if err != nil {
if common.IsNoRows(err) {
return []*entity.PaymentOrder{}, int64(total), nil
}
return nil, 0, err
}
return list, int64(total), nil
}