service/app/mix/service/logic/vas.go

2190 lines
63 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package logic
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"database/sql"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
"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"
"service/bizcommon/util"
"service/dbstruct"
"service/library/idgenerator"
"service/library/logger"
"service/library/payclients/alipaycli"
"time"
"github.com/go-pay/gopay/alipay"
"github.com/gin-gonic/gin"
"github.com/jmoiron/sqlx"
"github.com/samber/lo"
goproto "google.golang.org/protobuf/proto"
)
type Vas struct {
store *dao.Store
streamer *Streamer
account *Account
}
func NewVas(store *dao.Store, streamer *Streamer, account *Account) (v *Vas) {
return &Vas{
store: store,
account: account,
streamer: streamer,
}
}
func (v *Vas) GetCoinsProductList(ctx *gin.Context, req *vasproto.GetCoinsProductListReq) (listAlipayH5 []*dbstruct.Product, err error) {
// 获取所有金币商品
list, err := v.store.GetProductByDtType(ctx, req.DevType, dbstruct.ProductTypeCoins)
if err != nil {
logger.Error("GetProductByDtType fail, mid: %v, dt: %v", req.Mid, req.DevType)
return
}
logger.Info("GetProductByDtType, len: %v", len(list))
productMap := lo.SliceToMap(list, func(item *dbstruct.Product) (string, *dbstruct.Product) {
return item.Id, item
})
productIdSeqH5 := []string{
dbstruct.ProductIdH5Coin500,
dbstruct.ProductIdH5Coin1000,
dbstruct.ProductIdH5Coin3000,
dbstruct.ProductIdH5Coin5000,
dbstruct.ProductIdH5Coin8000,
dbstruct.ProductIdH5Coin10000,
dbstruct.ProductIdH5Coin15000,
dbstruct.ProductIdH5Coin18000,
dbstruct.ProductIdH5Coin20000,
}
listAlipayH5 = make([]*dbstruct.Product, 0)
for _, id := range productIdSeqH5 {
if v, ok := productMap[id]; ok {
listAlipayH5 = append(listAlipayH5, v)
}
}
return
}
func (v *Vas) CreateOrder(ctx *gin.Context, req *vasproto.CreateOrderReq) (data *vasproto.CreateOrderData, err error) {
var (
productId = req.ProductId
order *dbstruct.Order
orderId = idgenerator.GenOrderId() // 订单id
)
var (
alipayParamStr string
alipayH5ParamStr string
)
defer func() {
if order != nil {
_ = v.AddOplogOrder(
ctx,
&dbstruct.OplogOrder{
OrderId: orderId,
Action: dbstruct.OrderOpLogActionAdd,
Operator: req.Operator,
Detail: "创建订单",
BeforeStatus: dbstruct.VasOrderStatusNone,
AfterStatus: util.DerefInt32(order.OrderStatus),
},
err,
)
}
}()
// 商品id特殊处理
switch productId {
// h5直接购买微信
case dbstruct.ProductIdH5ContactWechat:
if req.Uid <= 0 {
logger.Error("invalid uid, req: %v", util.ToJson(req))
err = errs.ErrVasInvalidParam
return
}
// 自定义充值金币
case dbstruct.ProductIdH5CustomCoin:
if req.CustomCoins <= 0 {
logger.Error("invalid custom_coins, req: %v", util.ToJson(req))
err = errs.ErrVasInvalidParam
return
}
req.CalcPrice = req.CustomCoins * 10 // 单位:分
}
// 获取商品
product, err := v.store.GetProductById(ctx, productId)
if err != nil {
logger.Error("GetProductById fail, req: %v, err: %v", util.ToJson(req), err)
return
}
if product == nil {
logger.Error("invalid product, req: %v, err: %v", util.ToJson(req), err)
err = errs.ErrVasProductNotExists
return
}
if product.PayMeans != dbstruct.ProductPayMeansMoney {
logger.Error("not money product, req: %v, err: %v", util.ToJson(req), err)
err = errs.ErrVasNotMoneyProduct
return
}
if product.PriceFixType == dbstruct.ProductPriceFixTypeCalc {
if req.CalcPrice <= 0 {
logger.Error("invalid calc price, req: %v", util.ToJson(req))
err = errs.ErrVasInvalidCalcPrice
return
}
product.RealPrice = req.CalcPrice
}
if req.CustomCoins > 0 {
product.ValueCoins = req.CustomCoins
}
// 支付参数
switch req.PayType {
case vasproto.PayTypeOp:
case vasproto.PayTypeAlipay:
alipayCli := alipaycli.GetDefaultAlipayClient()
appPayParam := &alipaycli.AppPayParam{
OutTradeNo: orderId,
Subject: product.Subject,
TotalAmount: product.RealPrice,
TimeOutSeconds: 900,
}
alipayParamStr, err = alipayCli.AppPay(ctx, appPayParam)
if err != nil {
logger.Error("alipay AppPay fail, req: %v, appPayParam: %v, err: %v", util.ToJson(req), util.ToJson(appPayParam), err)
return
}
case vasproto.PayTypeAlipayH5:
alipayCli := alipaycli.GetDefaultAlipayClient()
appPayParam := &alipaycli.WapPayParam{
OutTradeNo: orderId,
Subject: product.Subject,
TotalAmount: product.RealPrice,
TimeOutSeconds: 900,
ReturnUrl: req.ReturnUrl,
}
alipayH5ParamStr, err = alipayCli.WapPay(ctx, appPayParam)
if err != nil {
logger.Error("alipay WapPay fail, req: %v, wapPayParam: %v, err: %v", util.ToJson(req), util.ToJson(appPayParam), err)
return
}
}
// 添加订单
var (
timeNow = time.Now().Unix()
)
order = &dbstruct.Order{
ID: goproto.String(orderId),
Mid: goproto.Int64(req.Mid),
Uid: goproto.Int64(req.Uid),
Oid1: goproto.String(req.Oid1),
Oid2: goproto.String(req.Oid2),
Oid3: goproto.String(req.Oid3),
ProductId: goproto.String(req.ProductId),
PayType: goproto.String(req.PayType),
PayAmount: goproto.Int64(product.RealPrice),
Coins: goproto.Int64(product.ValueCoins),
OrderStatus: goproto.Int32(dbstruct.VasOrderStatusInit),
OrderFrom: goproto.String(req.From),
Ct: goproto.Int64(timeNow),
Ut: goproto.Int64(timeNow),
Operator: goproto.String(req.Operator),
Did: goproto.String(req.Did),
Version: goproto.String(req.Version),
OsVersion: goproto.String(req.Version),
DevType: goproto.Int32(req.DevType),
Channel: goproto.String(req.Channel),
Model: goproto.String(req.Model),
NetType: goproto.String(req.NetType),
Ip: goproto.String(req.Ip),
}
err = v.store.CreateOrder(ctx, nil, order)
if err != nil {
logger.Error("CreateOrder fail, order: %v, err: %v", util.ToJson(order), err)
return
}
data = &vasproto.CreateOrderData{
OrderId: orderId,
AlipayParamStr: alipayParamStr,
AlipayH5ParamStr: alipayH5ParamStr,
}
return
}
func (v *Vas) CheckWalletExist(ctx *gin.Context, mid int64) (wallet *dbstruct.Wallet, exist bool) {
if mid <= 0 {
return
}
wallet, err := v.store.GetWalletByMid(ctx, mid)
switch err {
case sql.ErrNoRows:
err = v.store.CreateWallet(ctx, nil, mid)
if err != nil {
logger.Error("CreateWallet fail, mid: %v, err: %v", mid, err)
return
}
logger.Info("CreateWallet success, mid: %v", mid)
wallet, err = v.store.GetWalletByMid(ctx, mid)
if err != nil {
logger.Error("GetWalletByMid after create fail, mid: %v, err: %v", mid, err)
return
}
case nil:
err = nil
default:
logger.Error("GetWalletByMid fail, mid: %v, err: %v", mid, err)
return
}
if wallet == nil {
logger.Error("Wallet not exist, mid: %v, err: %v", mid, err)
return
}
exist = true
return
}
func (v *Vas) CheckCoinOrderUidOwner(ctx *gin.Context, uid int64, orderId string) (coinOrder *dbstruct.CoinOrder, err error) {
coinOrder, err = v.store.GetCoinOrderById(ctx, nil, orderId)
if err != nil {
return
}
if coinOrder == nil {
err = errs.ErrVasOrderNotExists
return
}
if coinOrder.GetUid() != uid {
err = errs.ErrVasOrderNotMatch
return
}
return
}
func (v *Vas) OpCreateOrder(ctx *gin.Context, req *vasproto.OpCreateOrderReq) (err error) {
// 支付参数
var (
orderId = idgenerator.GenOrderId() // 订单id
timeNow = time.Now().Unix()
mid = req.Mid
coins = req.Coins
order *dbstruct.Order
)
req.Money = coins / 10
// 检查钱包
wallet, has := v.CheckWalletExist(ctx, mid)
if !has {
err = errs.ErrVasWalletNotExist
return
}
// 开启事务
tx, err := v.store.VasBegin(ctx)
if err != nil {
logger.Error("vas begin fail, err: %v", err)
return
}
defer func() {
if order != nil {
_ = v.AddOplogOrder(
ctx,
&dbstruct.OplogOrder{
OrderId: orderId,
Action: dbstruct.OrderOpLogActionAdd,
Operator: req.Operator,
Detail: fmt.Sprintf("后台充值%d金币", coins),
BeforeStatus: dbstruct.VasOrderStatusNone,
AfterStatus: util.DerefInt32(order.OrderStatus),
},
err,
)
}
if err != nil {
logger.Error("global err, req: %v, err: %v", util.ToJson(req), err)
}
errTx := v.store.DealTxCR(tx, err)
if errTx != nil {
logger.Error("DealTxCR fail, err: %v", errTx)
return
}
}()
// 锁定钱包
_, err = v.store.GetWalletForUpdate(ctx, tx, mid)
if err != nil {
logger.Error("GetWalletForUpdate fail, mid: %, err: %v", mid, err)
return
}
// 添加订单
order = &dbstruct.Order{
ID: goproto.String(orderId),
Mid: goproto.Int64(mid),
ProductId: goproto.String(dbstruct.ProductIdOpCoin),
PayType: goproto.String(vasproto.PayTypeOp),
PayAmount: goproto.Int64(req.Money),
Coins: goproto.Int64(coins),
OrderStatus: goproto.Int32(dbstruct.VasOrderStatusFinish),
OrderFrom: goproto.String(dbstruct.VasCoinOrderFromOp),
Ct: goproto.Int64(timeNow),
Ut: goproto.Int64(timeNow),
Operator: goproto.String(req.Operator),
}
err = v.store.CreateOrder(ctx, tx, order)
if err != nil {
logger.Error("CreateOrder fail, order: %v, err: %v", util.ToJson(order), err)
return
}
// 消费历史
ch := &dbstruct.ConsumeHistory{
Mid: goproto.Int64(req.Mid),
Type: goproto.Int32(dbstruct.CHTypeCharge),
SType: goproto.Int32(dbstruct.CHSTypeChargeOp),
TypeId: goproto.String(dbstruct.ProductTypeCoins),
OrderId: goproto.String(orderId),
Change: goproto.Int64(coins),
Before: goproto.Int64(wallet.GetCoins()),
After: goproto.Int64(wallet.GetCoins() + coins),
Count: goproto.Int64(coins),
Ct: goproto.Int64(timeNow),
}
err = v.store.CreateConsumeHistory(ctx, tx, ch)
if err != nil {
logger.Error("CreateConsumeHistory fail, ch: %v, err: %v", util.ToJson(ch), err)
return
}
// 增加金币
err = v.store.IncCoins(ctx, tx, mid, coins)
if err != nil {
logger.Error("IncCoins, mid: %v, coins: %v, err: %v", mid, coins, err)
return
}
return
}
// 预解锁联系方式
func (v *Vas) PreUnlockContact(ctx *gin.Context, req *vasproto.PreUnlockContactReq) (orderIdRet string, err error) {
var (
mid = req.Mid // 解锁人
uid = req.Uid // 要解锁的人,即 mid 要解锁 uid 的联系方式
contactProductId = req.ContactProductId // 要解锁的联系方式
)
// 获取用户钱包
wallet, has := v.CheckWalletExist(ctx, mid)
if !has {
err = errs.ErrVasWalletNotExist
return
}
// 获取uid增值信息
uVasInfo, err := v.store.GetUserVasInfoByMid(ctx, uid)
if err != nil {
logger.Error("GetUserVasInfoByMid fail, mid: %v, uid: %v, err: %v", mid, uid, err)
return
}
if uVasInfo == nil {
err = errs.ErrVasUserVasNotExist
logger.Warn("no user_vas info, mid: %v, uid: %v, err: %v", mid, uid, err)
return
}
var (
coinPrice int64 = 0
)
switch contactProductId {
case dbstruct.ProductIdContactWechat:
// 获取uid微信金币价格
coinPrice = uVasInfo.WechatCoinPrice
default:
err = errs.ErrVasInvalidContactProduct
return
}
if coinPrice <= 0 {
err = errs.ErrVasInvalidCoinPrice
logger.Warn("invalid coin price, mid: %v, uid: %v, contactProductId: %v, coinPrice: %v", mid, uid, contactProductId, coinPrice)
return
}
// 检查用户余额
if coinPrice < wallet.GetCoins() {
err = errs.ErrVasNoEnoughCoin
logger.Warn("no enough coin, mid: %v, uid: %v, contactProductId: %v, coinPrice: %v, userCoins: %v", mid, uid, contactProductId, coinPrice, wallet.GetCoins())
return
}
// 生成金币订单
var (
orderId = idgenerator.GenCoinOrderId()
timeNow = time.Now().Unix()
)
coinOrder := &dbstruct.CoinOrder{
ID: goproto.String(orderId),
Mid: goproto.Int64(req.Mid),
Uid: goproto.Int64(req.Uid),
ProductId: goproto.String(req.ContactProductId),
Coins: goproto.Int64(coinPrice),
OrderStatus: goproto.Int32(dbstruct.VasOrderStatusInit),
Ct: goproto.Int64(timeNow),
Ut: goproto.Int64(timeNow),
Did: goproto.String(req.Did),
Version: goproto.String(req.Version),
OsVersion: goproto.String(req.Version),
DevType: goproto.Int32(req.DevType),
Channel: goproto.String(req.Channel),
Model: goproto.String(req.Model),
NetType: goproto.String(req.NetType),
Ip: goproto.String(req.Ip),
}
err = v.store.CreateCoinOrder(ctx, nil, coinOrder)
if err != nil {
logger.Error("CreateCoinOrder fail, order: %v, err: %v", util.ToJson(coinOrder), err)
return
}
orderIdRet = orderId
return
}
// 解锁联系方式
func (v *Vas) ConfirmUnlockContact(ctx *gin.Context, req *vasproto.ConfirmUnlockContactReq) (contact string, err error) {
var (
mid = req.Mid // 解锁人
orderId = req.OrderId // 订单id
)
// 检查钱包
_, has := v.CheckWalletExist(ctx, mid)
if !has {
err = errs.ErrVasWalletNotExist
return
}
// 开启事务
tx, err := v.store.VasBegin(ctx)
if err != nil {
logger.Error("vas begin fail, err: %v", err)
return
}
defer func() {
if err != nil {
logger.Error("global err, req: %v, err: %v", util.ToJson(req), err)
}
err = v.store.DealTxCR(tx, err)
if err != nil {
logger.Error("DealTxCR fail, err: %v", err)
return
}
}()
// 锁定钉 订单检查
order, err := v.store.GetCoinOrderByIdForUpdate(ctx, tx, orderId)
if err != nil {
logger.Error("GetCoinOrderByIdForUpdate fail, mid: %v, orderId: %v, err: %v", mid, orderId, err)
return
}
if order == nil {
err = errs.ErrVasNoEnoughCoin
logger.Error("invalid order, mid: %v, orderId: %v, err: %v", mid, orderId, err)
return
}
if order.GetMid() != mid {
err = errs.ErrVasOrderNotMatch
logger.Error("order not match, mid: %v, orderMid: %v, orderId: %v, err: %v", mid, order.GetMid(), orderId, err)
return
}
consumeCoins := order.GetCoins()
// 锁钱包 余额检查
wallet, err := v.store.GetWalletForUpdate(ctx, tx, mid)
if err != nil {
logger.Error("GetWalletForUpdate fail, mid: %v, orderId: %v, err: %v", mid, orderId, err)
return
}
if wallet == nil {
err = errs.ErrVasWalletNotExist
logger.Error("invalid wallet, mid: %v, orderId: %v, err: %v", mid, orderId, err)
return
}
if wallet.GetCoins() < consumeCoins {
err = errs.ErrVasNoEnoughCoin
logger.Error("coin not enough, mid: %v, orderId: %v, walletCoins: %v, orderCoins: %v", mid, orderId, wallet.GetCoins(), consumeCoins)
return
}
// 扣钱
err = v.store.DecCoins(ctx, tx, mid, consumeCoins)
if err != nil {
logger.Error("DecCoins fail, mid: %v, err: %v", mid, err)
return
}
// 增加历史
return
}
// 一步解锁联系方式
func (v *Vas) OneStepUnlockContactV2(ctx *gin.Context, req *vasproto.OneStepUnlockContactReq) (contact string, err error) {
var (
mid = req.Mid // 解锁人
uid = req.Uid // 要解锁的人,即 mid 要解锁 uid 的联系方式
contactProductId = req.ContactProductId // 要解锁的联系方式
)
// 获取uid增值信息
uVasInfo, err := v.store.GetUserVasInfoByMid(ctx, uid)
if err != nil {
logger.Error("GetUserVasInfoByMid fail, mid: %v, uid: %v, err: %v", mid, uid, err)
return
}
if uVasInfo == nil {
err = errs.ErrVasUserVasNotExist
logger.Warn("no user_vas info, mid: %v, uid: %v, err: %v", mid, uid, err)
return
}
// uid(网红)钱包
streamerWallet, _ := v.CheckWalletExist(ctx, req.Uid)
// 官网钱包
officialWallet, _ := v.CheckWalletExist(ctx, common.OfficialMid)
// 邀请人钱包
inviterWallet, _ := v.CheckWalletExist(ctx, req.InviterMid)
var (
coinPrice int64 = 0
)
switch contactProductId {
case dbstruct.ProductIdContactWechat:
// 获取uid微信金币价格
coinPrice = uVasInfo.WechatCoinPrice
default:
err = errs.ErrVasInvalidContactProduct
return
}
if coinPrice <= 0 {
err = errs.ErrVasInvalidCoinPrice
logger.Warn("invalid coin price, mid: %v, uid: %v, contactProductId: %v, coinPrice: %v", mid, uid, contactProductId, coinPrice)
return
}
// 开启事务
tx, err := v.store.VasBegin(ctx)
if err != nil {
logger.Error("vas begin fail, err: %v", err)
return
}
defer func() {
if err != nil {
logger.Error("global err, req: %v, err: %v", util.ToJson(req), err)
}
err = v.store.DealTxCR(tx, err)
if err != nil {
logger.Error("DealTxCR fail, err: %v", err)
return
}
contact = uVasInfo.WechatContact
}()
// 锁钱包 余额检查
wallet, err := v.store.GetWalletForUpdate(ctx, tx, mid)
if err != nil {
logger.Error("GetWalletForUpdate fail, mid: %v, uid: %v, err: %v", mid, uid, err)
return
}
if wallet == nil {
err = errs.ErrVasWalletNotExist
logger.Error("invalid wallet, mid: %v, err: %v", mid, err)
return
}
if wallet.GetCoins() < coinPrice {
err = errs.ErrVasNoEnoughCoin
logger.Error("coin not enough, mid: %v, walletCoins: %v, coinPrice: %v", mid, wallet.GetCoins(), coinPrice)
return
}
l := v.NewOneStepUnlockContactLogic(
ctx,
tx,
req,
wallet,
streamerWallet,
officialWallet,
inviterWallet,
coinPrice,
)
// 消费者逻辑
err = l.DoConsumerLogic()
if err != nil {
logger.Error("DoConsumerLogic fail, err: %v", err)
return
}
// 网红逻辑
err = l.DoStreamerLogic()
if err != nil {
logger.Error("DoStreamerLogic fail, err: %v", err)
return
}
// 官方逻辑
err = l.DoOfficialLogic()
if err != nil {
logger.Error("DoOfficialLogic fail, err: %v", err)
return
}
// 邀请人逻辑
err = l.DoInviterLogic()
if err != nil {
logger.Error("DoInviterLogic fail, err: %v", err)
return
}
return
}
// 一步解锁联系方式
func (v *Vas) OneStepUnlockContact(ctx *gin.Context, req *vasproto.OneStepUnlockContactReq) (lockType int32, orderId, retContact string, err error) {
var (
mid = req.Mid // 解锁人
uid = req.Uid // 要解锁的人,即 mid 要解锁 uid 的联系方式
contactProductId = req.ContactProductId // 要解锁的联系方式
)
// 获取uid的邀请人mid
mp, _ := v.streamer.GetStreamerMapByMids(ctx, []int64{uid})
if mp[uid] != nil && mp[uid].Inviters != nil && len(*mp[uid].Inviters) > 0 {
req.InviterMid = (*mp[uid].Inviters)[0]
}
// 检查钱包
_, has := v.CheckWalletExist(ctx, mid)
if !has {
err = errs.ErrVasWalletNotExist
return
}
// uid(网红)钱包
streamerWallet, _ := v.CheckWalletExist(ctx, uid)
// 官网钱包
officialWallet, _ := v.CheckWalletExist(ctx, common.OfficialMid)
// 邀请人钱包
inviterWallet, _ := v.CheckWalletExist(ctx, req.InviterMid)
v.CheckWalletExist(ctx, common.OfficialMid)
if req.InviterMid > 0 {
v.CheckWalletExist(ctx, req.InviterMid)
}
// 是否已经解锁过
unlockInfo, _ := v.store.GetUserVasUnlock(ctx, nil, mid, uid, contactProductId)
if unlockInfo != nil {
err = errs.ErrVasAlreadyUnlock
return
}
// 获取uid增值信息
uVasInfo, err := v.store.GetUserVasInfoByMid(ctx, uid)
if err != nil {
logger.Error("GetUserVasInfoByMid fail, mid: %v, uid: %v, err: %v", mid, uid, err)
return
}
if uVasInfo == nil {
err = errs.ErrVasUserVasNotExist
logger.Warn("no user_vas info, mid: %v, uid: %v, err: %v", mid, uid, err)
return
}
lockType = uVasInfo.WechatLockType
var (
coinPrice int64 = 0
timeNow = time.Now().Unix()
coinOrderId = idgenerator.GenCoinOrderId()
coinOrder *dbstruct.CoinOrder
)
switch contactProductId {
case dbstruct.ProductIdContactWechat:
// 获取uid微信金币价格
coinPrice = uVasInfo.WechatCoinPrice
case dbstruct.ProductIdH5ContactWechat:
contactProductId = dbstruct.ProductIdContactWechat
coinPrice = uVasInfo.GetH5WechatCoinPrice()
default:
err = errs.ErrVasInvalidContactProduct
return
}
if coinPrice <= 0 {
err = errs.ErrVasInvalidCoinPrice
logger.Warn("invalid coin price, mid: %v, uid: %v, contactProductId: %v, coinPrice: %v", mid, uid, contactProductId, coinPrice)
return
}
// 开启事务
tx, err := v.store.VasBegin(ctx)
if err != nil {
logger.Error("vas begin fail, err: %v", err)
return
}
defer func() {
_ = v.AddOplogCoinOrder(
ctx,
&dbstruct.OplogCoinOrder{
OrderId: coinOrderId,
Mid: mid,
Action: dbstruct.OrderOpLogActionAdd,
BeforeStatus: dbstruct.VasCoinOrderStatusNone,
AfterStatus: coinOrder.GetOrderStatus(),
},
err,
)
if err != nil {
logger.Error("global err, req: %v, err: %v", util.ToJson(req), err)
}
errTx := v.store.DealTxCR(tx, err)
if errTx != nil {
logger.Error("DealTxCR fail, err: %v", errTx)
return
}
orderId = coinOrderId
switch contactProductId {
case dbstruct.ProductIdContactWechat:
if uVasInfo.WechatLockType == dbstruct.UserVasLockTypeOpen {
retContact = uVasInfo.WechatContact
}
}
}()
// 锁钱包 余额检查
wallet, err := v.store.GetWalletForUpdate(ctx, tx, mid)
if err != nil {
logger.Error("GetWalletForUpdate fail, mid: %v, uid: %v, err: %v", mid, uid, err)
return
}
if wallet == nil {
err = errs.ErrVasWalletNotExist
logger.Error("invalid wallet, mid: %v, err: %v", mid, err)
return
}
if wallet.GetCoins() < coinPrice {
err = errs.ErrVasNoEnoughCoin
logger.Error("coin not enough, mid: %v, walletCoins: %v, coinPrice: %v", mid, wallet.GetCoins(), coinPrice)
return
}
// 生成金币订单
coinOrder = &dbstruct.CoinOrder{
ID: goproto.String(coinOrderId),
Mid: goproto.Int64(req.Mid),
Uid: goproto.Int64(req.Uid),
ProductId: goproto.String(contactProductId),
Coins: goproto.Int64(coinPrice),
Ct: goproto.Int64(timeNow),
Ut: goproto.Int64(timeNow),
Did: goproto.String(req.Did),
Version: goproto.String(req.Version),
OsVersion: goproto.String(req.Version),
DevType: goproto.Int32(req.DevType),
Channel: goproto.String(req.Channel),
Model: goproto.String(req.Model),
NetType: goproto.String(req.NetType),
Ip: goproto.String(req.Ip),
}
switch uVasInfo.WechatLockType {
case dbstruct.UserVasLockTypeOpen:
coinOrder.OrderStatus = goproto.Int32(dbstruct.VasCoinOrderStatusWaitDeal)
case dbstruct.UserVasLockTypeLock:
coinOrder.OrderStatus = goproto.Int32(dbstruct.VasCoinOrderStatusNotFill)
}
err = v.store.CreateCoinOrder(ctx, tx, coinOrder)
if err != nil {
logger.Error("CreateCoinOrder fail, order: %v, err: %v", util.ToJson(coinOrder), err)
return
}
// 增加金币消费历史
ch := &dbstruct.ConsumeHistory{
Mid: goproto.Int64(req.Mid),
Uid: goproto.Int64(req.Uid),
Did: goproto.String(req.Did),
Type: goproto.Int32(dbstruct.CHTypeCost),
SType: goproto.Int32(dbstruct.CHSTypeCostContact),
TypeId: goproto.String(contactProductId),
OrderId: goproto.String(coinOrderId),
Change: goproto.Int64(-coinPrice),
Before: goproto.Int64(util.DerefInt64(wallet.Coins)),
After: goproto.Int64(util.DerefInt64(wallet.Coins) - coinPrice),
Ct: goproto.Int64(timeNow),
}
err = v.store.CreateConsumeHistory(ctx, tx, ch)
if err != nil {
logger.Error("CreateConsumeHistory fail, ch: %v, err: %v", util.ToJson(ch), err)
return
}
// 解锁记录
userVasUnlock := &dbstruct.UserVasUnlock{
Mid: goproto.Int64(req.Mid),
Uid: goproto.Int64(req.Uid),
ProductId: goproto.String(contactProductId),
Ct: goproto.Int64(timeNow),
LockType: goproto.Int32(uVasInfo.WechatLockType),
Means: goproto.String(dbstruct.UserVasUnlockMeansCoins),
OrderId: goproto.String(coinOrderId),
}
switch uVasInfo.WechatLockType {
case dbstruct.UserVasLockTypeOpen:
userVasUnlock.Status = goproto.Int32(dbstruct.UserVasUnlockStatusFinish)
case dbstruct.UserVasLockTypeLock:
userVasUnlock.Status = goproto.Int32(dbstruct.UserVasUnlockStatusWait)
}
err = v.store.CreateUserVasUnlock(ctx, tx, userVasUnlock)
if err != nil {
logger.Error("CreateUserVasUnlock fail, userVasUnlock: %v, err: %v", util.ToJson(userVasUnlock), err)
return
}
// 扣金币
err = v.store.DecCoins(ctx, tx, req.Mid, coinPrice)
if err != nil {
logger.Error("DecCoins fail, mid: %v, coinPrice: %v, err: %v", req.Mid, coinPrice, err)
return
}
// 加钻石
var (
TotalDias = coinPrice
StreamerDias = int64(float64(TotalDias) * 0.8)
InviterDias = int64(0)
OfficialDias = int64(0)
)
if req.InviterMid > 0 {
InviterDias = int64(float64(TotalDias) * 0.05)
}
OfficialDias = TotalDias - StreamerDias - InviterDias
// 主播钻石收益明细
chStreamer := &dbstruct.ConsumeHistory{
Mid: goproto.Int64(req.Uid),
Uid: goproto.Int64(req.Mid),
Did: goproto.String(req.Did),
Type: goproto.Int32(dbstruct.CHTypeIncome),
SType: goproto.Int32(dbstruct.CHSTypeIncomeContact),
TypeId: goproto.String(contactProductId),
OrderId: goproto.String(coinOrderId),
Change: goproto.Int64(StreamerDias),
Before: goproto.Int64(streamerWallet.GetDiamonds()),
After: goproto.Int64(streamerWallet.GetDiamonds() + StreamerDias),
Ct: goproto.Int64(timeNow),
}
err = v.store.CreateConsumeHistory(ctx, tx, chStreamer)
if err != nil {
logger.Error("CreateConsumeHistory fail, ch: %v, err: %v", util.ToJson(chStreamer), err)
return
}
err = v.store.IncDiamonds(ctx, tx, req.Uid, StreamerDias)
if err != nil {
logger.Error("IncDiamonds fail, streamer, mid: %v, dias: %v, err: %v", req.Uid, StreamerDias, err)
return
}
// 官方
chOfficial := &dbstruct.ConsumeHistory{
Mid: goproto.Int64(common.OfficialMid),
Uid: goproto.Int64(req.Mid),
Did: goproto.String(req.Did),
Type: goproto.Int32(dbstruct.CHTypeIncome),
SType: goproto.Int32(dbstruct.CHSTypeIncomeContact),
TypeId: goproto.String(contactProductId),
OrderId: goproto.String(coinOrderId),
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(req.InviterMid),
Uid: goproto.Int64(req.Mid),
Did: goproto.String(req.Did),
Type: goproto.Int32(dbstruct.CHTypeIncome),
SType: goproto.Int32(dbstruct.CHSTypeIncomeInvite),
TypeId: goproto.String(contactProductId),
OrderId: goproto.String(coinOrderId),
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, req.InviterMid, InviterDias)
if err != nil {
logger.Error("IncDiamonds fail, inviter, mid: %v, dias: %v, err: %v", req.InviterMid, InviterDias, err)
return
}
}
return
}
// 用户填写联系方式
func (v *Vas) ConsumerFillContact(ctx *gin.Context, req *vasproto.ConsumerFillContactReq) (err error) {
var (
mid = req.Mid // 解锁人
coinOrderId = req.OrderId
coinOrder *dbstruct.CoinOrder
)
defer func() {
// 操作记录
_ = v.AddOplogCoinOrder(
ctx,
&dbstruct.OplogCoinOrder{
OrderId: coinOrderId,
Mid: mid,
Action: dbstruct.OrderOpLogActionUpdate,
Detail: "填写地址",
BeforeStatus: coinOrder.GetOrderStatus(),
AfterStatus: dbstruct.VasCoinOrderStatusWaitDeal,
},
err,
)
}()
// 检查
coinOrder, _ = v.store.GetCoinOrderById(ctx, nil, coinOrderId)
if coinOrder == nil {
err = errs.ErrVasOrderNotExists
return
}
if util.DerefInt64(coinOrder.Mid) != mid {
err = errs.ErrVasOrderNotMatch
return
}
// 填联系方式
err = v.store.FillCoinOderContact(ctx, nil, coinOrderId, "", req.Wechat, "", "", req.Note)
if err != nil {
logger.Error("FillCoinOderContact fail, req: %v, err: %v", util.ToJson(req), err)
return
}
return
}
// todo 获取待结算订单
func (v *Vas) GetWaitSettleCoinOrders() (list []*dbstruct.CoinOrder, err error) {
return
}
type OneStepUnlockContact struct {
v *Vas
ctx *gin.Context
tx *sqlx.Tx
req *vasproto.OneStepUnlockContactReq
consumerWallet *dbstruct.Wallet
streamerWallet *dbstruct.Wallet
officialWallet *dbstruct.Wallet
inviterWallet *dbstruct.Wallet
coinOrderId string
coinPrice int64
streamerDias int64
officialDias int64
inviterDias int64
}
func (v *Vas) NewOneStepUnlockContactLogic(ctx *gin.Context, tx *sqlx.Tx, req *vasproto.OneStepUnlockContactReq,
consumerWallet, streamerWallet, officialWallet, inviterWallet *dbstruct.Wallet, coinPrice int64) *OneStepUnlockContact {
ret := &OneStepUnlockContact{
v: v,
ctx: ctx,
tx: tx,
req: req,
consumerWallet: consumerWallet,
streamerWallet: streamerWallet,
officialWallet: officialWallet,
inviterWallet: inviterWallet,
coinOrderId: idgenerator.GenCoinOrderId(),
coinPrice: coinPrice,
}
var (
totalDias = coinPrice
)
ret.streamerDias = int64(float64(totalDias) * 0.8)
if req.InviterMid > 0 {
ret.officialDias = int64(float64(totalDias) * 0.2)
} else {
ret.officialDias = int64(float64(totalDias) * 0.15)
}
ret.inviterDias = totalDias - ret.streamerDias - ret.officialDias
return ret
}
// 消费者逻辑
func (l *OneStepUnlockContact) DoConsumerLogic() (err error) {
if l.consumerWallet == nil {
err = errors.New(fmt.Sprintf("invalid consumerWallet, mid: %v", l.req.Mid))
return
}
// 生成金币订单
var (
timeNow = time.Now().Unix()
)
coinOrder := &dbstruct.CoinOrder{
ID: goproto.String(l.coinOrderId),
Mid: goproto.Int64(l.req.Mid),
Uid: goproto.Int64(l.req.Uid),
ProductId: goproto.String(l.req.ContactProductId),
Coins: goproto.Int64(l.coinPrice),
OrderStatus: goproto.Int32(dbstruct.VasCoinOrderStatusPaySuccess),
Ct: goproto.Int64(timeNow),
Ut: goproto.Int64(timeNow),
Did: goproto.String(l.req.Did),
Version: goproto.String(l.req.Version),
OsVersion: goproto.String(l.req.Version),
DevType: goproto.Int32(l.req.DevType),
Channel: goproto.String(l.req.Channel),
Model: goproto.String(l.req.Model),
NetType: goproto.String(l.req.NetType),
Ip: goproto.String(l.req.Ip),
}
err = l.v.store.CreateCoinOrder(l.ctx, l.tx, coinOrder)
if err != nil {
logger.Error("CreateCoinOrder fail, order: %v, err: %v", util.ToJson(coinOrder), err)
return
}
// 增加金币消费历史
ch := &dbstruct.ConsumeHistory{
Mid: goproto.Int64(l.req.Mid),
Uid: goproto.Int64(l.req.Uid),
Did: goproto.String(l.req.Did),
Type: goproto.Int32(dbstruct.CHTypeCost),
SType: goproto.Int32(dbstruct.CHSTypeCostContact),
TypeId: goproto.String(l.req.ContactProductId),
OrderId: goproto.String(l.coinOrderId),
Change: goproto.Int64(-l.coinPrice),
Before: goproto.Int64(util.DerefInt64(l.consumerWallet.Coins)),
After: goproto.Int64(util.DerefInt64(l.consumerWallet.Coins) - l.coinPrice),
Ct: goproto.Int64(timeNow),
}
err = l.v.store.CreateConsumeHistory(l.ctx, l.tx, ch)
if err != nil {
logger.Error("CreateConsumeHistory fail, ch: %v, err: %v", util.ToJson(ch), err)
return
}
// 增加解锁记录
userVasUnlock := &dbstruct.UserVasUnlock{
Mid: goproto.Int64(l.req.Mid),
Uid: goproto.Int64(l.req.Uid),
ProductId: goproto.String(l.req.ContactProductId),
Ct: goproto.Int64(timeNow),
LockType: nil,
Status: nil,
Means: nil,
OrderId: nil,
}
err = l.v.store.CreateUserVasUnlock(l.ctx, l.tx, userVasUnlock)
if err != nil {
logger.Error("CreateUserVasUnlock fail, userVasUnlock: %v, err: %v", util.ToJson(userVasUnlock), err)
return
}
// 扣钱
err = l.v.store.DecCoins(l.ctx, l.tx, l.req.Mid, l.coinPrice)
if err != nil {
logger.Error("DecCoins fail, mid: %v, coinPrice: %v, err: %v", l.req.Mid, l.coinPrice, err)
return
}
return
}
// 网红逻辑
func (l *OneStepUnlockContact) DoStreamerLogic() (err error) {
if l.streamerWallet == nil {
err = errors.New(fmt.Sprintf("invalid streamerWallet, mid: %v", l.req.Uid))
return
}
// 收入历史
var (
timeNow = time.Now().Unix()
)
ch := &dbstruct.ConsumeHistory{
Mid: goproto.Int64(l.req.Uid),
Uid: goproto.Int64(l.req.Mid),
Did: goproto.String(l.req.Did),
Type: goproto.Int32(dbstruct.CHTypeIncome),
SType: goproto.Int32(dbstruct.CHSTypeIncomeContact),
TypeId: goproto.String(l.req.ContactProductId),
OrderId: goproto.String(l.coinOrderId),
Change: goproto.Int64(l.streamerDias),
Before: goproto.Int64(util.DerefInt64(l.streamerWallet.Diamonds)),
After: goproto.Int64(util.DerefInt64(l.streamerWallet.Diamonds) + l.streamerDias),
Ct: goproto.Int64(timeNow),
}
err = l.v.store.CreateConsumeHistory(l.ctx, l.tx, ch)
if err != nil {
logger.Error("CreateConsumeHistory fail, ch: %v, err: %v", util.ToJson(ch), err)
return
}
// 加钻石
err = l.v.store.IncDiamonds(l.ctx, l.tx, l.req.Uid, l.streamerDias)
if err != nil {
logger.Error("IncDiamonds fail, mid: %v, dias: %v, err: %v", l.req.Mid, l.streamerDias, err)
return
}
return
}
// 官方逻辑
func (l *OneStepUnlockContact) DoOfficialLogic() (err error) {
if l.officialWallet == nil {
err = errors.New("invalid officialWallet")
return
}
// 收入历史
var (
timeNow = time.Now().Unix()
)
ch := &dbstruct.ConsumeHistory{
Mid: goproto.Int64(common.OfficialMid),
Uid: goproto.Int64(l.req.Mid),
Did: goproto.String(l.req.Did),
Type: goproto.Int32(dbstruct.CHTypeIncome),
SType: goproto.Int32(dbstruct.CHSTypeIncomeContact),
TypeId: goproto.String(l.req.ContactProductId),
OrderId: goproto.String(l.coinOrderId),
Change: goproto.Int64(l.officialDias),
Before: goproto.Int64(util.DerefInt64(l.officialWallet.Diamonds)),
After: goproto.Int64(util.DerefInt64(l.officialWallet.Diamonds) + l.officialDias),
Ct: goproto.Int64(timeNow),
}
err = l.v.store.CreateConsumeHistory(l.ctx, l.tx, ch)
if err != nil {
logger.Error("CreateConsumeHistory fail, ch: %v, err: %v", util.ToJson(ch), err)
return
}
// 加钻石
err = l.v.store.IncDiamonds(l.ctx, l.tx, common.OfficialMid, l.officialDias)
if err != nil {
logger.Error("IncDiamonds fail, mid: %v, dias: %v, err: %v", common.OfficialMid, l.officialDias, err)
return
}
return
}
// 邀请人逻辑
func (l *OneStepUnlockContact) DoInviterLogic() (err error) {
if l.req.InviterMid <= 0 {
return
}
if l.streamerWallet == nil {
err = errors.New(fmt.Sprintf("invalid streamerWallet, mid: %v", l.req.Uid))
return
}
// 收入历史
var (
timeNow = time.Now().Unix()
)
ch := &dbstruct.ConsumeHistory{
Mid: goproto.Int64(l.req.InviterMid),
Uid: goproto.Int64(l.req.Uid),
Did: goproto.String(l.req.Did),
Type: goproto.Int32(dbstruct.CHTypeIncome),
SType: goproto.Int32(dbstruct.CHSTypeIncomeContact),
TypeId: goproto.String(l.req.ContactProductId),
OrderId: goproto.String(l.coinOrderId),
Change: goproto.Int64(l.inviterDias),
Before: goproto.Int64(util.DerefInt64(l.inviterWallet.Diamonds)),
After: goproto.Int64(util.DerefInt64(l.inviterWallet.Diamonds) + l.inviterDias),
Ct: goproto.Int64(timeNow),
}
err = l.v.store.CreateConsumeHistory(l.ctx, l.tx, ch)
if err != nil {
logger.Error("CreateConsumeHistory fail, ch: %v, err: %v", util.ToJson(ch), err)
return
}
// 加钻石
err = l.v.store.IncDiamonds(l.ctx, l.tx, l.req.InviterMid, l.officialDias)
if err != nil {
logger.Error("IncDiamonds fail, mid: %v, dias: %v, err: %v", l.req.InviterMid, l.inviterDias, err)
return
}
return
}
// 添加金币订单操作历史
func (v *Vas) AddOplogCoinOrder(ctx *gin.Context, param *dbstruct.OplogCoinOrder, errIn error) (err error) {
doc := &dbstruct.OplogCoinOrder{
OrderId: param.OrderId,
Mid: param.Mid,
Ct: time.Now().Unix(),
Action: param.Action,
OperatorType: dbstruct.OrderOpLogOperatorTypeOp,
Operator: param.Operator,
Result: dbstruct.OrderOpLogResultOk,
}
if param.Mid > 0 {
doc.OperatorType = dbstruct.OrderOpLogOperatorTypeUser
doc.Operator = fmt.Sprintf("%d", param.Mid)
}
detail := fmt.Sprintf(
"%s->%s\n",
dbstruct.CoinOrderStatusDescMap[param.BeforeStatus], dbstruct.CoinOrderStatusDescMap[param.AfterStatus],
)
if len(param.Detail) > 0 {
detail += fmt.Sprintf("%s", param.Detail)
}
if errIn != nil {
detail = fmt.Sprintf("\nerr:%v", errIn)
doc.Result = dbstruct.OrderOpLogResultFail
}
doc.Detail = detail
err = v.store.AddOplogCoinOrder(ctx, doc)
return
}
// 订单操作记录
func (v *Vas) AddOplogOrder(ctx *gin.Context, param *dbstruct.OplogOrder, errIn error) (err error) {
doc := &dbstruct.OplogOrder{
OrderId: param.OrderId,
Ct: time.Now().Unix(),
Action: param.Action,
OperatorType: dbstruct.OrderOpLogOperatorTypeOp,
Operator: param.Operator,
Result: dbstruct.OrderOpLogResultOk,
}
if param.Mid > 0 {
doc.OperatorType = dbstruct.OrderOpLogOperatorTypeUser
doc.Operator = fmt.Sprintf("%d", param.Mid)
}
detail := fmt.Sprintf(
"%s->%s\n",
dbstruct.OrderStatusDescMap[param.BeforeStatus], dbstruct.OrderStatusDescMap[param.AfterStatus],
)
if len(param.Detail) > 0 {
detail += fmt.Sprintf("%s", param.Detail)
}
if errIn != nil {
detail = fmt.Sprintf("\nerr:%v", errIn)
doc.Result = dbstruct.OrderOpLogResultFail
}
doc.Detail = detail
err = v.store.AddOplogOrder(ctx, doc)
return
}
// 获取待添加微信列表
func (v *Vas) GetAddWechatCoinOrders(ctx *gin.Context, mid int64, offset, limit int, tab int32) (list []*dbstruct.CoinOrder, err error) {
statusList := make([]int32, 0)
switch tab {
case vasproto.AddWechatListTabWait:
statusList = []int32{dbstruct.VasCoinOrderStatusNotFill, dbstruct.VasCoinOrderStatusWaitDeal, dbstruct.VasCoinOrderStatusRefund}
case vasproto.AddWechatListTabFinish:
statusList = []int32{dbstruct.VasCoinOrderStatusDeal, dbstruct.VasCoinOrderStatusFinish, dbstruct.VasCoinOrderStatusRefund}
}
list, err = v.store.GetWaitWechatAddCoinOrders(ctx, nil, mid, statusList, offset, limit)
if err == sql.ErrNoRows {
err = nil
return
}
return
}
// 确定添加微信
func (v *Vas) ConfirmAddWechat(ctx *gin.Context, mid int64, coinOrderId string) (err error) {
var coinOrder *dbstruct.CoinOrder
defer func() {
// 操作记录
_ = v.AddOplogCoinOrder(
ctx,
&dbstruct.OplogCoinOrder{
OrderId: coinOrderId,
Mid: mid,
Action: dbstruct.OrderOpLogActionUpdate,
BeforeStatus: coinOrder.GetOrderStatus(),
AfterStatus: dbstruct.VasCoinOrderStatusDeal,
},
err,
)
}()
// 获取订单
coinOrder, err = v.CheckCoinOrderUidOwner(ctx, mid, coinOrderId)
if err != nil {
logger.Error("CheckCoinOrderUidOwner fail, uid: %v, coinOrderId: %v, err: %v", mid, coinOrderId, err)
return
}
if coinOrder.GetOrderStatus() >= dbstruct.VasCoinOrderStatusDeal {
err = errs.ErrVasRepeatDeal
return
}
err = v.store.ConfirmAddWechat(ctx, nil, coinOrderId)
if err != nil {
logger.Error("ConfirmAddWechat fail, mid: %v, coinOrderId: %v, err: %v", mid, coinOrderId, err)
return
}
return
}
// 已解锁微信列表
func (v *Vas) GetUnlockWechatList(ctx *gin.Context, mid int64, offset, limit int) (list []*dbstruct.UserVasUnlock, err error) {
list, err = v.store.GetUnlockWechatList(ctx, nil, mid, offset, limit)
if err == sql.ErrNoRows {
err = nil
return
}
return
}
// 消费历史
func (v *Vas) GetCHList(ctx *gin.Context, mid int64, typ int32, offset, limit int) (list []*dbstruct.ConsumeHistory, err error) {
list, err = v.store.GetUCHList(ctx, nil, mid, typ, offset, limit)
if err == sql.ErrNoRows {
err = nil
return
}
return
}
// 更新微信
func (v *Vas) UpdateWechat(ctx *gin.Context, req *vasproto.UpdateWechatReq) (err error) {
err = v.store.UpdateUserVasInfo(ctx, req)
return
}
// 查看微信
func (v *Vas) QueryWechat(ctx *gin.Context, req *vasproto.QueryWechatReq) (isUnlock, lockType int32, wechat string, err error) {
uu, err := v.store.GetUserVasUnlock(ctx, nil, req.Mid, req.Uid, dbstruct.ProductIdContactWechat)
if err == sql.ErrNoRows || uu == nil {
err = errs.ErrVasNotUnlock
return
}
isUnlock = 1
lockType = uu.GetLockType()
var uVasInfo *dbstruct.UserVasInfo
switch lockType {
case dbstruct.UserVasLockTypeLock:
return
case dbstruct.UserVasLockTypeOpen:
uVasInfo, err = v.store.GetUserVasInfoByMid(ctx, req.Uid)
if err != nil {
logger.Error("GetUserVasInfoByMid fail, req: %v, err: %v", util.ToJson(req), err)
return
}
if uVasInfo == nil {
logger.Warn("no uVasInfo fail, req: %v", util.ToJson(req))
return
}
wechat = uVasInfo.WechatContact
}
return
}
// h5直接解锁微信
func (v *Vas) H5DirectUnlockWechat(ctx *gin.Context, req *vasproto.H5DirectUnlockWechatReq) (data *vasproto.H5DirectUnlockWechatData, err error) {
var (
uid = req.Uid
)
// 是否重复解锁
unlockInfo, _ := v.store.GetUserVasUnlock(ctx, nil, req.Mid, uid, dbstruct.ProductIdContactWechat)
if unlockInfo != nil {
err = errs.ErrVasAlreadyUnlock
return
}
// 获取主播信息
uVas, _ := v.store.GetUserVasInfoByMid(ctx, uid)
if uVas == nil {
err = errs.ErrVasUserVasNotExist
return
}
// 检查钱包
wallet, _ := v.CheckWalletExist(ctx, req.Mid)
// 金币够不够
if wallet.GetCoins() >= uVas.GetH5WechatCoinPrice() {
_, _, _, err = v.OneStepUnlockContact(ctx, &vasproto.OneStepUnlockContactReq{
BaseRequest: req.BaseRequest,
ContactProductId: dbstruct.ProductIdContactWechat,
Uid: req.Uid,
InviterMid: req.InviterMid,
})
if err != nil {
logger.Error("OneStepUnlockContact fail, req: %v, err: %v", util.ToJson(req), err)
return
}
data = &vasproto.H5DirectUnlockWechatData{
CoinEnough: 1,
}
return
}
// 创建订单
cData, err := v.CreateOrder(ctx, &vasproto.CreateOrderReq{
BaseRequest: req.BaseRequest,
Ip: ctx.ClientIP(),
ProductId: dbstruct.ProductIdH5ContactWechat,
PayType: req.PayType,
From: dbstruct.VasCoinOrderFromH5,
CustomCoins: uVas.GetH5WechatCoinPrice(),
CalcPrice: uVas.GetH5WechatCoinPrice() * 10,
Uid: uid,
Oid1: fmt.Sprintf("%d", uid),
ReturnUrl: "https://tiefen.fun/purchased",
})
if err != nil {
logger.Error("CreateOrder fail, req: %v, err: %v", util.ToJson(req), err)
return
}
data = &vasproto.H5DirectUnlockWechatData{
OrderId: cData.OrderId,
AlipayParamStr: cData.AlipayParamStr,
AlipayH5ParamStr: cData.AlipayH5ParamStr,
}
return
}
// 主播增值信息
func (v *Vas) GetUserVasInfo(ctx *gin.Context, mid int64) (uVas *dbstruct.UserVasInfo, err error) {
uVas, err = v.store.GetUserVasInfoByMid(ctx, mid)
if err == sql.ErrNoRows {
err = nil
return
}
return
}
// 微信解锁信息
func (v *Vas) GetUserWechatUnlock(ctx *gin.Context, mid, uid int64) (uu *dbstruct.UserVasUnlock, err error) {
uu, err = v.store.GetUserVasUnlock(ctx, nil, mid, uid, dbstruct.ProductIdContactWechat)
if err == sql.ErrNoRows {
err = nil
return
}
return
}
// 支付宝【支付】回调
func (v *Vas) AlipayCallback(ctx *gin.Context, p *vasproto.AlipayCallbackParamIn) {
var (
orderId = p.OrderId
alipayOrderId = p.AlipayOrderId
afterStatus int32
)
// 获取订单
checkOrder, err := v.store.GetOrderById(ctx, nil, orderId)
if err != nil {
logger.Error("GetOrderById fail, p: %v, err: %v", util.ToJson(p), err)
return
}
if checkOrder == nil {
logger.Warn("GetOrderById nil, p: %v", util.ToJson(p))
return
}
// 是否已处理过的订单
if checkOrder.GetOrderStatus() != dbstruct.VasOrderStatusInit {
logger.Error("repeat deal, p: %v", util.ToJson(p))
return
}
// ali_order_id检查
outOrder, err := v.store.GetOrderByOutOrderId(ctx, nil, alipayOrderId)
switch err {
case sql.ErrNoRows:
err = nil
}
if err != nil {
logger.Error("GetOrderByOutOrderId fail, p: %v, err: %v", util.ToJson(p), err)
return
}
if outOrder != nil {
logger.Error("out order exists, p: %v", util.ToJson(p))
return
}
// 获取商品
product, err := v.store.GetProductById(ctx, checkOrder.GetProductId())
if err != nil {
logger.Error("GetProductById fail, id: %v, err: %v", checkOrder.GetProductId(), err)
return
}
if product == nil {
logger.Error("GetProductById nil, id: %v", checkOrder.GetProductId())
return
}
// 钱包
_, hasWallet := v.CheckWalletExist(ctx, checkOrder.GetMid())
if !hasWallet {
logger.Error("CheckWalletExist fail, mid: %v", checkOrder.GetMid())
return
}
// 开启事务
tx, err := v.store.VasBegin(ctx)
if err != nil {
logger.Error("vas begin fail, err: %v", err)
return
}
defer func() {
_ = v.AddOplogOrder(
ctx,
&dbstruct.OplogOrder{
OrderId: orderId,
Mid: checkOrder.GetMid(),
Action: dbstruct.OrderOpLogActionAdd,
Detail: fmt.Sprintf("充值%d金币", checkOrder.GetCoins()),
BeforeStatus: checkOrder.GetOrderStatus(),
AfterStatus: afterStatus,
},
err,
)
if err != nil {
logger.Error("global err, p: %v, order: %v, err: %v", util.ToJson(p), util.ToJson(checkOrder), err)
}
errTx := v.store.DealTxCR(tx, err)
if errTx != nil {
logger.Error("DealTxCR fail, err: %v", errTx)
return
}
// 后续处理
switch {
case product.Id == dbstruct.ProductIdH5ContactWechat:
// 解锁
_, _, _, errIn := v.OneStepUnlockContact(ctx, &vasproto.OneStepUnlockContactReq{
BaseRequest: base.BaseRequest{
Mid: checkOrder.GetMid(),
},
ContactProductId: dbstruct.ProductIdH5ContactWechat,
Uid: checkOrder.GetUid(),
})
if errIn != nil {
logger.Error("OneStepUnlockContact fail, order: %v", util.ToJson(checkOrder))
return
}
}
}()
// 锁住订单
order, err := v.store.GetOrderByIdForUpdate(ctx, tx, orderId)
if err != nil {
logger.Error("GetOrderByIdForUpdate fail, p: %v", util.ToJson(p))
return
}
// 锁住钱包
wallet, _ := v.store.GetWalletForUpdate(ctx, tx, order.GetMid())
// 消费历史
ch := &dbstruct.ConsumeHistory{
Mid: goproto.Int64(order.GetMid()),
Type: goproto.Int32(dbstruct.CHTypeCharge),
SType: goproto.Int32(dbstruct.CHSTypeChargeUser),
TypeId: goproto.String(dbstruct.ProductTypeCoins),
OrderId: goproto.String(orderId),
Change: goproto.Int64(order.GetCoins()),
Before: goproto.Int64(wallet.GetCoins()),
After: goproto.Int64(wallet.GetCoins() + order.GetCoins()),
Count: goproto.Int64(order.GetCoins()),
Ct: goproto.Int64(time.Now().Unix()),
}
err = v.store.CreateConsumeHistory(ctx, tx, ch)
if err != nil {
logger.Error("CreateConsumeHistory fail, ch: %v, err: %v", util.ToJson(ch), err)
return
}
// 更新状态
err = v.store.UpdateOrderStatus(ctx, tx, orderId, dbstruct.VasOrderStatusInit, dbstruct.VasOrderStatusPaySuccess)
if err != nil {
logger.Error("UpdateOrderStatus fail, p: %v", util.ToJson(p))
return
}
afterStatus = dbstruct.VasOrderStatusPaySuccess
// 更新支付宝订单
err = v.store.UpdateOutOrderId(ctx, tx, orderId, alipayOrderId)
if err != nil {
logger.Error("UpdateOutOrderId fail, p: %v", util.ToJson(p))
return
}
// 商品判断
switch {
case product.Type == dbstruct.ProductTypeCoins:
// 充金币
err = v.store.IncCoins(ctx, tx, order.GetMid(), order.GetCoins())
if err != nil {
logger.Error("IncCoins fail, order: %v", util.ToJson(order))
return
}
err = v.store.UpdateOrderStatus(ctx, tx, orderId, dbstruct.VasOrderStatusPaySuccess, dbstruct.VasOrderStatusFinish)
if err != nil {
logger.Error("UpdateOrderStatus fail, order: %v", util.ToJson(order))
return
}
afterStatus = dbstruct.VasOrderStatusFinish
case product.Id == dbstruct.ProductIdH5ContactWechat:
// 充金币
err = v.store.IncCoins(ctx, tx, order.GetMid(), order.GetCoins())
if err != nil {
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, wallet)
if err != nil {
logger.Error("UnlockMembership fail, order: %v", util.ToJson(order))
return
}
}
}
func (v *Vas) GetCoinOrderById(ctx *gin.Context, id string) (*dbstruct.CoinOrder, error) {
return v.store.GetCoinOrderById(ctx, nil, id)
}
func (v *Vas) GetOrderCountGroupByStatus(ctx *gin.Context, req *vasproto.GetOrderByStatusReq) ([]*dbstruct.VasOrderStatusCount, error) {
return v.store.GetOrderCountGroupByStatus(ctx, nil, req.OrderStatuses, req.CtStart, req.CtEnd)
}
var privateKey = []byte(`
-----BEGIN RSA PRIVATE KEY-----
MIICXgIBAAKBgQDlQ1WoxfQUmNHKFQiRLSq7Bu+vUHV4UIpEf6qgJ1m9ExIAsTGT
BVu5v4U1A8Z1uprov2vb+a6XA1I61liZ0Z7aaYDr8IhY/wuktmNLxqIkzE3Vhvk/
TGJIkLC8RUYxnzyIgGK4UuzfOj1S/o2ymjZo44+sBXEhAF+PWgpqFVtewwIDAQAB
AoGBAOS11qdmy0cc6PSDJSfG+kDX+5ZWWsnq9vS8s5fPicuAUc5U9pKnnsjf0eCA
YqShwtX72Hr7S3ulOYwutvbEUoXHMC7dVLmAaoby86A7lJYaDABbKN13PFMwIvGi
lcQCqxTzz0Uvd5Jx3QettIBjNOi/rfa2ZNLcCn2uPU5cEdM5AkEA/UxTgFOdIWPN
WdMeBDw1aXjSGgpVRFhLbBmVfpdi63l6CZbANu14D9ljaF2/XViM4ynOb+n0rca0
toJLdm0+bwJBAOe1YF1NjUDtsdYSH0RNyOSXfUqClPfFOIuPKxvPnTn8R/AnLAwc
51iQx4vldXYFnW1vBc7WyOTBI5+PsqtZju0CQQDMCgfZf4E7vGFW0jGDx9xesezM
/TXicB2RXqqF5vzQInKj9sOve2sTmVHyaFIWp5YWBz8794IZ2c8IlbykESwRAkEA
38gl1Jb0yHOIoMaJ2g8B6fyBLjglpZKdhPP133tJT1pfJArBGMXFjZzujCdFpYHQ
xINIaba4+W2reQxws9rgFQJAPOB0/ymfTRDNgKZBB/MFHQcRA4z3L591lwEFN9Bl
qE6UU5JMf5egQk1A9kyz1RIXaEC+L2LTi9TiWoy+XxGrAg==
-----END RSA PRIVATE KEY-----
`)
// 公钥: 根据私钥生成
// openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
var publicKey = []byte(`
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDlQ1WoxfQUmNHKFQiRLSq7Bu+v
UHV4UIpEf6qgJ1m9ExIAsTGTBVu5v4U1A8Z1uprov2vb+a6XA1I61liZ0Z7aaYDr
8IhY/wuktmNLxqIkzE3Vhvk/TGJIkLC8RUYxnzyIgGK4UuzfOj1S/o2ymjZo44+s
BXEhAF+PWgpqFVtewwIDAQAB
-----END PUBLIC KEY-----
`)
// 加密
func RsaEncrypt(origData []byte) ([]byte, error) {
//解密pem格式的公钥
block, _ := pem.Decode(publicKey)
if block == nil {
return nil, errors.New("public key error")
}
// 解析公钥
pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
// 类型断言
pub := pubInterface.(*rsa.PublicKey)
//加密
return rsa.EncryptPKCS1v15(rand.Reader, pub, origData)
}
// 解密
func RsaDecrypt(ciphertext []byte) ([]byte, error) {
//解密
block, _ := pem.Decode(privateKey)
if block == nil {
return nil, errors.New("private key error!")
}
//解析PKCS1格式的私钥
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
// 解密
return rsa.DecryptPKCS1v15(rand.Reader, priv, ciphertext)
}
// 提现申请
func (v *Vas) WithdrawApply(ctx *gin.Context, req *vasproto.WithdrawApplyReq) (transferResp *alipay.FundTransUniTransferResponse, err error) {
var (
mid = req.Mid
diamonds = req.Diamonds
money = req.Diamonds * 10
authAlipayId = req.AuthAlipayId
authAlipayName = req.AuthAlipayName
)
// 今日提现次数
st := util.GetTodayZeroTime().Unix()
et := st + 86400
list, _ := v.store.GetWithdrawOrdersByMid(ctx, nil, mid, st, et)
if len(list) > 0 {
err = errs.ErrVasOverTodayWithdrawCnt
return
}
// todo 分布式锁
// 检查余额
wallet, _ := v.CheckWalletExist(ctx, mid)
if wallet == nil {
err = errs.ErrVasWalletNotExist
return
}
if wallet.GetWithdrawDiamonds() < diamonds {
err = errs.ErrVasNoEnoughWithdrawDias
return
}
// 支付宝账号解密
alipayIdDecodeBytes, err := base64.StdEncoding.DecodeString(authAlipayId)
if err != nil {
logger.Error("DecodeString authAlipayId fail, req: %v, err: %v", util.ToJson(req), err)
return
}
alipayIdBytes, err := RsaDecrypt(alipayIdDecodeBytes)
if err != nil {
logger.Error("RsaDecrypt authAlipayId fail, req: %v, err: %v", util.ToJson(req), err)
return
}
alipayId := string(alipayIdBytes)
// 支付宝姓名
alipayNameDecodeBytes, err := base64.StdEncoding.DecodeString(authAlipayName)
if err != nil {
logger.Error("DecodeString authAlipayName fail, req: %v, err: %v", util.ToJson(req), err)
return
}
alipayNameBytes, err := RsaDecrypt(alipayNameDecodeBytes)
if err != nil {
logger.Error("RsaDecrypt authAlipayName fail, req: %v, err: %v", util.ToJson(req), err)
return
}
alipayName := string(alipayNameBytes)
var (
wOrder *dbstruct.WithdrawOrder
orderId = idgenerator.GenWithdrawOrderId()
)
// 开启事务
tx, err := v.store.VasBegin(ctx)
if err != nil {
logger.Error("vas begin fail, err: %v", err)
return
}
defer func() {
if err != nil {
logger.Error("global err, req: %v, err: %v", util.ToJson(req), err)
}
errTx := v.store.DealTxCR(tx, err)
if errTx != nil {
logger.Error("DealTxCR fail, err: %v", errTx)
return
}
}()
// 锁定钱包
walletLock, err := v.store.GetWalletForUpdate(ctx, tx, mid)
if err != nil {
logger.Error("GetWalletForUpdate fail, mid: %, err: %v", mid, err)
return
}
// 扣提现钻石
err = v.store.DecWithdrawDiamonds(ctx, tx, mid, diamonds)
if err != nil {
logger.Error("DecWithdrawDiamonds fail, mid: %, err: %v", mid, err)
return
}
// 添加提现订单
wOrder = &dbstruct.WithdrawOrder{
ID: goproto.String(orderId),
Mid: goproto.Int64(mid),
Did: goproto.String(req.Did),
ApplyTime: goproto.Int64(time.Now().Unix()),
AlipayId: goproto.String(alipayId),
AlipayName: goproto.String(alipayName),
WithdrawDias: goproto.Int64(diamonds),
WithdrawMoney: goproto.Int64(money),
Ip: goproto.String(req.Ip),
OrderStatus: goproto.Int32(dbstruct.VasWithdrawOrderStatusInit),
Operator: goproto.String(""),
OpTime: goproto.Int64(0),
}
err = v.store.CreateWithdrawOrder(ctx, tx, wOrder)
if err != nil {
logger.Error("CreateWithdrawOrder fail, req: %v, order: %v, err: %v", req, wOrder.ToString(), err)
return
}
// 2000元以下直接操作
if money <= 200000 {
// 更改状态
err = v.store.UpdateWithdrawOrderStatus(ctx, tx, orderId, dbstruct.VasWithdrawOrderStatusInit, dbstruct.VasWithdrawOrderStatusAuto)
if err != nil {
logger.Error("UpdateWithdrawOrderStatus fail, order: %v, err: %v", wOrder.ToString(), err)
return
}
// 添加提现记录
chWithdraw := &dbstruct.ConsumeHistory{
Mid: goproto.Int64(mid),
Did: goproto.String(req.Did),
Type: goproto.Int32(dbstruct.CHTypeWithdraw),
SType: goproto.Int32(dbstruct.CHSTypeWithdrawDiamondAuto),
TypeId: goproto.String("auto_withdraw_diamonds"),
OrderId: goproto.String(orderId),
Change: goproto.Int64(-diamonds),
Before: goproto.Int64(walletLock.GetWithdrawDiamonds()),
After: goproto.Int64(walletLock.GetWithdrawDiamonds() - diamonds),
Ct: goproto.Int64(time.Now().Unix()),
}
err = v.store.CreateConsumeHistory(ctx, tx, chWithdraw)
if err != nil {
logger.Error("CreateConsumeHistory fail, ch: %v, err: %v", util.ToJson(chWithdraw), err)
return
}
// 支付宝转账
transferParam := &alipaycli.UniTransferParam{
OutBizNo: orderId,
Amount: money,
Title: fmt.Sprintf("%d钻石提现", diamonds),
AlipayLoginId: alipayId,
AlipayName: alipayName,
}
transferResp, err = alipaycli.GetDefaultAlipayClient().UniTransfer(ctx, transferParam)
if err != nil {
logger.Error("UniTransfer fail, param: %v, err: %v", util.ToJson(transferParam), err)
return
}
if transferResp == nil {
err = errors.New("invalid transfer resp")
logger.Error("Invalid transfer resp, param: %v, err: %v", util.ToJson(transferParam), err)
return
}
if transferResp.Response == nil {
err = errors.New("invalid transfer resp")
logger.Error("Invalid transfer resp response, param: %v, resp: %v, err: %v", util.ToJson(transferParam), util.ToJson(transferResp), err)
return
}
if transferResp.Response.Status != "SUCCESS" {
err = errs.ErrVasAlipayUniTransferFail
logger.Error("UniTransfer fail, param: %v, resp: %v, err: %v", util.ToJson(transferParam), util.ToJson(transferResp), err)
return
}
}
return
}
// 结算订单
func (v *Vas) DealOneCoinOrder(ctx *gin.Context, coinOrderId string) (err error) {
coinOrder, err := v.store.GetCoinOrderById(ctx, nil, coinOrderId)
if err != nil {
logger.Error("GetCoinOrderById fail, id: %v, err: %v", coinOrderId, err)
return
}
if coinOrder == nil {
err = errs.ErrVasOrderNotExists
logger.Error("GetCoinOrderById fail nil, id: %v, err: %v", coinOrderId, err)
return
}
// 判断时间小于7天不处理
if time.Now().Unix()-coinOrder.GetCt() < 7*86400 {
logger.Info("DealOneCoinOrder ct fail, coinOrder: %v", coinOrder.ToString())
return
}
if coinOrder.GetOrderStatus() >= dbstruct.VasCoinOrderStatusFinish {
err = errors.New("repeat deal")
logger.Info("DealOneCoinOrder status fail, coinOrder: %v", coinOrder.ToString())
return
}
// 开启事务
tx, err := v.store.VasBegin(ctx)
if err != nil {
logger.Error("vas begin fail, err: %v", err)
return
}
defer func() {
if err != nil {
logger.Error("global err, coinOrder: %v, err: %v", util.ToJson(coinOrder), err)
}
errTx := v.store.DealTxCR(tx, err)
if errTx != nil {
logger.Error("DealTxCR fail, err: %v", errTx)
return
}
}()
// 把金币订单对应的收入记录拿出来
chList, err := v.store.GetIncomeCHList(ctx, tx, coinOrder.GetID())
if err != nil {
logger.Error("GetIncomeCHList fail, orderId: %v, err: %v", coinOrder.GetID(), err)
return
}
// 处理
for _, ch := range chList {
if ch.GetMid() == common.OfficialMid {
continue
}
// 获取钱包
wallet, errIn := v.store.GetWalletForUpdate(ctx, tx, ch.GetMid())
if errIn != nil {
logger.Error("GetWalletForUpdate fail, mid: %v, err: %v", ch.GetMid(), errIn)
err = errIn
return
}
// 添加记录
h := &dbstruct.WithdrawDiamondsHis{
Mid: goproto.Int64(ch.GetMid()),
IncomeChId: goproto.Int64(ch.GetId()),
OrderId: goproto.String(ch.GetOrderId()),
Ct: goproto.Int64(time.Now().Unix()),
BeforeWithdrawDiamonds: goproto.Int64(wallet.GetWithdrawDiamonds()),
AfterWithdrawDiamonds: goproto.Int64(wallet.GetWithdrawDiamonds() + ch.GetChange()),
Change: goproto.Int64(ch.GetChange()),
}
errIn = v.store.CreateWithdrawDiamondsHis(ctx, tx, h)
if errIn != nil {
logger.Error("CreateWithdrawDiamondsHis fail, mid: %v, err: %v", ch.GetMid(), errIn)
err = errIn
return
}
// 更新钱包
errIn = v.store.IncWithdrawDiamonds(ctx, tx, ch.GetMid(), ch.GetChange())
if errIn != nil {
logger.Error("IncWithdrawDiamonds fail, mid: %v, change: %v, err: %v", ch.GetMid(), ch.GetChange(), errIn)
err = errIn
return
}
}
// 更新订单状态
err = v.store.UpdateCoinOrderStatus(ctx, tx, coinOrder.GetID(), dbstruct.VasCoinOrderStatusFinish)
if err != nil {
logger.Error("UpdateCoinOrderStatus fail, orderId: %v, err: %v", coinOrder.GetID(), err)
return
}
return
}
// 解锁会员资格
func (v *Vas) UnlockMembership(ctx *gin.Context, mid int64, product *dbstruct.Product, order *dbstruct.Order, wallet *dbstruct.Wallet) (orderId string, err error) {
var (
did = util.DerefString(order.Did)
productId = product.Id
timeNow = time.Now().Unix()
coinPrice = order.GetCoins()
)
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)
}
// 增加金币消费历史(伪记录,仅为对账平插入一条消费流水)
ch := &dbstruct.ConsumeHistory{
Mid: goproto.Int64(mid),
Uid: goproto.Int64(mid),
Did: goproto.String(did),
Type: goproto.Int32(dbstruct.CHTypeCost),
SType: goproto.Int32(dbstruct.CHSTypeCostMembership),
TypeId: goproto.String(productId),
OrderId: goproto.String(orderId),
Change: goproto.Int64(-coinPrice),
Before: goproto.Int64(util.DerefInt64(wallet.Coins)),
After: goproto.Int64(util.DerefInt64(wallet.Coins) - coinPrice),
Ct: goproto.Int64(timeNow),
}
err = v.store.CreateConsumeHistory(ctx, tx, ch)
if err != nil {
logger.Error("CreateConsumeHistory fail, ch: %v, err: %v", util.ToJson(ch), err)
return
}
// 解锁记录
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.1)
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
}