167 lines
5.5 KiB
Go
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
|
|
}
|