192 lines
7.0 KiB
Go
192 lines
7.0 KiB
Go
package recharge
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
|
|
biz "kra/internal/biz/recharge"
|
|
"kra/internal/data/ledger"
|
|
)
|
|
|
|
type Provider interface{ DB() *gorm.DB }
|
|
type repo struct{ data Provider }
|
|
|
|
func NewRepo(data Provider) biz.Repo { return &repo{data: data} }
|
|
func (r *repo) db(ctx context.Context) *gorm.DB { return r.data.DB().WithContext(ctx) }
|
|
|
|
func (r *repo) Plans(ctx context.Context) ([]*biz.Plan, error) {
|
|
var rows []ledger.RechargePlanPO
|
|
if err := r.db(ctx).Where("enabled = ?", true).Order("sort ASC,id ASC").Find(&rows).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
out := make([]*biz.Plan, 0, len(rows))
|
|
for _, v := range rows {
|
|
out = append(out, &biz.Plan{ID: v.ID, Sort: v.Sort, Diamonds: v.Diamonds, AmountCents: v.AmountCents, Enabled: v.Enabled})
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (r *repo) SavePlans(ctx context.Context, plans []*biz.Plan) error {
|
|
return r.db(ctx).Transaction(func(tx *gorm.DB) error {
|
|
if err := tx.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&ledger.RechargePlanPO{}).Error; err != nil {
|
|
return err
|
|
}
|
|
for _, p := range plans {
|
|
if err := tx.Create(&ledger.RechargePlanPO{Sort: p.Sort, Diamonds: p.Diamonds, AmountCents: p.AmountCents, Enabled: true}).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
type row struct {
|
|
ledger.RechargeOrderPO
|
|
Username string `gorm:"column:username"`
|
|
Nickname string `gorm:"column:nickname"`
|
|
Provider string `gorm:"column:provider"`
|
|
ProviderTradeNo string `gorm:"column:provider_trade_no"`
|
|
PaymentStatus string `gorm:"column:payment_status"`
|
|
FulfillmentStatus string `gorm:"column:fulfillment_status"`
|
|
RefundStatus string `gorm:"column:refund_status"`
|
|
}
|
|
|
|
func query(db *gorm.DB, f biz.Filter) *gorm.DB {
|
|
q := db.Model(&ledger.RechargeOrderPO{}).
|
|
Joins("LEFT JOIN sys_users ON sys_users.id = live_recharge_orders.user_id").
|
|
Joins("LEFT JOIN pay_orders ON pay_orders.business_type = ? AND pay_orders.business_id = live_recharge_orders.order_no AND pay_orders.trade_no = live_recharge_orders.order_no", biz.BusinessType)
|
|
if f.OrderNo != "" {
|
|
q = q.Where("live_recharge_orders.order_no = ?", f.OrderNo)
|
|
}
|
|
if f.UserKeyword != "" {
|
|
v := "%" + f.UserKeyword + "%"
|
|
q = q.Where("sys_users.username LIKE ? OR sys_users.nick_name LIKE ?", v, v)
|
|
}
|
|
if f.PaymentStatus != "" {
|
|
q = q.Where("pay_orders.payment_status = ?", f.PaymentStatus)
|
|
}
|
|
if f.Provider != "" {
|
|
q = q.Where("pay_orders.provider = ?", f.Provider)
|
|
}
|
|
if len(f.CreatedAt) == 2 {
|
|
q = q.Where("live_recharge_orders.created_at BETWEEN ? AND ?", f.CreatedAt[0], f.CreatedAt[1])
|
|
}
|
|
return q
|
|
}
|
|
|
|
const orderSelect = "live_recharge_orders.*,sys_users.username,sys_users.nick_name AS nickname,pay_orders.provider,pay_orders.provider_trade_no,pay_orders.payment_status,pay_orders.fulfillment_status,pay_orders.refund_status"
|
|
|
|
func (r *repo) List(ctx context.Context, f biz.Filter, o biz.ListOptions) ([]*biz.Order, int64, *biz.Summary, error) {
|
|
q := query(r.db(ctx), f)
|
|
var total int64
|
|
if err := q.Count(&total).Error; err != nil {
|
|
return nil, 0, nil, err
|
|
}
|
|
var s struct{ PaidCount, AmountCents int64 }
|
|
if err := query(r.db(ctx), f).Select("COALESCE(SUM(CASE WHEN live_recharge_orders.credited_at IS NOT NULL THEN 1 ELSE 0 END),0) AS paid_count,COALESCE(SUM(CASE WHEN live_recharge_orders.credited_at IS NOT NULL THEN live_recharge_orders.amount_cents ELSE 0 END),0) AS amount_cents").Scan(&s).Error; err != nil {
|
|
return nil, 0, nil, err
|
|
}
|
|
var rows []row
|
|
if err := q.Select(orderSelect).Order(orderBy(o.OrderBy)).Offset(o.Offset).Limit(o.Limit).Scan(&rows).Error; err != nil {
|
|
return nil, 0, nil, err
|
|
}
|
|
out := make([]*biz.Order, 0, len(rows))
|
|
for i := range rows {
|
|
out = append(out, toBiz(&rows[i]))
|
|
}
|
|
return out, total, &biz.Summary{Count: total, PaidCount: s.PaidCount, AmountCents: s.AmountCents}, nil
|
|
}
|
|
|
|
func (r *repo) Get(ctx context.Context, no string) (*biz.Order, error) {
|
|
var v row
|
|
if err := query(r.db(ctx), biz.Filter{OrderNo: no}).Select(orderSelect).Scan(&v).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
if v.ID == 0 {
|
|
return nil, biz.ErrNotFound
|
|
}
|
|
return toBiz(&v), nil
|
|
}
|
|
|
|
func (r *repo) Create(ctx context.Context, userID, planID uint) (*biz.Order, error) {
|
|
var plan ledger.RechargePlanPO
|
|
if err := r.db(ctx).Where("id = ? AND enabled = ?", planID, true).First(&plan).Error; err != nil {
|
|
return nil, biz.ErrInvalid
|
|
}
|
|
rate := int64(10)
|
|
var set ledger.SettingPO
|
|
if err := r.db(ctx).Where(&ledger.SettingPO{Key: "diamond_rate_cents"}).First(&set).Error; err == nil {
|
|
if v, e := strconv.ParseInt(set.Value, 10, 64); e == nil {
|
|
rate = v
|
|
}
|
|
}
|
|
v := ledger.RechargeOrderPO{OrderNo: number("CZ"), UserID: userID, Diamonds: plan.Diamonds, AmountCents: plan.AmountCents, RateCents: rate}
|
|
if err := r.db(ctx).Create(&v).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return r.Get(ctx, v.OrderNo)
|
|
}
|
|
|
|
func (r *repo) MarkPaid(ctx context.Context, no string) (*biz.Order, error) {
|
|
var credited ledger.RechargeOrderPO
|
|
err := r.db(ctx).Transaction(func(tx *gorm.DB) error {
|
|
var v ledger.RechargeOrderPO
|
|
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("order_no = ?", no).First(&v).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return biz.ErrNotFound
|
|
}
|
|
return err
|
|
}
|
|
if v.CreditedAt != nil {
|
|
credited = v
|
|
return nil
|
|
}
|
|
now := time.Now()
|
|
v.CreditedAt = &now
|
|
if err := tx.Save(&v).Error; err != nil {
|
|
return err
|
|
}
|
|
wallet := ledger.WalletPO{UserID: v.UserID}
|
|
if err := tx.Where("user_id = ?", v.UserID).FirstOrCreate(&wallet).Error; err != nil {
|
|
return err
|
|
}
|
|
if err := tx.Model(&wallet).Updates(map[string]any{"diamond_balance": gorm.Expr("diamond_balance + ?", v.Diamonds), "total_recharge_cents": gorm.Expr("total_recharge_cents + ?", v.AmountCents)}).Error; err != nil {
|
|
return err
|
|
}
|
|
credited = v
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return toBiz(&row{RechargeOrderPO: credited}), nil
|
|
}
|
|
|
|
func toBiz(v *row) *biz.Order {
|
|
return &biz.Order{
|
|
ID: v.ID, OrderNo: v.OrderNo, UserID: v.UserID, Username: v.Username, Nickname: v.Nickname,
|
|
Diamonds: v.Diamonds, AmountCents: v.AmountCents, Provider: v.Provider, ProviderTradeNo: v.ProviderTradeNo,
|
|
PaymentStatus: v.PaymentStatus, FulfillmentStatus: v.FulfillmentStatus, RefundStatus: v.RefundStatus,
|
|
CreatedAt: v.CreatedAt, CreditedAt: v.CreditedAt,
|
|
}
|
|
}
|
|
|
|
func orderBy(v string) string {
|
|
m := map[string]string{"diamonds_asc": "live_recharge_orders.diamonds ASC", "diamonds_desc": "live_recharge_orders.diamonds DESC", "amount_asc": "live_recharge_orders.amount_cents ASC", "amount_desc": "live_recharge_orders.amount_cents DESC", "created_at_asc": "live_recharge_orders.created_at ASC", "created_at_desc": "live_recharge_orders.created_at DESC", "id_asc": "live_recharge_orders.id ASC", "id_desc": "live_recharge_orders.id DESC"}
|
|
if x := m[strings.ToLower(v)]; x != "" {
|
|
return x
|
|
}
|
|
return "live_recharge_orders.created_at DESC"
|
|
}
|
|
|
|
func number(p string) string {
|
|
return fmt.Sprintf("%s%s%06d", p, time.Now().Format("20060102150405"), time.Now().UnixNano()%1000000)
|
|
}
|