优化表结构,重构模型,重新实现基于白银网关的提取节点流程
This commit is contained in:
@@ -1,5 +1,11 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
m "platform/web/models"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
var Bill = &billService{}
|
||||
|
||||
type billService struct{}
|
||||
@@ -7,3 +13,31 @@ type billService struct{}
|
||||
func (s *billService) GenNo() string {
|
||||
return ID.GenReadable("bil")
|
||||
}
|
||||
|
||||
func newForRecharge(uid int32, billNo string, info string, amount decimal.Decimal, trade *m.Trade) *m.Bill {
|
||||
return &m.Bill{
|
||||
UserID: uid,
|
||||
BillNo: billNo,
|
||||
TradeID: &trade.ID,
|
||||
Type: m.BillTypeRecharge,
|
||||
Info: &info,
|
||||
Amount: amount,
|
||||
}
|
||||
}
|
||||
|
||||
func newForConsume(uid int32, billNo string, info string, amount decimal.Decimal, resource *m.Resource, trade ...*m.Trade) *m.Bill {
|
||||
var bill = &m.Bill{
|
||||
UserID: uid,
|
||||
BillNo: billNo,
|
||||
ResourceID: &resource.ID,
|
||||
Type: m.BillTypeConsume,
|
||||
Info: &info,
|
||||
Amount: amount,
|
||||
}
|
||||
|
||||
if len(trade) > 0 {
|
||||
bill.TradeID = &trade[0].ID
|
||||
}
|
||||
|
||||
return bill
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
308
web/services/channel_baiyin.go
Normal file
308
web/services/channel_baiyin.go
Normal file
@@ -0,0 +1,308 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/netip"
|
||||
"platform/pkg/env"
|
||||
"platform/pkg/u"
|
||||
"platform/web/core"
|
||||
e "platform/web/events"
|
||||
g "platform/web/globals"
|
||||
"platform/web/globals/orm"
|
||||
m "platform/web/models"
|
||||
q "platform/web/queries"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hibiken/asynq"
|
||||
)
|
||||
|
||||
type channelBaiyinService struct{}
|
||||
|
||||
func (s *channelBaiyinService) CreateChannels(source netip.Addr, userId int32, resourceId int32, authWhitelist bool, authPassword bool, count int, edgeFilter ...EdgeFilter) ([]*m.Channel, error) {
|
||||
var filter *EdgeFilter = nil
|
||||
if len(edgeFilter) > 0 {
|
||||
filter = &edgeFilter[0]
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
batch := ID.GenReadable("bat")
|
||||
|
||||
// 获取用户白名单并检查用户 ip 地址
|
||||
whitelists, err := q.Whitelist.Where(
|
||||
q.Whitelist.UserID.Eq(userId),
|
||||
).Find()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
whitelistIPs := make([]string, len(whitelists))
|
||||
pass := false
|
||||
for i, item := range whitelists {
|
||||
whitelistIPs[i] = item.IP.String()
|
||||
if item.IP.Compare(source) != 0 {
|
||||
pass = true
|
||||
}
|
||||
}
|
||||
if !pass {
|
||||
return nil, core.NewBizErr("IP 地址不在白名单内")
|
||||
}
|
||||
|
||||
// 获取用户套餐并检查
|
||||
resource, err := findResource(q.Q, resourceId, userId, count, now)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expire := now.Add(resource.Live)
|
||||
|
||||
// 获取可用通道
|
||||
chans, err := lockChans(batch, count, expire)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取对应代理
|
||||
ips := make([]driver.Valuer, 0)
|
||||
findProxy := make(map[orm.Inet]*m.Proxy)
|
||||
for _, ch := range chans {
|
||||
ip := orm.Inet{Addr: ch.Addr()}
|
||||
if _, ok := findProxy[ip]; !ok {
|
||||
ips = append(ips, ip)
|
||||
findProxy[ip] = nil
|
||||
}
|
||||
}
|
||||
|
||||
proxies, err := q.Proxy.Where(
|
||||
q.Proxy.Type.Eq(int(m.ProxyTypeBaiYin)),
|
||||
q.Proxy.Status.Eq(int(m.ProxyStatusOnline)),
|
||||
q.Proxy.IP.In(ips...),
|
||||
).Find()
|
||||
if err != nil {
|
||||
return nil, core.NewBizErr("获取代理失败", err)
|
||||
}
|
||||
|
||||
groups := make(map[*m.Proxy][]*m.Channel)
|
||||
for _, proxy := range proxies {
|
||||
findProxy[proxy.IP] = proxy
|
||||
groups[proxy] = make([]*m.Channel, 0)
|
||||
}
|
||||
|
||||
// 准备通道数据
|
||||
actions := make([]*m.LogsUserUsage, len(chans))
|
||||
channels := make([]*m.Channel, len(chans))
|
||||
for i, ch := range chans {
|
||||
if err != nil {
|
||||
return nil, core.NewBizErr("解析通道地址失败", err)
|
||||
}
|
||||
|
||||
// 使用记录
|
||||
actions[i] = &m.LogsUserUsage{
|
||||
UserID: userId,
|
||||
ResourceID: resourceId,
|
||||
Count: int32(count),
|
||||
ISP: u.P(filter.Isp.String()),
|
||||
Prov: filter.Prov,
|
||||
City: filter.City,
|
||||
}
|
||||
|
||||
// 通道数据
|
||||
inet := orm.Inet{Addr: ch.Addr()}
|
||||
channels[i] = &m.Channel{
|
||||
UserID: userId,
|
||||
ResourceID: resourceId,
|
||||
BatchNo: batch,
|
||||
ProxyID: findProxy[inet].ID,
|
||||
Port: ch.Port(),
|
||||
FilterISP: filter.Isp,
|
||||
FilterProv: filter.Prov,
|
||||
FilterCity: filter.City,
|
||||
ExpiredAt: expire,
|
||||
}
|
||||
if authWhitelist {
|
||||
channels[i].Whitelists = &orm.Slice[string]{Arr: whitelistIPs}
|
||||
}
|
||||
if authPassword {
|
||||
username, password := genPassPair()
|
||||
channels[i].Username = &username
|
||||
channels[i].Password = &password
|
||||
}
|
||||
|
||||
// 关联代理
|
||||
proxy := findProxy[inet]
|
||||
groups[proxy] = append(groups[proxy], channels[i])
|
||||
}
|
||||
|
||||
// 保存数据
|
||||
err = q.Q.Transaction(func(q *q.Query) error {
|
||||
|
||||
// 更新套餐用量
|
||||
used := int32(count)
|
||||
if u.IsSameDate(now, resource.DailyLast) {
|
||||
used += resource.DailyUsed
|
||||
}
|
||||
|
||||
switch resource.Type {
|
||||
case m.ResourceTypeShort:
|
||||
_, err = q.ResourceShort.
|
||||
Where(
|
||||
q.ResourceShort.ResourceID.Eq(resource.Id),
|
||||
q.ResourceShort.Used.Eq(resource.Used),
|
||||
q.ResourceShort.DailyUsed.Eq(resource.DailyUsed),
|
||||
q.ResourceShort.DailyLast.Eq(resource.DailyLast),
|
||||
).
|
||||
UpdateSimple(
|
||||
q.ResourceShort.Used.Add(int32(count)),
|
||||
q.ResourceShort.DailyUsed.Value(used),
|
||||
q.ResourceShort.DailyLast.Value(now),
|
||||
)
|
||||
case m.ResourceTypeLong:
|
||||
_, err = q.ResourceLong.
|
||||
Where(
|
||||
q.ResourceLong.ResourceID.Eq(resource.Id),
|
||||
q.ResourceLong.Used.Eq(resource.Used),
|
||||
q.ResourceLong.DailyUsed.Eq(resource.DailyUsed),
|
||||
q.ResourceLong.DailyLast.Eq(resource.DailyLast),
|
||||
).
|
||||
UpdateSimple(
|
||||
q.ResourceLong.Used.Add(int32(count)),
|
||||
q.ResourceLong.DailyUsed.Value(used),
|
||||
q.ResourceLong.DailyLast.Value(now),
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
return core.NewServErr("更新套餐使用记录失败", err)
|
||||
}
|
||||
|
||||
// 保存通道和分配记录
|
||||
err = q.Channel.Create(channels...)
|
||||
if err != nil {
|
||||
return core.NewServErr("保存通道失败", err)
|
||||
}
|
||||
|
||||
err = q.LogsUserUsage.Create(actions...)
|
||||
if err != nil {
|
||||
return core.NewServErr("保存用户使用记录失败", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 提交异步任务关闭通道
|
||||
_, err = g.Asynq.Enqueue(
|
||||
e.NewRemoveChannel(e.RemoveChannelData{
|
||||
Batch: batch,
|
||||
IDs: core.GetIDs(channels),
|
||||
}),
|
||||
asynq.ProcessAt(expire),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, core.NewServErr("提交关闭通道任务失败", err)
|
||||
}
|
||||
|
||||
// 提交配置
|
||||
for proxy, chanels := range groups {
|
||||
secret := strings.Split(u.Z(proxy.Secret), ":")
|
||||
gateway := g.NewGateway(proxy.IP.String(), secret[0], secret[1])
|
||||
|
||||
configs := make([]g.PortConfigsReq, len(chanels))
|
||||
for i, channel := range chanels {
|
||||
configs[i] = g.PortConfigsReq{
|
||||
Port: int(channel.Port),
|
||||
Status: true,
|
||||
AutoEdgeConfig: &g.AutoEdgeConfig{
|
||||
Isp: channel.FilterISP.String(),
|
||||
Province: u.Z(channel.FilterProv),
|
||||
City: u.Z(channel.FilterCity),
|
||||
},
|
||||
}
|
||||
if authWhitelist {
|
||||
configs[i].Whitelist = &channel.Whitelists.Arr
|
||||
}
|
||||
if authPassword {
|
||||
configs[i].Userpass = u.P(fmt.Sprintf("%s:%s", *channel.Username, *channel.Password))
|
||||
}
|
||||
}
|
||||
|
||||
if env.DebugExternalChange {
|
||||
err := gateway.GatewayPortConfigs(configs)
|
||||
if err != nil {
|
||||
return nil, core.NewServErr(fmt.Sprintf("配置代理 %s 端口失败", proxy.IP.String()), err)
|
||||
}
|
||||
} else {
|
||||
bytes, _ := json.Marshal(configs)
|
||||
slog.Debug("提交代理端口配置", "config", string(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
return channels, nil
|
||||
}
|
||||
|
||||
func (s *channelBaiyinService) RemoveChannels(batch string, ids []int32) error {
|
||||
|
||||
// 获取连接数据
|
||||
channels, err := q.Channel.Debug().
|
||||
Preload(q.Channel.Proxy).
|
||||
Where(q.Channel.ID.In(ids...)).
|
||||
Find()
|
||||
if err != nil {
|
||||
return core.NewServErr("获取通道数据失败", err)
|
||||
}
|
||||
if len(channels) != len(ids) {
|
||||
return core.NewServErr("获取通道数据不完整", err)
|
||||
}
|
||||
|
||||
proxies := make(map[string]*m.Proxy, len(channels))
|
||||
groups := make(map[string][]*m.Channel, len(channels))
|
||||
chans := make([]string, len(channels))
|
||||
for i, channel := range channels {
|
||||
ip := channel.Proxy.IP.String()
|
||||
groups[ip] = append(groups[ip], channel)
|
||||
proxies[ip] = &channel.Proxy
|
||||
chans[i] = fmt.Sprintf("%s:%d", ip, channel.Port)
|
||||
}
|
||||
|
||||
addrs := make([]netip.AddrPort, len(channels))
|
||||
for i, channel := range channels {
|
||||
addrs[i] = netip.AddrPortFrom(channel.Proxy.IP.Addr, channel.Port)
|
||||
}
|
||||
|
||||
// 释放端口
|
||||
err = freeChans(batch, chans)
|
||||
if err != nil {
|
||||
return core.NewServErr("释放端口失败", err)
|
||||
}
|
||||
|
||||
// 清空配置
|
||||
for ip, channels := range groups {
|
||||
proxy := proxies[ip]
|
||||
secret := strings.Split(*proxy.Secret, ":")
|
||||
gateway := g.NewGateway(ip, secret[0], secret[1])
|
||||
|
||||
configs := make([]g.PortConfigsReq, len(channels))
|
||||
for i, channel := range channels {
|
||||
configs[i] = g.PortConfigsReq{
|
||||
Status: false,
|
||||
Port: int(channel.Port),
|
||||
Edge: &[]string{},
|
||||
}
|
||||
}
|
||||
|
||||
if env.DebugExternalChange {
|
||||
err := gateway.GatewayPortConfigs(configs)
|
||||
if err != nil {
|
||||
return core.NewServErr(fmt.Sprintf("清空代理 %s 端口配置失败", proxy.IP.String()), err)
|
||||
}
|
||||
} else {
|
||||
bytes, _ := json.Marshal(configs)
|
||||
slog.Debug("清除代理端口配置", "config", bytes)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
edge2 "platform/web/domains/edge"
|
||||
m "platform/web/models"
|
||||
q "platform/web/queries"
|
||||
)
|
||||
@@ -12,14 +11,14 @@ type edgeService struct{}
|
||||
|
||||
func (s *edgeService) AllEdges(count int, filter EdgeFilter) ([]*m.Edge, error) {
|
||||
do := q.Edge.Where()
|
||||
if filter.Prov != "" {
|
||||
do = do.Where(q.Edge.Prov.Eq(filter.Prov))
|
||||
if filter.Prov != nil {
|
||||
do = do.Where(q.Edge.Prov.Eq(*filter.Prov))
|
||||
}
|
||||
if filter.City != "" {
|
||||
do = do.Where(q.Edge.City.Eq(filter.City))
|
||||
if filter.City != nil {
|
||||
do = do.Where(q.Edge.City.Eq(*filter.City))
|
||||
}
|
||||
if filter.Isp != "" {
|
||||
do = do.Where(q.Edge.Isp.Eq(int32(edge2.ISPFromStr(filter.Isp))))
|
||||
if filter.Isp != nil {
|
||||
do = do.Where(q.Edge.ISP.Eq(int(*filter.Isp)))
|
||||
}
|
||||
if count > 0 {
|
||||
do = do.Limit(count)
|
||||
@@ -33,11 +32,8 @@ func (s *edgeService) AllEdges(count int, filter EdgeFilter) ([]*m.Edge, error)
|
||||
return edges, nil
|
||||
}
|
||||
|
||||
type EdgeInfo struct {
|
||||
}
|
||||
|
||||
type EdgeFilter struct {
|
||||
Isp string `json:"isp"`
|
||||
Prov string `json:"prov"`
|
||||
City string `json:"city"`
|
||||
Isp *m.EdgeISP `json:"isp"`
|
||||
Prov *string `json:"prov"`
|
||||
City *string `json:"city"`
|
||||
}
|
||||
|
||||
25
web/services/proxy.go
Normal file
25
web/services/proxy.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
m "platform/web/models"
|
||||
q "platform/web/queries"
|
||||
"time"
|
||||
)
|
||||
|
||||
var Proxy = &proxyService{}
|
||||
|
||||
type proxyService struct{}
|
||||
|
||||
func (s *proxyService) AllProxies(proxyType m.ProxyType, channels bool) ([]*m.Proxy, error) {
|
||||
proxies, err := q.Proxy.Where(
|
||||
q.Proxy.Type.Eq(int(proxyType)),
|
||||
q.Proxy.Status.Eq(int(m.ProxyStatusOnline)),
|
||||
).Preload(
|
||||
q.Proxy.Channels.On(q.Channel.ExpiredAt.Gte(time.Now())),
|
||||
).Find()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return proxies, nil
|
||||
}
|
||||
@@ -5,11 +5,7 @@ import (
|
||||
"fmt"
|
||||
"platform/pkg/u"
|
||||
"platform/web/core"
|
||||
bill2 "platform/web/domains/bill"
|
||||
resource2 "platform/web/domains/resource"
|
||||
trade2 "platform/web/domains/trade"
|
||||
g "platform/web/globals"
|
||||
"platform/web/globals/orm"
|
||||
m "platform/web/models"
|
||||
q "platform/web/queries"
|
||||
"time"
|
||||
@@ -24,7 +20,7 @@ type resourceService struct{}
|
||||
func (s *resourceService) CreateResourceByBalance(uid int32, now time.Time, data *CreateResourceData) error {
|
||||
return g.Redsync.WithLock(userBalanceKey(uid), func() error {
|
||||
return q.Q.Transaction(func(q *q.Query) error {
|
||||
// 检查用户
|
||||
// 找到用户
|
||||
user, err := q.User.
|
||||
Where(q.User.ID.Eq(uid)).
|
||||
Take()
|
||||
@@ -38,22 +34,22 @@ func (s *resourceService) CreateResourceByBalance(uid int32, now time.Time, data
|
||||
return ErrBalanceNotEnough
|
||||
}
|
||||
|
||||
// 更新用户余额
|
||||
_, err = q.User.
|
||||
Where(q.User.ID.Eq(uid), q.User.Balance.Eq(user.Balance)).
|
||||
UpdateSimple(q.User.Balance.Value(amount))
|
||||
if err != nil {
|
||||
return core.NewServErr("更新用户余额失败", err)
|
||||
}
|
||||
|
||||
// 保存套餐
|
||||
resource, err := createResource(q, uid, now, data)
|
||||
if err != nil {
|
||||
return core.NewServErr("创建套餐失败", err)
|
||||
}
|
||||
|
||||
// 更新用户余额
|
||||
_, err = q.User.
|
||||
Where(q.User.ID.Eq(uid)).
|
||||
UpdateSimple(q.User.Balance.Value(amount))
|
||||
if err != nil {
|
||||
return core.NewServErr("更新用户余额失败", err)
|
||||
}
|
||||
|
||||
// 生成账单
|
||||
err = q.Bill.Create(bill2.NewForConsume(uid, Bill.GenNo(), data.GetSubject(), data.GetAmount(), resource))
|
||||
err = q.Bill.Create(newForConsume(uid, Bill.GenNo(), data.GetSubject(), data.GetAmount(), resource))
|
||||
if err != nil {
|
||||
return core.NewServErr("生成账单失败", err)
|
||||
}
|
||||
@@ -73,7 +69,7 @@ func (s *resourceService) CreateResourceByTrade(uid int32, now time.Time, data *
|
||||
}
|
||||
|
||||
// 生成账单
|
||||
err = q.Bill.Create(bill2.NewForConsume(uid, Bill.GenNo(), data.GetSubject(), data.GetAmount(), resource, trade))
|
||||
err = q.Bill.Create(newForConsume(uid, Bill.GenNo(), data.GetSubject(), data.GetAmount(), resource, trade))
|
||||
if err != nil {
|
||||
return core.NewServErr("生成账单失败", err)
|
||||
}
|
||||
@@ -89,38 +85,37 @@ func createResource(q *q.Query, uid int32, now time.Time, data *CreateResourceDa
|
||||
UserID: uid,
|
||||
ResourceNo: u.P(ID.GenReadable("res")),
|
||||
Active: true,
|
||||
Type: data.Type,
|
||||
}
|
||||
switch data.Type {
|
||||
|
||||
// 短效套餐
|
||||
case resource2.TypeShort:
|
||||
case m.ResourceTypeShort:
|
||||
var short = data.Short
|
||||
if short == nil {
|
||||
return nil, core.NewBizErr("短效套餐数据不能为空")
|
||||
}
|
||||
var duration = time.Duration(short.Expire) * 24 * time.Hour
|
||||
resource.Type = int32(resource2.TypeShort)
|
||||
resource.Short = &m.ResourceShort{
|
||||
Type: short.Mode,
|
||||
Live: short.Live,
|
||||
Quota: &short.Quota,
|
||||
Expire: u.P(orm.LocalDateTime(now.Add(duration))),
|
||||
Expire: u.P(now.Add(duration)),
|
||||
DailyLimit: short.DailyLimit,
|
||||
}
|
||||
|
||||
// 长效套餐
|
||||
case resource2.TypeLong:
|
||||
case m.ResourceTypeLong:
|
||||
var long = data.Long
|
||||
if long == nil {
|
||||
return nil, core.NewBizErr("长效套餐数据不能为空")
|
||||
}
|
||||
var duration = time.Duration(long.Expire) * 24 * time.Hour
|
||||
resource.Type = int32(resource2.TypeLong)
|
||||
resource.Long = &m.ResourceLong{
|
||||
Type: long.Mode,
|
||||
Live: long.Live,
|
||||
Quota: &long.Quota,
|
||||
Expire: u.P(orm.LocalDateTime(now.Add(duration))),
|
||||
Expire: u.P(now.Add(duration)),
|
||||
DailyLimit: long.DailyLimit,
|
||||
}
|
||||
default:
|
||||
@@ -136,42 +131,42 @@ func createResource(q *q.Query, uid int32, now time.Time, data *CreateResourceDa
|
||||
}
|
||||
|
||||
type CreateResourceData struct {
|
||||
Type resource2.Type `json:"type" validate:"required"`
|
||||
Type m.ResourceType `json:"type" validate:"required"`
|
||||
Short *CreateShortResourceData `json:"short,omitempty"`
|
||||
Long *CreateLongResourceData `json:"long,omitempty"`
|
||||
}
|
||||
|
||||
type CreateShortResourceData struct {
|
||||
Live int32 `json:"live" validate:"required,min=180"`
|
||||
Mode int32 `json:"mode" validate:"required"`
|
||||
Expire int32 `json:"expire"`
|
||||
DailyLimit int32 `json:"daily_limit" validate:"min=2000"`
|
||||
Quota int32 `json:"quota" validate:"min=10000"`
|
||||
Live int32 `json:"live" validate:"required,min=180"`
|
||||
Mode m.ResourceMode `json:"mode" validate:"required"`
|
||||
Expire int32 `json:"expire"`
|
||||
DailyLimit int32 `json:"daily_limit" validate:"min=2000"`
|
||||
Quota int32 `json:"quota" validate:"min=10000"`
|
||||
|
||||
name string
|
||||
price *decimal.Decimal
|
||||
}
|
||||
|
||||
type CreateLongResourceData struct {
|
||||
Live int32 `json:"live" validate:"required,oneof=1 4 8 12 24"`
|
||||
Mode int32 `json:"mode" validate:"required,oneof=1 2"`
|
||||
Expire int32 `json:"expire"`
|
||||
DailyLimit int32 `json:"daily_limit" validate:"min=100"`
|
||||
Quota int32 `json:"quota" validate:"min=500"`
|
||||
Live int32 `json:"live" validate:"required,oneof=1 4 8 12 24"`
|
||||
Mode m.ResourceMode `json:"mode" validate:"required,oneof=1 2"`
|
||||
Expire int32 `json:"expire"`
|
||||
DailyLimit int32 `json:"daily_limit" validate:"min=100"`
|
||||
Quota int32 `json:"quota" validate:"min=500"`
|
||||
|
||||
name string
|
||||
price *decimal.Decimal
|
||||
}
|
||||
|
||||
func (c *CreateResourceData) GetType() trade2.Type {
|
||||
return trade2.TypePurchase
|
||||
func (c *CreateResourceData) GetType() m.TradeType {
|
||||
return m.TradeTypePurchase
|
||||
}
|
||||
|
||||
func (c *CreateResourceData) GetSubject() string {
|
||||
switch c.Type {
|
||||
case resource2.TypeShort:
|
||||
case m.ResourceTypeShort:
|
||||
return c.Short.GetSubject()
|
||||
case resource2.TypeLong:
|
||||
case m.ResourceTypeLong:
|
||||
return c.Long.GetSubject()
|
||||
}
|
||||
panic("类型对应的数据为空")
|
||||
@@ -179,9 +174,9 @@ func (c *CreateResourceData) GetSubject() string {
|
||||
|
||||
func (c *CreateResourceData) GetAmount() decimal.Decimal {
|
||||
switch c.Type {
|
||||
case resource2.TypeShort:
|
||||
case m.ResourceTypeShort:
|
||||
return c.Short.GetAmount()
|
||||
case resource2.TypeLong:
|
||||
case m.ResourceTypeLong:
|
||||
return c.Long.GetAmount()
|
||||
}
|
||||
panic("类型对应的数据为空")
|
||||
@@ -250,11 +245,11 @@ func (data *CreateLongResourceData) GetSubject() string {
|
||||
func (data *CreateLongResourceData) GetAmount() decimal.Decimal {
|
||||
if data.price == nil {
|
||||
var factor int32 = 0
|
||||
switch resource2.Mode(data.Mode) {
|
||||
switch data.Mode {
|
||||
|
||||
case resource2.ModeTime:
|
||||
case m.ResourceModeTime:
|
||||
factor = data.Expire * data.DailyLimit
|
||||
case resource2.ModeCount:
|
||||
case m.ResourceModeQuota:
|
||||
factor = data.Quota
|
||||
}
|
||||
|
||||
@@ -283,14 +278,14 @@ func (data *CreateLongResourceData) GetAmount() decimal.Decimal {
|
||||
|
||||
type ResourceOnTradeComplete struct{}
|
||||
|
||||
func (r ResourceOnTradeComplete) Check(t trade2.Type) (trade2.ProductInfo, bool) {
|
||||
if t == trade2.TypePurchase {
|
||||
func (r ResourceOnTradeComplete) Check(t m.TradeType) (ProductInfo, bool) {
|
||||
if t == m.TradeTypePurchase {
|
||||
return &CreateResourceData{}, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (r ResourceOnTradeComplete) OnTradeComplete(info trade2.ProductInfo, trade *m.Trade) error {
|
||||
func (r ResourceOnTradeComplete) OnTradeComplete(info ProductInfo, trade *m.Trade) error {
|
||||
return Resource.CreateResourceByTrade(trade.UserID, time.Time(*trade.CompletedAt), info.(*CreateResourceData), trade)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,11 +10,8 @@ import (
|
||||
"platform/pkg/env"
|
||||
"platform/pkg/u"
|
||||
"platform/web/core"
|
||||
coupon2 "platform/web/domains/coupon"
|
||||
trade2 "platform/web/domains/trade"
|
||||
e "platform/web/events"
|
||||
g "platform/web/globals"
|
||||
"platform/web/globals/orm"
|
||||
m "platform/web/models"
|
||||
q "platform/web/queries"
|
||||
"time"
|
||||
@@ -53,7 +50,7 @@ func (s *tradeService) CreateTrade(uid int32, now time.Time, data *CreateTradeDa
|
||||
coupon, err := q.Coupon.
|
||||
Where(
|
||||
q.Coupon.Code.Eq(*data.CouponCode),
|
||||
q.Coupon.Status.Eq(int32(coupon2.StatusUnused)),
|
||||
q.Coupon.Status.Eq(int(m.CouponStatusUnused)),
|
||||
).
|
||||
Take()
|
||||
if err != nil {
|
||||
@@ -67,7 +64,7 @@ func (s *tradeService) CreateTrade(uid int32, now time.Time, data *CreateTradeDa
|
||||
if !expireAt.IsZero() && expireAt.Before(now) {
|
||||
_, err = q.Coupon.
|
||||
Where(q.Coupon.ID.Eq(coupon.ID)).
|
||||
Update(q.Coupon.Status, coupon2.StatusExpired)
|
||||
Update(q.Coupon.Status, m.CouponStatusExpired)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -86,7 +83,7 @@ func (s *tradeService) CreateTrade(uid int32, now time.Time, data *CreateTradeDa
|
||||
if expireAt.IsZero() {
|
||||
_, err = q.Coupon.
|
||||
Where(q.Coupon.ID.Eq(coupon.ID)).
|
||||
Update(q.Coupon.Status, int32(coupon2.StatusUsed))
|
||||
Update(q.Coupon.Status, int(m.CouponStatusUsed))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -112,7 +109,7 @@ func (s *tradeService) CreateTrade(uid int32, now time.Time, data *CreateTradeDa
|
||||
switch {
|
||||
|
||||
// 支付宝 + 电脑网站
|
||||
case method == trade2.MethodAlipay && platform == trade2.PlatformDesktop:
|
||||
case method == m.TradeMethodAlipay && platform == m.TradePlatformPC:
|
||||
resp, err := g.Alipay.TradePagePay(alipay.TradePagePay{
|
||||
QRPayMode: "4",
|
||||
QRCodeWidth: "196", // 二维码宽度需要-4,支付宝页面布局有问题
|
||||
@@ -130,7 +127,7 @@ func (s *tradeService) CreateTrade(uid int32, now time.Time, data *CreateTradeDa
|
||||
paymentUrl = resp.String()
|
||||
|
||||
// 微信 + 电脑网站
|
||||
case method == trade2.MethodWeChat && platform == trade2.PlatformDesktop:
|
||||
case method == m.TradeMethodWechat && platform == m.TradePlatformPC:
|
||||
resp, _, err := g.WechatPay.Native.Prepay(context.Background(), native.PrepayRequest{
|
||||
Appid: &env.WechatPayAppId,
|
||||
Mchid: &env.WechatPayMchId,
|
||||
@@ -149,14 +146,14 @@ func (s *tradeService) CreateTrade(uid int32, now time.Time, data *CreateTradeDa
|
||||
|
||||
// 商福通 + 电脑网站
|
||||
case
|
||||
method == trade2.MethodSftAlipay && platform == trade2.PlatformDesktop,
|
||||
method == trade2.MethodSftWeChat && platform == trade2.PlatformDesktop:
|
||||
method == m.TradeMethodSftAlipay && platform == m.TradePlatformPC,
|
||||
method == m.TradeMethodSftWechat && platform == m.TradePlatformPC:
|
||||
|
||||
var payType g.SftPayType
|
||||
switch method {
|
||||
case trade2.MethodSftAlipay:
|
||||
case m.TradeMethodSftAlipay:
|
||||
payType = g.SftAlipay
|
||||
case trade2.MethodSftWeChat:
|
||||
case m.TradeMethodSftWechat:
|
||||
payType = g.SftWeChat
|
||||
default:
|
||||
panic("unhandled default case")
|
||||
@@ -178,13 +175,13 @@ func (s *tradeService) CreateTrade(uid int32, now time.Time, data *CreateTradeDa
|
||||
|
||||
// 商福通 + 手机网站
|
||||
case
|
||||
method == trade2.MethodSftAlipay && platform == trade2.PlatformMobile,
|
||||
method == trade2.MethodSftWeChat && platform == trade2.PlatformMobile:
|
||||
method == m.TradeMethodSftAlipay && platform == m.TradePlatformMobile,
|
||||
method == m.TradeMethodSftWechat && platform == m.TradePlatformMobile:
|
||||
var payType g.SftPayType
|
||||
switch method {
|
||||
case trade2.MethodSftAlipay:
|
||||
case m.TradeMethodSftAlipay:
|
||||
payType = g.SftAlipay
|
||||
case trade2.MethodSftWeChat:
|
||||
case m.TradeMethodSftWechat:
|
||||
payType = g.SftWeChat
|
||||
default:
|
||||
panic("unhandled default case")
|
||||
@@ -214,11 +211,11 @@ func (s *tradeService) CreateTrade(uid int32, now time.Time, data *CreateTradeDa
|
||||
err = q.Trade.Create(&m.Trade{
|
||||
UserID: uid,
|
||||
InnerNo: tradeNo,
|
||||
Type: int32(tType),
|
||||
Type: tType,
|
||||
Subject: subject,
|
||||
Amount: amount,
|
||||
Method: int32(method),
|
||||
Platform: int32(platform),
|
||||
Method: method,
|
||||
Platform: platform,
|
||||
PaymentURL: &paymentUrl,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -266,11 +263,11 @@ func (s *tradeService) CompleteTrade(data *ModifyTradeData) error {
|
||||
if err != nil {
|
||||
return core.NewServErr("检查订单状态失败", err)
|
||||
}
|
||||
if result.Status != trade2.StatusSuccess {
|
||||
if result.Status != m.TradeStatusSuccess {
|
||||
switch result.Status {
|
||||
case trade2.StatusPending:
|
||||
case m.TradeStatusPending:
|
||||
return core.NewBizErr("订单未支付")
|
||||
case trade2.StatusCanceled:
|
||||
case m.TradeStatusCanceled:
|
||||
return core.NewBizErr("订单已过期")
|
||||
}
|
||||
}
|
||||
@@ -331,22 +328,22 @@ func completeTrade(data *OnTradeCompletedData) (*m.Trade, error) {
|
||||
}
|
||||
|
||||
// 检查交易状态
|
||||
switch trade2.Status(trade.Status) {
|
||||
case trade2.StatusCanceled:
|
||||
switch trade.Status {
|
||||
case m.TradeStatusCanceled:
|
||||
return core.NewBizErr("交易已取消")
|
||||
case trade2.StatusSuccess:
|
||||
case m.TradeStatusSuccess:
|
||||
return nil // 跳过更新交易信息
|
||||
case trade2.StatusPending:
|
||||
case m.TradeStatusPending:
|
||||
}
|
||||
|
||||
// 更新交易信息
|
||||
trade.Status = int32(trade2.StatusSuccess)
|
||||
trade.Status = m.TradeStatusSuccess
|
||||
trade.OuterNo = &transId
|
||||
trade.Payment = payment
|
||||
trade.Acquirer = u.P(int32(acquirer))
|
||||
trade.CompletedAt = u.P(orm.LocalDateTime(paidAt))
|
||||
trade.Acquirer = u.P(acquirer)
|
||||
trade.CompletedAt = u.P(paidAt)
|
||||
rs, err := q.Trade.
|
||||
Where(q.Trade.InnerNo.Eq(tradeNo), q.Trade.Status.Eq(int32(trade2.StatusPending))).
|
||||
Where(q.Trade.InnerNo.Eq(tradeNo), q.Trade.Status.Eq(int(m.TradeStatusPending))).
|
||||
Updates(trade)
|
||||
if rs.RowsAffected == 0 {
|
||||
return core.NewBizErr("交易状态已发生变化")
|
||||
@@ -372,13 +369,13 @@ func afterTradeComplete(trade *m.Trade) error {
|
||||
}
|
||||
|
||||
// 执行资源创建
|
||||
var ComplementEvents = []trade2.CompleteEvent{
|
||||
var ComplementEvents = []CompleteEvent{
|
||||
ResourceOnTradeComplete{},
|
||||
UserOnTradeComplete{},
|
||||
}
|
||||
|
||||
for _, event := range ComplementEvents {
|
||||
info, ok := event.Check(trade2.Type(trade.Type))
|
||||
info, ok := event.Check(trade.Type)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
@@ -405,7 +402,7 @@ func (s *tradeService) CancelTrade(data *ModifyTradeData, now time.Time) error {
|
||||
return g.Redsync.WithLock(tradeLockKey(tradeNo), func() error {
|
||||
switch method {
|
||||
|
||||
case trade2.MethodAlipay:
|
||||
case m.TradeMethodAlipay:
|
||||
resp, err := g.Alipay.TradeCancel(context.Background(), alipay.TradeCancel{
|
||||
OutTradeNo: tradeNo,
|
||||
})
|
||||
@@ -417,7 +414,7 @@ func (s *tradeService) CancelTrade(data *ModifyTradeData, now time.Time) error {
|
||||
return errors.New("上游取消交易失败")
|
||||
}
|
||||
|
||||
case trade2.MethodWeChat:
|
||||
case m.TradeMethodWechat:
|
||||
resp, err := g.WechatPay.Native.CloseOrder(context.Background(), native.CloseOrderRequest{
|
||||
Mchid: &env.WechatPayMchId,
|
||||
OutTradeNo: &tradeNo,
|
||||
@@ -435,7 +432,7 @@ func (s *tradeService) CancelTrade(data *ModifyTradeData, now time.Time) error {
|
||||
return errors.New("上游取消交易失败")
|
||||
}
|
||||
|
||||
case trade2.MethodSft, trade2.MethodSftAlipay, trade2.MethodSftWeChat:
|
||||
case m.TradeMethodSft, m.TradeMethodSftAlipay, m.TradeMethodSftWechat:
|
||||
_, err := g.SFTPay.OrderClose(&g.OrderCloseReq{
|
||||
MchOrderNo: &tradeNo,
|
||||
})
|
||||
@@ -468,7 +465,7 @@ func (s *tradeService) OnTradeCanceled(tradeNo string, now time.Time) error {
|
||||
func cancelTrade(tradeNo string, now time.Time) error {
|
||||
return q.Q.Transaction(func(q *q.Query) error {
|
||||
// 获取交易信息
|
||||
var status trade2.Status
|
||||
var status m.TradeStatus
|
||||
err := q.Trade.
|
||||
Where(q.Trade.InnerNo.Eq(tradeNo)).
|
||||
Select(q.Trade.Status).
|
||||
@@ -479,19 +476,19 @@ func cancelTrade(tradeNo string, now time.Time) error {
|
||||
|
||||
// 检查交易状态
|
||||
switch status {
|
||||
case trade2.StatusCanceled:
|
||||
case m.TradeStatusCanceled:
|
||||
return core.NewBizErr("交易已取消")
|
||||
case trade2.StatusSuccess:
|
||||
case m.TradeStatusSuccess:
|
||||
return core.NewBizErr("交易已完成")
|
||||
case trade2.StatusPending:
|
||||
case m.TradeStatusPending:
|
||||
}
|
||||
|
||||
// 更新交易状态
|
||||
_, err = q.Trade.
|
||||
Where(q.Trade.InnerNo.Eq(tradeNo)).
|
||||
UpdateSimple(
|
||||
q.Trade.Status.Value(int32(trade2.StatusCanceled)),
|
||||
q.Trade.CanceledAt.Value(orm.LocalDateTime(now)),
|
||||
q.Trade.Status.Value(int(m.TradeStatusCanceled)),
|
||||
q.Trade.CanceledAt.Value(now),
|
||||
)
|
||||
if err != nil {
|
||||
return core.NewServErr("更新交易状态失败", err)
|
||||
@@ -518,7 +515,7 @@ func (s *tradeService) CheckTrade(data *ModifyTradeData) (*CheckTradeResult, err
|
||||
switch method {
|
||||
|
||||
// 支付宝
|
||||
case trade2.MethodAlipay:
|
||||
case m.TradeMethodAlipay:
|
||||
|
||||
// 查询交易状态
|
||||
resp, err := g.Alipay.TradeQuery(context.Background(), alipay.TradeQuery{
|
||||
@@ -537,15 +534,15 @@ func (s *tradeService) CheckTrade(data *ModifyTradeData) (*CheckTradeResult, err
|
||||
switch resp.TradeStatus {
|
||||
|
||||
case alipay.TradeStatusWaitBuyerPay:
|
||||
result.Status = trade2.StatusPending
|
||||
result.Status = m.TradeStatusPending
|
||||
|
||||
case alipay.TradeStatusClosed:
|
||||
result.Status = trade2.StatusCanceled
|
||||
result.Status = m.TradeStatusCanceled
|
||||
|
||||
case alipay.TradeStatusSuccess, alipay.TradeStatusFinished:
|
||||
result.Status = trade2.StatusSuccess
|
||||
result.Status = m.TradeStatusSuccess
|
||||
result.Success = &TradeSuccessResult{}
|
||||
result.Success.Acquirer = trade2.AcquirerAlipay
|
||||
result.Success.Acquirer = m.TradeAcquirerAlipay
|
||||
result.Success.Payment, err = decimal.NewFromString(resp.TotalAmount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -557,7 +554,7 @@ func (s *tradeService) CheckTrade(data *ModifyTradeData) (*CheckTradeResult, err
|
||||
}
|
||||
|
||||
// 微信
|
||||
case trade2.MethodWeChat:
|
||||
case m.TradeMethodWechat:
|
||||
|
||||
// 查询交易状态
|
||||
resp, _, err := g.WechatPay.Native.QueryOrderByOutTradeNo(context.Background(), native.QueryOrderByOutTradeNoRequest{
|
||||
@@ -583,15 +580,15 @@ func (s *tradeService) CheckTrade(data *ModifyTradeData) (*CheckTradeResult, err
|
||||
switch *resp.TradeState {
|
||||
|
||||
case "NOTPAY":
|
||||
result.Status = trade2.StatusPending
|
||||
result.Status = m.TradeStatusPending
|
||||
|
||||
case "CLOSED":
|
||||
result.Status = trade2.StatusCanceled
|
||||
result.Status = m.TradeStatusCanceled
|
||||
|
||||
case "SUCCESS", "REFUND":
|
||||
result.Status = trade2.StatusSuccess
|
||||
result.Status = m.TradeStatusSuccess
|
||||
result.Success = &TradeSuccessResult{}
|
||||
result.Success.Acquirer = trade2.AcquirerWeChat
|
||||
result.Success.Acquirer = m.TradeAcquirerWechat
|
||||
result.Success.Payment = decimal.NewFromInt(*resp.Amount.PayerTotal).Div(decimal.NewFromInt(100))
|
||||
result.Success.Time, err = time.Parse(time.RFC3339, *resp.SuccessTime)
|
||||
if err != nil {
|
||||
@@ -600,7 +597,7 @@ func (s *tradeService) CheckTrade(data *ModifyTradeData) (*CheckTradeResult, err
|
||||
}
|
||||
|
||||
// 商福通
|
||||
case trade2.MethodSft, trade2.MethodSftAlipay, trade2.MethodSftWeChat:
|
||||
case m.TradeMethodSft, m.TradeMethodSftAlipay, m.TradeMethodSftWechat:
|
||||
|
||||
// 查询交易状态
|
||||
resp, err := g.SFTPay.QueryTrade(&g.QueryTradeReq{
|
||||
@@ -619,21 +616,21 @@ func (s *tradeService) CheckTrade(data *ModifyTradeData) (*CheckTradeResult, err
|
||||
switch resp.State {
|
||||
|
||||
case g.SftInit, g.SftTradeAwait, g.SftTradeFail:
|
||||
result.Status = trade2.StatusPending
|
||||
result.Status = m.TradeStatusPending
|
||||
|
||||
case g.SftTradeClosed, g.SftTradeCancel:
|
||||
result.Status = trade2.StatusCanceled
|
||||
result.Status = m.TradeStatusCanceled
|
||||
|
||||
case g.SftTradeSuccess, g.SftTradeRefund, g.SftRefundIng:
|
||||
result.Status = trade2.StatusSuccess
|
||||
result.Status = m.TradeStatusSuccess
|
||||
result.Success = &TradeSuccessResult{}
|
||||
switch resp.PayType {
|
||||
case "WECHAT":
|
||||
result.Success.Acquirer = trade2.AcquirerWeChat
|
||||
result.Success.Acquirer = m.TradeAcquirerWechat
|
||||
case "ALIPAY":
|
||||
result.Success.Acquirer = trade2.AcquirerAlipay
|
||||
result.Success.Acquirer = m.TradeAcquirerAlipay
|
||||
case "UNIONPAY":
|
||||
result.Success.Acquirer = trade2.AcquirerUnionPay
|
||||
result.Success.Acquirer = m.TradeAcquirerUnionPay
|
||||
}
|
||||
result.Success.Payment = decimal.NewFromInt(resp.Amount).Div(decimal.NewFromInt(100))
|
||||
result.Success.Time, err = time.Parse("2006-01-02 15:04:05", *resp.PayTime)
|
||||
@@ -659,10 +656,10 @@ func tradeLockKey(no string) string {
|
||||
}
|
||||
|
||||
type CreateTradeData struct {
|
||||
Platform trade2.Platform `json:"platform" validate:"required"`
|
||||
Method trade2.Method `json:"method" validate:"required"`
|
||||
Platform m.TradePlatform `json:"platform" validate:"required"`
|
||||
Method m.TradeMethod `json:"method" validate:"required"`
|
||||
CouponCode *string `json:"coupon_code"`
|
||||
Product trade2.ProductInfo
|
||||
Product ProductInfo
|
||||
}
|
||||
|
||||
type CreateTradeResult struct {
|
||||
@@ -672,17 +669,17 @@ type CreateTradeResult struct {
|
||||
|
||||
type ModifyTradeData struct {
|
||||
TradeNo string `json:"trade_no" validate:"required"`
|
||||
Method trade2.Method `json:"method" validate:"required"`
|
||||
Method m.TradeMethod `json:"method" validate:"required"`
|
||||
}
|
||||
|
||||
type CheckTradeResult struct {
|
||||
TransId string
|
||||
Status trade2.Status
|
||||
Status m.TradeStatus
|
||||
Success *TradeSuccessResult
|
||||
}
|
||||
|
||||
type TradeSuccessResult struct {
|
||||
Acquirer trade2.Acquirer
|
||||
Acquirer m.TradeAcquirer
|
||||
Payment decimal.Decimal
|
||||
Time time.Time
|
||||
}
|
||||
@@ -693,6 +690,19 @@ type OnTradeCompletedData struct {
|
||||
*TradeSuccessResult
|
||||
}
|
||||
|
||||
type ProductInfo interface {
|
||||
GetType() m.TradeType
|
||||
GetSubject() string
|
||||
GetAmount() decimal.Decimal
|
||||
Serialize() (string, error)
|
||||
Deserialize(str string) error
|
||||
}
|
||||
|
||||
type CompleteEvent interface {
|
||||
Check(t m.TradeType) (ProductInfo, bool)
|
||||
OnTradeComplete(info ProductInfo, trade *m.Trade) error
|
||||
}
|
||||
|
||||
type TradeErr string
|
||||
|
||||
func (e TradeErr) Error() string {
|
||||
|
||||
@@ -4,8 +4,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"platform/web/core"
|
||||
bill2 "platform/web/domains/bill"
|
||||
trade2 "platform/web/domains/trade"
|
||||
g "platform/web/globals"
|
||||
m "platform/web/models"
|
||||
q "platform/web/queries"
|
||||
@@ -27,7 +25,7 @@ func (s *userService) UpdateBalanceByTrade(uid int32, info *RechargeProductInfo,
|
||||
}
|
||||
|
||||
// 生成账单
|
||||
err = q.Bill.Create(bill2.NewForRecharge(uid, Bill.GenNo(), info.GetSubject(), info.GetAmount(), trade))
|
||||
err = q.Bill.Create(newForRecharge(uid, Bill.GenNo(), info.GetSubject(), info.GetAmount(), trade))
|
||||
if err != nil {
|
||||
return core.NewServErr("生成账单失败", err)
|
||||
}
|
||||
@@ -73,8 +71,8 @@ type RechargeProductInfo struct {
|
||||
Amount int `json:"amount"`
|
||||
}
|
||||
|
||||
func (r *RechargeProductInfo) GetType() trade2.Type {
|
||||
return trade2.TypeRecharge
|
||||
func (r *RechargeProductInfo) GetType() m.TradeType {
|
||||
return m.TradeTypeRecharge
|
||||
}
|
||||
|
||||
func (r *RechargeProductInfo) GetSubject() string {
|
||||
@@ -96,13 +94,13 @@ func (r *RechargeProductInfo) Deserialize(str string) error {
|
||||
|
||||
type UserOnTradeComplete struct{}
|
||||
|
||||
func (u UserOnTradeComplete) Check(t trade2.Type) (trade2.ProductInfo, bool) {
|
||||
if t == trade2.TypeRecharge {
|
||||
func (u UserOnTradeComplete) Check(t m.TradeType) (ProductInfo, bool) {
|
||||
if t == m.TradeTypeRecharge {
|
||||
return &RechargeProductInfo{}, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (u UserOnTradeComplete) OnTradeComplete(info trade2.ProductInfo, trade *m.Trade) error {
|
||||
func (u UserOnTradeComplete) OnTradeComplete(info ProductInfo, trade *m.Trade) error {
|
||||
return User.UpdateBalanceByTrade(trade.UserID, info.(*RechargeProductInfo), trade)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user