diff --git a/api/proto/account/proto/account_vo_api.go b/api/proto/account/proto/account_vo_api.go index 17404811..8ff5c498 100644 --- a/api/proto/account/proto/account_vo_api.go +++ b/api/proto/account/proto/account_vo_api.go @@ -16,6 +16,7 @@ type ApiListVO struct { GoldNum *int64 `json:"gold_num" bson:"gold_num"` // 金币数量 DiamondNum *int64 `json:"diamond_num" bson:"diamond_num"` // 钻石数量 WithdrawDiamondNum *int64 `json:"withdraw_diamond_num" bson:"withdraw_diamond_num"` // 提现钻石数量 + IsAMember *int64 `json:"is_a_member" bson:"is_a_member"` // 是否会员 Ct *int64 `json:"ct" bson:"ct"` // 创建时间 Ut *int64 `json:"ut" bson:"ut"` // 更新时间 } @@ -45,6 +46,7 @@ func (vo *ApiListVO) CopyAccount(account *dbstruct.Account) *ApiListVO { vo.IsDndModeEnabled = account.IsDndModeEnabled vo.GoldNum = account.GoldNum vo.DiamondNum = account.DiamondNum + vo.IsAMember = account.IsAMember vo.Ct = account.Ct vo.Ut = account.Ut return vo diff --git a/api/proto/account/proto/account_vo_op.go b/api/proto/account/proto/account_vo_op.go index 3e789fbe..fb5bc3ca 100644 --- a/api/proto/account/proto/account_vo_op.go +++ b/api/proto/account/proto/account_vo_op.go @@ -15,6 +15,7 @@ type OpListVO struct { IsDndModeEnabled *int64 `json:"is_dnd_mode_enabled" bson:"is_dnd_mode_enabled"` // 是否开启勿扰模式 GoldNum *int64 `json:"gold_num" bson:"gold_num"` // 金币数量 DiamondNum *int64 `json:"diamond_num" bson:"diamond_num"` // 钻石数量 + IsAMember *int64 `json:"is_a_member" bson:"is_a_member"` // 是否会员 Ct *int64 `json:"ct" bson:"ct"` // 创建时间 Ut *int64 `json:"ut" bson:"ut"` // 更新时间 } @@ -44,6 +45,7 @@ func (vo *OpListVO) CopyAccount(account *dbstruct.Account) *OpListVO { vo.IsDndModeEnabled = account.IsDndModeEnabled vo.GoldNum = account.GoldNum vo.DiamondNum = account.DiamondNum + vo.IsAMember = account.IsAMember vo.Ct = account.Ct vo.Ut = account.Ut return vo 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 be025104..b3cd6528 100644 --- a/app/mix/dao/mysql.go +++ b/app/mix/dao/mysql.go @@ -61,17 +61,18 @@ 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" // 提现订单表 - TableWithdrawDiamondsHis = "vas_withdraw_diamonds_his" // 提现金币历史 + 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" // 提现订单表 + TableWithdrawDiamondsHis = "vas_withdraw_diamonds_his" // 提现金币历史 + TableVasUserMembershipUnlock = "vas_user_membership_unlock" // 会员资格解锁 ) func (m *Mysql) ChTableName(ch *dbstruct.ConsumeHistory) (string, error) { @@ -615,6 +616,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 7bd63c31..165dee17 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 + } } } @@ -2024,3 +2035,134 @@ func (v *Vas) DealOneCoinOrder(ctx *gin.Context, coinOrderId string) (err error) } return } + +// 解锁会员资格 +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.UserVasUnlockMembershipMeansMoney), + 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 = int64(float64(product.RealPrice) * 0.01) + InviterDias = int64(0) + OfficialDias = int64(0) + ) + if inviterMid > 0 { + InviterDias = int64(float64(TotalDias) * 0.8) + } + 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 b280c6a2..98d05b72 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/app/mix/service/utilservice.go b/app/mix/service/utilservice.go index ccaf6dc7..17cc79f3 100644 --- a/app/mix/service/utilservice.go +++ b/app/mix/service/utilservice.go @@ -65,6 +65,7 @@ func (s *Service) utilRegisterUser(ctx *gin.Context, req *loginproto.MobilePhone account.MobilePhone = goproto.String(req.MobilePhone) account.RegionCode = goproto.String(req.RegionCode) account.PhoneHash = goproto.String(req.PhoneHash) + account.IsAMember = goproto.Int64(0) if inviterUserId != 0 { account.Inviter = goproto.Int64(inviterUserId) } 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.sql b/dbstruct/vas.sql index 230fe8b7..d53d26c4 100644 --- a/dbstruct/vas.sql +++ b/dbstruct/vas.sql @@ -153,4 +153,15 @@ CREATE TABLE `vas_withdraw_diamonds_his` CREATE INDEX ix_mid ON vas_withdraw_diamonds_his (mid); CREATE INDEX ix_ct ON vas_withdraw_diamonds_his (ct); CREATE INDEX ix_order_id ON vas_withdraw_diamonds_his (order_id); -CREATE INDEX ix_chid ON vas_withdraw_diamonds_his (income_ch_id); \ No newline at end of file +CREATE INDEX ix_chid ON vas_withdraw_diamonds_his (income_ch_id); +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 diff --git a/dbstruct/vas_mysql.go b/dbstruct/vas_mysql.go index 292862f1..df488789 100644 --- a/dbstruct/vas_mysql.go +++ b/dbstruct/vas_mysql.go @@ -621,6 +621,11 @@ const ( UserVasUnlockMeansMoney = "money" // 现金解锁 ) +const ( + UserVasUnlockMembershipMeansCoins = "coins" // 金币解锁 + UserVasUnlockMembershipMeansMoney = "money" // 现金解锁 +) + type UserVasUnlock struct { Id *int64 `json:"id" db:"id"` Mid *int64 `json:"mid" db:"mid"` @@ -848,3 +853,26 @@ func (p *WithdrawDiamondsHis) GetChange() 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 "" +}