@@ -0,0 +1,125 @@
|
||||
package rechargeser
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"91porn-server/common/log"
|
||||
"91porn-server/models/commod"
|
||||
"91porn-server/models/v/productmod"
|
||||
"91porn-server/models/v/rchgordmod"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
type Record struct {
|
||||
ID primitive.ObjectID `json:"id" bson:"_id,omitempty"` //流水id
|
||||
UID uint64 `json:"uid" bson:"uid"` //用户id
|
||||
DevID string `json:"devID" bson:"devID"` //设备id
|
||||
OID string `json:"oid" bson:"oid"` //支付平台订单号
|
||||
TraderId string `json:"traderId" bson:"traderId"` //商户ID
|
||||
UserIP string `json:"userIP" bson:"userIP"` //用户ip
|
||||
Name string `json:"name" bson:"name"` //用户名称
|
||||
Tel string `json:"tel" bson:"tel"` //用户手机号
|
||||
PayAct string `json:"payAct" bson:"payAct"` //支付账号
|
||||
DevType string `json:"devType" bson:"devType"` //设备系统类型 ios pc android
|
||||
Amount decimal.Decimal `json:"amount" bson:"amount"` //蝴蝶币数量
|
||||
Money decimal.Decimal `json:"money" bson:"money"` //充值金额 订单金额 李秋山确认
|
||||
PayMoney decimal.Decimal `json:"payMoney" bson:"payMoney"` //实际到账金额 用户实际支付金额 李秋山确认
|
||||
RechargeType string `json:"rechargeType" bson:"rechargeType"` //充值类型
|
||||
ProductID primitive.ObjectID `json:"productID" bson:"productID"` //充值金币类型id
|
||||
VipID primitive.ObjectID `json:"vipID" bson:"vipID"` //是否是vip冲 hi
|
||||
VipName string `json:"vipName" bson:"vipName"` //vip名称
|
||||
Channel string `json:"channel" bson:"channel"` //渠道类型 鲨鱼 金鱼
|
||||
Proof string `json:"proof" bson:"proof"` //代充平台 支付凭证
|
||||
Status int `json:"status" bson:"status"` //1进行中 2付款失败 3付款成功 4已经退款
|
||||
StatusDesc string `json:"statusDesc" bson:"statusDesc"` //状态描述
|
||||
LockStatus int `json:"lockStatus" bson:"lockStatus"` //订单操作锁定状态 用户订单更新操作 0-未锁定 可用于编辑订单 1-已锁定 不可编辑
|
||||
NotifyTime int64 `json:"notifyTime" bson:"notifyTime"` //第三方回调本地订单的时间
|
||||
NotifyStatus int `json:"notifyStatus" bson:"notifyStatus"` //通知第三方上分情况状态 1-成功 2-失败
|
||||
Remark string `json:"remark" bson:"remark"` //备注
|
||||
ProgressAt time.Time `json:"progressAt" bson:"progressAt"` //第三方下单成功时间
|
||||
FailureAt time.Time `json:"failureAt" bson:"failureAt"` //回调失败时间
|
||||
SuccessAt time.Time `json:"successAt" bson:"successAt"` //回调成功时间
|
||||
PaymentAt time.Time `json:"paymentAt" bson:"paymentAt"` //支付时间
|
||||
CreatedAt time.Time `json:"createdAt" bson:"createdAt"` //创建时间
|
||||
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"` //刷新时间
|
||||
commod.DiscDoc `bson:",inline"` //商区
|
||||
ProductType int `json:"productType" bson:"productType"` //支付产品类型 0 站群 1 棋牌
|
||||
SourcePage string `json:"sourcePage" bson:"sourcePage"`
|
||||
SourceRef string `json:"sourceRef" bson:"sourceRef"`
|
||||
VideoID string `json:"videoId" bson:"videoId"`
|
||||
ActivityID string `json:"activityId" bson:"activityId"`
|
||||
ExperimentID string `json:"experimentId" bson:"experimentId"`
|
||||
ExperimentVariant string `json:"experimentVariant" bson:"experimentVariant"`
|
||||
SessionID string `json:"sessionId" bson:"sessionId"`
|
||||
}
|
||||
|
||||
var _100 = decimal.NewFromInt(100)
|
||||
|
||||
func BaiYuan(orders []*rchgordmod.RechargeOrder) []*Record {
|
||||
records := make([]*Record, len(orders))
|
||||
ids := []primitive.ObjectID{}
|
||||
for i, v := range orders {
|
||||
if !v.VipID.IsZero() {
|
||||
ids = append(ids, v.VipID)
|
||||
}
|
||||
records[i] = &Record{
|
||||
ID: v.ID,
|
||||
UID: v.UID,
|
||||
DevID: v.DevID,
|
||||
OID: v.OID,
|
||||
TraderId: v.TraderId,
|
||||
UserIP: v.UserIP,
|
||||
Name: v.Name,
|
||||
Tel: v.Tel,
|
||||
PayAct: v.PayAct,
|
||||
DevType: v.DevType,
|
||||
Amount: decimal.NewFromInt(v.Amount).Div(_100).Round(4),
|
||||
Money: decimal.NewFromInt(v.Money).Div(_100).Round(4),
|
||||
PayMoney: decimal.NewFromInt(v.PayMoney).Div(_100).Round(4),
|
||||
RechargeType: v.RechargeType,
|
||||
ProductID: v.ProductID,
|
||||
VipID: v.VipID,
|
||||
Channel: v.Channel,
|
||||
Proof: v.Proof,
|
||||
Status: v.Status,
|
||||
StatusDesc: v.StatusDesc,
|
||||
LockStatus: v.LockStatus,
|
||||
NotifyTime: v.NotifyTime,
|
||||
NotifyStatus: v.NotifyStatus,
|
||||
Remark: v.Remark,
|
||||
ProgressAt: v.ProgressAt,
|
||||
FailureAt: v.FailureAt,
|
||||
SuccessAt: v.SuccessAt,
|
||||
PaymentAt: v.PaymentAt,
|
||||
CreatedAt: v.CreatedAt,
|
||||
UpdatedAt: v.UpdatedAt,
|
||||
DiscDoc: v.DiscDoc,
|
||||
ProductType: v.ProductType,
|
||||
SourcePage: v.SourcePage,
|
||||
SourceRef: v.SourceRef,
|
||||
VideoID: v.VideoID,
|
||||
ActivityID: v.ActivityID,
|
||||
ExperimentID: v.ExperimentID,
|
||||
ExperimentVariant: v.ExperimentVariant,
|
||||
SessionID: v.SessionID,
|
||||
}
|
||||
}
|
||||
//增加会员卡名称查询
|
||||
if len(ids) > 0 {
|
||||
productMap, err := productmod.ListByIDsMap(ids)
|
||||
if err != nil {
|
||||
log.Error("BaiYuan productmod.FindByProductIDs fail", log.E(err))
|
||||
return records
|
||||
}
|
||||
if productMap != nil {
|
||||
for i, r := range records {
|
||||
if !r.VipID.IsZero() && productMap[r.VipID] != nil {
|
||||
records[i].VipName = productMap[r.VipID].Name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return records
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package rechargeser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"91porn-server/models/v/rchgordmod"
|
||||
)
|
||||
|
||||
func TestBaiYuanCopiesOrderAttribution(t *testing.T) {
|
||||
order := &rchgordmod.RechargeOrder{
|
||||
SourcePage: "VIDEO_BOTTOM_SHEET",
|
||||
SourceRef: "source-ref",
|
||||
VideoID: "video-id",
|
||||
ActivityID: "activity-id",
|
||||
ExperimentID: "experiment-id",
|
||||
ExperimentVariant: "A",
|
||||
SessionID: "session-id",
|
||||
}
|
||||
records := BaiYuan([]*rchgordmod.RechargeOrder{order})
|
||||
if len(records) != 1 {
|
||||
t.Fatalf("len(records) = %d", len(records))
|
||||
}
|
||||
record := records[0]
|
||||
if record.SourcePage != order.SourcePage ||
|
||||
record.SourceRef != order.SourceRef ||
|
||||
record.VideoID != order.VideoID ||
|
||||
record.ActivityID != order.ActivityID ||
|
||||
record.ExperimentID != order.ExperimentID ||
|
||||
record.ExperimentVariant != order.ExperimentVariant ||
|
||||
record.SessionID != order.SessionID {
|
||||
t.Fatalf("attribution not copied: %#v", record)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
package rechargeser
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"91porn-server/common"
|
||||
"91porn-server/common/db"
|
||||
"91porn-server/common/log"
|
||||
"91porn-server/common/stderr"
|
||||
"91porn-server/models/v/prdcthsomod"
|
||||
"91porn-server/models/v/productmod"
|
||||
"91porn-server/models/v/rchgamtmod"
|
||||
"91porn-server/models/v/rchgordmod"
|
||||
"91porn-server/models/v/txnmod"
|
||||
"91porn-server/models/v/usermod"
|
||||
"91porn-server/models/v/walletmod"
|
||||
"91porn-server/web/webg"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
// RechargeCallBack 充值成功回调处理逻辑
|
||||
func RechargeCallBack(ctx context.Context, oid string, payMoney int64, tradeNo string, code int, paymentAt time.Time) error {
|
||||
r, err := rchgordmod.FindRechargeOrderByID(tradeNo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.Status == rchgordmod.SUCCESS {
|
||||
return nil
|
||||
}
|
||||
if r.OID != "" && r.OID != oid {
|
||||
log.Error("WalletSer RechargeCallBack ObjectIDFromHex fail error: 1:")
|
||||
return errors.New("1")
|
||||
}
|
||||
if r.Status != rchgordmod.Processing {
|
||||
log.Error(fmt.Sprintf("WalletSer RechargeCallBack status fail data:%+v:", r.Status))
|
||||
return errors.New("1")
|
||||
}
|
||||
g, err := rchgamtmod.GetGoldByID(r.ProductID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if g == nil {
|
||||
return errors.New("invalid productID")
|
||||
}
|
||||
id, err := primitive.ObjectIDFromHex(tradeNo)
|
||||
if err != nil {
|
||||
log.ErrorX(ctx, "ObjectIDFromHex fail", log.Any("tradeNo", tradeNo), log.E(err))
|
||||
return err
|
||||
}
|
||||
now := time.Now()
|
||||
set := rchgordmod.EditSelector{
|
||||
PayMoney: &payMoney,
|
||||
OID: &oid,
|
||||
PaymentAt: &paymentAt,
|
||||
}
|
||||
if stderr.Code(code) != stderr.Success {
|
||||
set.Status = rchgordmod.FAILURE
|
||||
statusDesc := rchgordmod.Status(rchgordmod.FAILURE).Desc()
|
||||
set.StatusDesc = &statusDesc
|
||||
set.FailureAt = &now
|
||||
return rchgordmod.CallBackModifyWithProcessing(ctx, nil, id, set)
|
||||
}
|
||||
if err = webg.VideoDB.Trans(func(t *db.MongoTool) error {
|
||||
set.SuccessAt = &now
|
||||
set.Status = rchgordmod.SUCCESS
|
||||
statusDesc := rchgordmod.Status(rchgordmod.FAILURE).Desc()
|
||||
set.StatusDesc = &statusDesc
|
||||
if err = rchgordmod.CallBackModifyWithProcessing(ctx, t, id, set); err != nil {
|
||||
return err
|
||||
}
|
||||
//默认以实际支付金额上分
|
||||
amount := payMoney / 10
|
||||
//实际支付小于订单金额1块,返回错误
|
||||
if amount < (g.Coins - 10) {
|
||||
log.WarnX(ctx, "invalid;payMoney invalid payMoney value;", log.Any("gold coins", g.Coins), log.Any("amount", amount))
|
||||
return errors.New("invalid payMoney value;")
|
||||
}
|
||||
//实际支付金额小于订单金额,范围在1块以内,按订单金额上分
|
||||
if amount < g.Coins {
|
||||
amount = g.Coins
|
||||
}
|
||||
wallet, err := walletmod.CreditAmount(t, amount, r.UID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = walletmod.CreditConsumption(t, payMoney, r.UID); err != nil {
|
||||
return err
|
||||
}
|
||||
tt := txnmod.TransactionLog{
|
||||
TransNo: r.ID,
|
||||
UID: r.UID,
|
||||
Amount: r.Amount,
|
||||
TranType: txnmod.Rchg.Key(),
|
||||
TranTypeInt: int64(txnmod.Rchg),
|
||||
ChannelType: r.RechargeType,
|
||||
Desc: "充值新增-" + strconv.FormatInt(amount, 10),
|
||||
DiscDoc: r.DiscDoc,
|
||||
SysType: r.DevType,
|
||||
ActualAmount: float64(r.Amount),
|
||||
RealAmount: walletmod.GetRealAmount(wallet),
|
||||
}
|
||||
//插入一条支出流水
|
||||
return txnmod.InsertTransactionLog(t, &tt)
|
||||
}); err != nil {
|
||||
log.ErrorX(ctx, "WalletSer RechargeCallBack Trans fail", log.E(err))
|
||||
return err
|
||||
}
|
||||
if !r.VipID.IsZero() {
|
||||
common.Go(func() { _ = buyVIP(r.UID, r.VipID, r.DevType) })
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkVipRenew(u *usermod.User, p *productmod.Product) (time.Time, int, int) {
|
||||
now := time.Now()
|
||||
var end time.Time
|
||||
level := p.VipLevel
|
||||
payVidDiscount := p.PayVidDiscount
|
||||
d := time.Hour * 24 * time.Duration(p.Duration)
|
||||
if u.VipExpireDate.After(now) { //renew
|
||||
end = u.VipExpireDate.Add(d)
|
||||
if u.VipLevel > p.VipLevel { //当前用户的vip等级比这次购买的大,使用用户的
|
||||
level = u.VipLevel
|
||||
}
|
||||
if u.PayVidDiscount > 0 && u.PayVidDiscount < p.PayVidDiscount {
|
||||
payVidDiscount = u.PayVidDiscount
|
||||
}
|
||||
} else {
|
||||
end = now.Add(d)
|
||||
}
|
||||
return end, level, payVidDiscount
|
||||
}
|
||||
|
||||
func debitPlan(w *walletmod.Wallet, amt int64) *walletmod.DebitPlan {
|
||||
p := walletmod.DebitPlan{}
|
||||
l1 := w.Amount - amt
|
||||
if l1 >= 0 { //amount够了
|
||||
p.Amount = amt
|
||||
return &p
|
||||
}
|
||||
// l1 < 0
|
||||
p.Amount = w.Amount //amount 扣完
|
||||
l2 := w.Income + l1
|
||||
if l2 >= 0 {
|
||||
p.Income = -l1
|
||||
return &p
|
||||
}
|
||||
return nil //余额不足
|
||||
}
|
||||
|
||||
func getDiscInfo(user *usermod.User) txnmod.DiscDoc {
|
||||
discDoc := txnmod.DiscDoc{}
|
||||
discDoc.IsDirect = user.IsDirect
|
||||
discDoc.DistrictCode = user.DistrictCode
|
||||
discDoc.PromSeqe = user.PromSeqe
|
||||
return discDoc
|
||||
}
|
||||
|
||||
// BuyProduct 购买产品产生的行为
|
||||
func buyVIP(uid uint64, productID primitive.ObjectID, sys string) stderr.Code {
|
||||
p, err := productmod.FindProduct(productID, sys)
|
||||
if err != nil || p == nil {
|
||||
return stderr.ErrParamError
|
||||
}
|
||||
u, err := usermod.FindUserByUID(uid)
|
||||
if err != nil || u == nil {
|
||||
return stderr.ErrDbQueryError
|
||||
}
|
||||
w, err := walletmod.GetWallet(uid)
|
||||
if err != nil || w == nil {
|
||||
return stderr.ErrDbQueryError
|
||||
}
|
||||
plan := debitPlan(w, p.DiscountedPrice)
|
||||
if plan == nil {
|
||||
return stderr.InsufficientBalance
|
||||
}
|
||||
vipExpire, vipLevel, payVidDiscount := checkVipRenew(u, p)
|
||||
sel := usermod.UserSelector{VipExpireDate: &vipExpire, VipLevel: &vipLevel, PayVidDiscount: &payVidDiscount}
|
||||
if p.DramaDays > 0 {
|
||||
expire := usermod.RenewDramaExpire(u.DramaExpire, time.Now(), p.DramaDays)
|
||||
sel.DramaExpire = &expire
|
||||
}
|
||||
if err = webg.VideoDB.Trans(func(t *db.MongoTool) error {
|
||||
var err error
|
||||
var wallet *walletmod.Wallet
|
||||
if wallet, err = walletmod.Debit(t, plan, uid); err != nil { //扣钱
|
||||
return err
|
||||
}
|
||||
history := prdcthsomod.ProductHistory{ //插入商品购买记录
|
||||
UID: uid,
|
||||
ProductID: productID,
|
||||
Name: p.Name,
|
||||
Amount: plan.Amount,
|
||||
Income: plan.Income,
|
||||
ProductType: prdcthsomod.VIP,
|
||||
DiscDoc: u.DiscDoc,
|
||||
SysType: u.SysType,
|
||||
ProductSnapShot: p,
|
||||
}
|
||||
if err = prdcthsomod.InsertProductHistory(t, &history); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = usermod.UpdateVIP(t, uid, u.VipExpireDate, sel); err != nil {
|
||||
return err
|
||||
}
|
||||
txnLog := txnmod.TransactionLog{UID: uid, Amount: -p.DiscountedPrice, TranType: txnmod.PayVIP.Key(), TranTypeInt: int64(txnmod.PayVIP), TransNo: history.ID, Desc: "购买了" + p.Name, DiscDoc: getDiscInfo(u), SysType: u.SysType, ActualAmount: -float64(p.DiscountedPrice), RealAmount: walletmod.GetRealAmount(wallet)}
|
||||
return txnmod.InsertTransactionLog(t, &txnLog)
|
||||
}); err != nil {
|
||||
return stderr.ErrDbTransError
|
||||
}
|
||||
return stderr.Success
|
||||
}
|
||||
Reference in New Issue
Block a user