From 851e62264fc996924be21ed96f64938fa06d63c9 Mon Sep 17 00:00:00 2001 From: Leufolium Date: Mon, 22 Jan 2024 14:21:13 +0800 Subject: [PATCH 1/4] by Robin at 20240122; fix --- api/proto/vas/proto/pay.go | 27 ++++++ app/mix/dao/mysql.go | 66 +++++++++++--- app/mix/service/logic/account.go | 24 +++++ app/mix/service/logic/vas.go | 146 ++++++++++++++++++++++++++++++- app/mix/service/service.go | 2 +- dbstruct/account.go | 1 + dbstruct/product.go | 8 +- dbstruct/vas_mysql.go | 23 +++++ 8 files changed, 282 insertions(+), 15 deletions(-) diff --git a/api/proto/vas/proto/pay.go b/api/proto/vas/proto/pay.go index c4f7d246..469ed9ba 100644 --- a/api/proto/vas/proto/pay.go +++ b/api/proto/vas/proto/pay.go @@ -134,3 +134,30 @@ type AlipayCallbackParamIn struct { OrderId string `json:"order_id"` // 我们自己服务的订单id AlipayOrderId string `json:"alipay_order_id"` // 支付宝订单id } + +// 解锁会员资格 +type UnlockMembershipReq struct { + base.BaseRequest + Ip string `json:"ip"` + MembershipProductId string `json:"membership_product_id"` // 商品id,dbstruct.ProductIdMembership* + InviterMid int64 // 邀请人mid +} + +type UnlockMembershipData struct { + LockType int32 `json:"lock_type"` // 见 dbstruct.UserVasLockType + OrderId string `json:"order_id"` // 订单id +} + +// h5直接解锁会员资格 +type H5DirectUnlockMembershipReq struct { + base.BaseRequest + PayType string `json:"pay_type"` // 支付类型 + InviterMid int64 +} + +type H5DirectUnlockMembershipData struct { + CoinEnough int32 `json:"coin_enough"` // 0:不够(用下面的支付宝参数),1:够 + OrderId string `json:"order_id"` // 订单id + AlipayParamStr string `json:"alipay_param_str"` // 支付宝 app支付参数 + AlipayH5ParamStr string `json:"alipay_h5_param_str"` // 支付宝 h5支付参数 +} diff --git a/app/mix/dao/mysql.go b/app/mix/dao/mysql.go index 3c269ea8..a7e78fc7 100644 --- a/app/mix/dao/mysql.go +++ b/app/mix/dao/mysql.go @@ -61,16 +61,17 @@ func (m *Mysql) DealTxCR(tx *sqlx.Tx, errIn error) (errOut error) { // mysql const ( - DatabaseVas = "vas" - TableOrder = "vas_order" // 订单表 - TableWallet = "vas_wallet" // 钱包 - TableCoinOrder = "vas_coin_order" // 金币订单 - TableConsumeHistoryCost = "vas_ch_cost" // 消费明细 - TableConsumeHistoryCharge = "vas_ch_charge" // 充值明细 - TableConsumeHistoryIncome = "vas_ch_income" // 收入明细 - TableConsumeHistoryWithdraw = "vas_ch_withdraw" // 提现明细 - TableVasUserUnlock = "vas_user_unlock" // 用增解锁 - TableWithdrawOrder = "vas_withdraw_order" // 提现订单表 + DatabaseVas = "vas" + TableOrder = "vas_order" // 订单表 + TableWallet = "vas_wallet" // 钱包 + TableCoinOrder = "vas_coin_order" // 金币订单 + TableConsumeHistoryCost = "vas_ch_cost" // 消费明细 + TableConsumeHistoryCharge = "vas_ch_charge" // 充值明细 + TableConsumeHistoryIncome = "vas_ch_income" // 收入明细 + TableConsumeHistoryWithdraw = "vas_ch_withdraw" // 提现明细 + TableVasUserUnlock = "vas_user_unlock" // 用增解锁 + TableWithdrawOrder = "vas_withdraw_order" // 提现订单表 + TableVasUserMembershipUnlock = "vas_user_membership_unlock" // 会员资格解锁 ) func (m *Mysql) ChTableName(ch *dbstruct.ConsumeHistory) (string, error) { @@ -580,6 +581,51 @@ func (m *Mysql) GetUserVasUnlock(ctx *gin.Context, tx *sqlx.Tx, mid, uid int64, return } +// 创建解锁记录 +func (m *Mysql) CreateUserVasMembershipUnlock(ctx *gin.Context, tx *sqlx.Tx, uu *dbstruct.UserVasMembershipUnlock) error { + var ( + err error + timeNow = time.Now().Unix() + ) + sqlStr := "insert into " + TableVasUserMembershipUnlock + + " (mid, product_id, ct, means, order_id) " + + " values (?, ?, ?, ?, ?) " + if tx != nil { + _, err = tx.ExecContext(ctx, sqlStr, + uu.Mid, uu.ProductId, timeNow, uu.Means, uu.OrderId, + ) + } else { + db := m.getDBVas() + _, err = db.ExecContext(ctx, sqlStr, + uu.Mid, uu.ProductId, timeNow, uu.Means, uu.OrderId, + ) + } + if err != nil { + logger.Error("CreateUserVasMembershipUnlock fail, uu: %v, err: %v", util.ToJson(uu), err) + return err + } + return err +} + +// 获取会员资格解锁记录 +func (m *Mysql) GetUserVasMembershipUnlock(ctx *gin.Context, tx *sqlx.Tx, mid int64, productId string) (uu *dbstruct.UserVasMembershipUnlock, err error) { + var uuTmp dbstruct.UserVasMembershipUnlock + sqlStr := fmt.Sprintf("select * from %s where mid=? and product_id=?", TableVasUserMembershipUnlock) + fmt.Println(sqlStr) + + if tx != nil { + err = tx.GetContext(ctx, &uuTmp, sqlStr, mid, productId) + } else { + db := m.getDBVas() + err = db.GetContext(ctx, &uuTmp, sqlStr, mid, productId) + } + if err != nil { + return + } + uu = &uuTmp + return +} + // 获取解锁记录 func (m *Mysql) GetUnlockWechatList(ctx *gin.Context, tx *sqlx.Tx, mid int64, offset, limit int) (list []*dbstruct.UserVasUnlock, err error) { list = make([]*dbstruct.UserVasUnlock, 0) diff --git a/app/mix/service/logic/account.go b/app/mix/service/logic/account.go index 3183f61e..8ecb4816 100644 --- a/app/mix/service/logic/account.go +++ b/app/mix/service/logic/account.go @@ -163,6 +163,30 @@ func (p *Account) OpCount(ctx *gin.Context, req *accountproto.OpCountReq) (int64 return count, err } +func (p *Account) GetInviterMid(ctx *gin.Context, mid int64) (int64, error) { + inviterMid := int64(0) + userAcct, err := p.OpListByMid(ctx, &accountproto.OpListByMidReq{ + Mid: goproto.Int64(mid), + }) + if err != nil { + logger.Error("OpListByMid fail, err: %v", err) + return 0, err + } + if userAcct != nil && userAcct.Inviter != nil { + inviterAcct, err := p.OpListByUserId(ctx, &accountproto.OpListByUserIdReq{ + UserId: userAcct.Inviter, + }) + if err != nil { + logger.Error("OpListByUserId fail, err: %v", err) + return 0, err + } + if inviterAcct != nil { + inviterMid = util.DerefInt64(inviterAcct.Mid) + } + } + return inviterMid, nil +} + func (p *Account) GenerateOriginalAccount() (*dbstruct.Account, error) { key := "account_init" cfg := apollostruct.AccountInitCfg{} diff --git a/app/mix/service/logic/vas.go b/app/mix/service/logic/vas.go index 08680afd..e6e10831 100644 --- a/app/mix/service/logic/vas.go +++ b/app/mix/service/logic/vas.go @@ -9,9 +9,9 @@ import ( "encoding/pem" "errors" "fmt" - "github.com/go-pay/gopay/alipay" "service/api/base" "service/api/errs" + accountproto "service/api/proto/account/proto" vasproto "service/api/proto/vas/proto" "service/app/mix/dao" "service/bizcommon/common" @@ -22,6 +22,8 @@ import ( "service/library/payclients/alipaycli" "time" + "github.com/go-pay/gopay/alipay" + "github.com/gin-gonic/gin" "github.com/jmoiron/sqlx" "github.com/samber/lo" @@ -31,11 +33,13 @@ import ( type Vas struct { store *dao.Store streamer *Streamer + account *Account } -func NewVas(store *dao.Store, streamer *Streamer) (v *Vas) { +func NewVas(store *dao.Store, streamer *Streamer, account *Account) (v *Vas) { return &Vas{ store: store, + account: account, streamer: streamer, } } @@ -1679,6 +1683,13 @@ func (v *Vas) AlipayCallback(ctx *gin.Context, p *vasproto.AlipayCallbackParamIn logger.Error("IncCoins fail, order: %v", util.ToJson(order)) return } + case product.Id == dbstruct.ProductIdMembership: + // 解锁会员资格 + _, err = v.UnlockMembership(ctx, util.DerefInt64(order.Mid), product, order) + if err != nil { + logger.Error("UnlockMembership fail, order: %v", util.ToJson(order)) + return + } } } @@ -1968,3 +1979,134 @@ func (v *Vas) DealOneCoinOrder(ctx *gin.Context, coinOrder *dbstruct.CoinOrder) // 更新订单状态 } + +// 解锁会员资格 +func (v *Vas) UnlockMembership(ctx *gin.Context, mid int64, product *dbstruct.Product, order *dbstruct.Order) (orderId string, err error) { + + var ( + did = util.DerefString(order.Did) + productId = product.Id + timeNow = time.Now().Unix() + ) + orderId = util.DerefString(order.ID) + + // 查询邀请人mid + inviterMid, _ := v.account.GetInviterMid(ctx, mid) + + // 是否已经解锁过 + unlockInfo, _ := v.store.GetUserVasMembershipUnlock(ctx, nil, mid, product.Id) + if unlockInfo != nil { + err = errs.ErrVasAlreadyUnlock + return + } + + // 开启事务 + tx, err := v.store.VasBegin(ctx) + if err != nil { + logger.Error("vas begin fail, err: %v", err) + return + } + defer func() { + errTx := v.store.DealTxCR(tx, err) + if errTx != nil { + logger.Error("DealTxCR fail, err: %v", errTx) + return + } + }() + + // 官网钱包 + officialWallet, _ := v.CheckWalletExist(ctx, common.OfficialMid) + // 邀请人钱包 + var inviterWallet *dbstruct.Wallet + if inviterMid > 0 { + inviterWallet, _ = v.CheckWalletExist(ctx, inviterMid) + } + + // 解锁记录 + userVasMembershipUnlock := &dbstruct.UserVasMembershipUnlock{ + Mid: goproto.Int64(mid), + ProductId: goproto.String(productId), + Ct: goproto.Int64(timeNow), + Means: goproto.String(dbstruct.UserVasUnlockMeansMoney), + OrderId: order.ID, + } + err = v.store.CreateUserVasMembershipUnlock(ctx, tx, userVasMembershipUnlock) + if err != nil { + logger.Error("CreateUserVasMembershipUnlock fail, userVasMembershipUnlock: %v, err: %v", util.ToJson(userVasMembershipUnlock), err) + return + } + + // 加钻石 + var ( + TotalDias = product.RealCoinPrice + InviterDias = int64(0) + OfficialDias = int64(0) + ) + if inviterMid > 0 { + InviterDias = int64(float64(TotalDias) * 0.05) + } + OfficialDias = TotalDias - InviterDias + + // 官方 + chOfficial := &dbstruct.ConsumeHistory{ + Mid: goproto.Int64(common.OfficialMid), + Uid: goproto.Int64(mid), + Did: goproto.String(did), + Type: goproto.Int32(dbstruct.CHTypeIncome), + SType: goproto.Int32(dbstruct.CHSTypeIncomeContact), + TypeId: goproto.String(productId), + OrderId: goproto.String(orderId), + Change: goproto.Int64(OfficialDias), + Before: goproto.Int64(officialWallet.GetDiamonds()), + After: goproto.Int64(officialWallet.GetDiamonds() + OfficialDias), + Ct: goproto.Int64(timeNow), + } + err = v.store.CreateConsumeHistory(ctx, tx, chOfficial) + if err != nil { + logger.Error("CreateConsumeHistory fail, ch: %v, err: %v", util.ToJson(chOfficial), err) + return + } + err = v.store.IncDiamonds(ctx, tx, common.OfficialMid, OfficialDias) + if err != nil { + logger.Error("IncDiamonds fail, official, mid: %v, dias: %v, err: %v", common.OfficialMid, OfficialDias, err) + return + } + + if InviterDias > 0 { + chInviter := &dbstruct.ConsumeHistory{ + Mid: goproto.Int64(inviterMid), + Uid: goproto.Int64(mid), + Did: goproto.String(did), + Type: goproto.Int32(dbstruct.CHTypeIncome), + SType: goproto.Int32(dbstruct.CHSTypeIncomeInvite), + TypeId: goproto.String(productId), + OrderId: goproto.String(orderId), + Change: goproto.Int64(InviterDias), + Before: goproto.Int64(inviterWallet.GetDiamonds()), + After: goproto.Int64(inviterWallet.GetDiamonds() + InviterDias), + Ct: goproto.Int64(timeNow), + } + err = v.store.CreateConsumeHistory(ctx, tx, chInviter) + if err != nil { + logger.Error("CreateConsumeHistory fail, ch: %v, err: %v", util.ToJson(chInviter), err) + return + } + err = v.store.IncDiamonds(ctx, tx, inviterMid, InviterDias) + if err != nil { + logger.Error("IncDiamonds fail, inviter, mid: %v, dias: %v, err: %v", inviterMid, InviterDias, err) + return + } + } + + err = v.account.OpUpdate(ctx, &accountproto.OpUpdateReq{ + Account: &dbstruct.Account{ + Mid: goproto.Int64(mid), + IsAMember: goproto.Int64(1), + }, + }) + if err != nil { + logger.Error("OpUpdate fail, err: %v", err) + return + } + return +} diff --git a/app/mix/service/service.go b/app/mix/service/service.go index c382458b..0fdb6222 100644 --- a/app/mix/service/service.go +++ b/app/mix/service/service.go @@ -157,7 +157,7 @@ func (s *Service) Init(c any) (err error) { _DefaultImageAuditTask = logic.NewImageAuditTask(store) _DefaultTextAudit = logic.NewTextAudit(store) _DefaultTextAuditTask = logic.NewTextAuditTask(store) - _DefaultVas = logic.NewVas(store, _DefaultStreamer) + _DefaultVas = logic.NewVas(store, _DefaultStreamer, _DefaultAccount) _DefaultContactCustomerServiceSession = logic.NewContactCustomerServiceSession(store) _DefaultDailyStatement = logic.NewDailyStatement(store) return diff --git a/dbstruct/account.go b/dbstruct/account.go index 672226ec..9c365ff0 100644 --- a/dbstruct/account.go +++ b/dbstruct/account.go @@ -21,6 +21,7 @@ type Account struct { GoldNum *int64 `json:"gold_num" bson:"gold_num"` // 金币数量 DiamondNum *int64 `json:"diamond_num" bson:"diamond_num"` // 钻石数量 Inviter *int64 `json:"inviter" bson:"inviter"` // 邀请人user_id + IsAMember *int64 `json:"is_a_member" bson:"is_a_member"` // 是否是会员,0-否,1-是 Latitude *float64 `bson:"latitude"` // 纬度 Longitude *float64 `bson:"longitude"` // 经度 Ct *int64 `json:"ct" bson:"ct"` // 创建时间 diff --git a/dbstruct/product.go b/dbstruct/product.go index a334c734..5a54cd08 100644 --- a/dbstruct/product.go +++ b/dbstruct/product.go @@ -17,12 +17,16 @@ const ( ProductIdContactWechat = "contact_wechat" // 微信联系方式 ProductIdH5ContactWechat = "h5_contact_wechat" // h5的联系方式,rmb直接解锁 + + ProductIdMembership = "membership" // 会员 + ProductIdH5Membership = "h5_membership" // 会员 ) // 商品类型 const ( - ProductTypeCoins = "coins" // 商品类型:金币 - ProductTypeMoneyContact = "money_contact" // 商品类型:联系方式 + ProductTypeCoins = "coins" // 商品类型:金币 + ProductTypeMoneyContact = "money_contact" // 商品类型:联系方式 + ProductTypeMoneyMembership = "money_membership" // 商品类型:会员资格 ) // 商品支付手段 diff --git a/dbstruct/vas_mysql.go b/dbstruct/vas_mysql.go index 4760d466..651ab187 100644 --- a/dbstruct/vas_mysql.go +++ b/dbstruct/vas_mysql.go @@ -781,3 +781,26 @@ func (p *WithdrawOrder) GetOpTime() int64 { } return 0 } + +type UserVasMembershipUnlock struct { + Id *int64 `json:"id" db:"id"` + Mid *int64 `json:"mid" db:"mid"` + ProductId *string `json:"product_id" db:"product_id"` + Ct *int64 `json:"ct" db:"ct"` + Means *string `json:"means" db:"means"` // 解锁方式,UserVasUnlockMeans + OrderId *string `json:"order_id" db:"order_id"` // 关联的订单id +} + +func (p *UserVasMembershipUnlock) GetMid() int64 { + if p != nil && p.Mid != nil { + return *p.Mid + } + return 0 +} + +func (p *UserVasMembershipUnlock) GetOrderId() string { + if p != nil && p.OrderId != nil { + return *p.OrderId + } + return "" +} -- 2.41.0 From 0839b0772aa1a5aba259beb97ec16a5f686ad167 Mon Sep 17 00:00:00 2001 From: Leufolium Date: Mon, 22 Jan 2024 14:32:43 +0800 Subject: [PATCH 2/4] by Robin at 20240122; vas_user_membership_unlock --- dbstruct/vas.sql | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/dbstruct/vas.sql b/dbstruct/vas.sql index 49c70407..f27f9653 100644 --- a/dbstruct/vas.sql +++ b/dbstruct/vas.sql @@ -136,3 +136,15 @@ CREATE TABLE `vas_withdraw_order` PRIMARY KEY (`id`) ); CREATE INDEX ix_mid_applytime ON vas_withdraw_order (mid, apply_time); + +CREATE TABLE `vas_user_membership_unlock` +( + `id` bigint AUTO_INCREMENT COMMENT 'id', + `mid` bigint NOT NULL COMMENT '用户id', + `product_id` varchar(128) NOT NULL COMMENT '商品id', + `ct` bigint DEFAULT NULL COMMENT '时间', + `means` varchar(128) DEFAULT NULL COMMENT '解锁方式', + `order_id` varchar(128) DEFAULT NULL COMMENT '订单id', + PRIMARY KEY (`id`) +); +CREATE INDEX uni_idx_mid_product_id ON vas_user_unlock (mid, product_id); \ No newline at end of file -- 2.41.0 From 87f088aeaaa821c937737d71960b2e1fe010315c Mon Sep 17 00:00:00 2001 From: Leufolium Date: Mon, 22 Jan 2024 15:10:51 +0800 Subject: [PATCH 3/4] by Robin at 20240122; fix --- app/mix/service/logic/vas.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/mix/service/logic/vas.go b/app/mix/service/logic/vas.go index e6e10831..13ba5dcc 100644 --- a/app/mix/service/logic/vas.go +++ b/app/mix/service/logic/vas.go @@ -2038,7 +2038,7 @@ func (v *Vas) UnlockMembership(ctx *gin.Context, mid int64, product *dbstruct.Pr // 加钻石 var ( - TotalDias = product.RealCoinPrice + TotalDias = int64(float64(product.RealPrice) * 0.01) InviterDias = int64(0) OfficialDias = int64(0) ) -- 2.41.0 From f91231f5b3005556de77977afef22ff230acf892 Mon Sep 17 00:00:00 2001 From: Leufolium Date: Mon, 22 Jan 2024 15:13:52 +0800 Subject: [PATCH 4/4] by Robin at 20240112; fix --- app/mix/service/logic/vas.go | 2 +- dbstruct/vas_mysql.go | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/mix/service/logic/vas.go b/app/mix/service/logic/vas.go index 13ba5dcc..adc6bff9 100644 --- a/app/mix/service/logic/vas.go +++ b/app/mix/service/logic/vas.go @@ -2027,7 +2027,7 @@ func (v *Vas) UnlockMembership(ctx *gin.Context, mid int64, product *dbstruct.Pr Mid: goproto.Int64(mid), ProductId: goproto.String(productId), Ct: goproto.Int64(timeNow), - Means: goproto.String(dbstruct.UserVasUnlockMeansMoney), + Means: goproto.String(dbstruct.UserVasUnlockMembershipMeansMoney), OrderId: order.ID, } err = v.store.CreateUserVasMembershipUnlock(ctx, tx, userVasMembershipUnlock) diff --git a/dbstruct/vas_mysql.go b/dbstruct/vas_mysql.go index 651ab187..c16bb628 100644 --- a/dbstruct/vas_mysql.go +++ b/dbstruct/vas_mysql.go @@ -614,6 +614,11 @@ const ( UserVasUnlockMeansMoney = "money" // 现金解锁 ) +const ( + UserVasUnlockMembershipMeansCoins = "coins" // 金币解锁 + UserVasUnlockMembershipMeansMoney = "money" // 现金解锁 +) + type UserVasUnlock struct { Id *int64 `json:"id" db:"id"` Mid *int64 `json:"mid" db:"mid"` -- 2.41.0