diff --git a/api/errcode/errcode.go b/api/errcode/errcode.go index 7f3383e8..118a9f79 100644 --- a/api/errcode/errcode.go +++ b/api/errcode/errcode.go @@ -56,6 +56,9 @@ var ErrCodeMsgMap = map[ErrCode]string{ ErrCodeVasAlreadyUnlock: "已解锁,重复购买", ErrCodeVasRepeatDeal: "重复处理", ErrCodeVasInvalidCalcPrice: "价格计算错误", + ErrCodeVasNoEnoughWithdrawDias: "可提现钻石不足", + ErrCodeVasInvalidVerifycode: "验证码错误", + ErrCodeVasAlipayUniTransferFail: "支付宝提现失败", ErrCodeMomentSrvFail: "动态服务错误", ErrCodeMomentNotExist: "动态不存在", @@ -208,6 +211,9 @@ const ( ErrCodeVasRepeatDeal ErrCode = -7016 // 重复处理 ErrCodeVasInvalidCalcPrice ErrCode = -7017 // 计算价格错误 ErrCodeVasInvalidParam ErrCode = -7018 // 参数错误 + ErrCodeVasNoEnoughWithdrawDias ErrCode = -7019 // 参数错误 + ErrCodeVasInvalidVerifycode ErrCode = -7020 // 提现验证码错误 + ErrCodeVasAlipayUniTransferFail ErrCode = -7021 // 支付宝提现失败 // Moment: 8xxx ErrCodeMomentSrvOk ErrCode = ErrCodeOk diff --git a/api/errs/error.go b/api/errs/error.go index 6cbce1fb..43799edd 100644 --- a/api/errs/error.go +++ b/api/errs/error.go @@ -38,6 +38,8 @@ var ErrEcMap = map[error]errcode.ErrCode{ ErrVasRepeatDeal: errcode.ErrCodeVasRepeatDeal, ErrVasInvalidCalcPrice: errcode.ErrCodeVasInvalidCalcPrice, ErrVasInvalidParam: errcode.ErrCodeVasInvalidParam, + ErrVasNoEnoughWithdrawDias: errcode.ErrCodeVasNoEnoughWithdrawDias, + ErrVasAlipayUniTransferFail: errcode.ErrCodeVasAlipayUniTransferFail, } var ( @@ -56,4 +58,6 @@ var ( ErrVasRepeatDeal = errors.New("repeat deal") ErrVasInvalidCalcPrice = errors.New("invalid calc price") ErrVasInvalidParam = errors.New("invalid param") + ErrVasNoEnoughWithdrawDias = errors.New("no enough withdraw dias") + ErrVasAlipayUniTransferFail = errors.New("alipay uni transfer fail") ) diff --git a/api/proto/vas/proto/vas.go b/api/proto/vas/proto/vas.go index c08c2a26..5913b139 100644 --- a/api/proto/vas/proto/vas.go +++ b/api/proto/vas/proto/vas.go @@ -1,6 +1,7 @@ package proto import ( + "github.com/go-pay/gopay/alipay" "service/api/base" "service/dbstruct" ) @@ -109,7 +110,9 @@ type WithdrawApplyReq struct { AuthAlipayId string `json:"auth_alipay_id"` // 加密的支付宝账号 AuthAlipayName string `json:"auth_alipay_name"` // 加密的真实姓名 VerifyCode string `json:"verify_code"` // 短信验证码 + Ip string `json:"ip"` } type WithdrawApplyData struct { + TransferResp *alipay.FundTransUniTransferResponse `json:"transfer_resp"` } diff --git a/app/mix/controller/init.go b/app/mix/controller/init.go index 63942d00..a7069bf5 100644 --- a/app/mix/controller/init.go +++ b/app/mix/controller/init.go @@ -191,6 +191,7 @@ func Init(r *gin.Engine) { vasPayGroup.POST("h5_get_unlock_wechat_list", middleware.JSONParamValidator(vasproto.GetUnlockWechatListReq{}), GetUnlockWechatList) vasPayGroup.POST("withdraw_page", middleware.JSONParamValidator(vasproto.WithdrawPageReq{}), WithdrawPage) vasPayGroup.POST("withdraw_send_verifycode", middleware.JSONParamValidator(vasproto.WithdrawSendVerifycodeReq{}), WithdrawSendVerifycode) + vasPayGroup.POST("withdraw_apply", middleware.JSONParamValidator(vasproto.WithdrawApplyReq{}), WithdrawApply) extVasPayGroup := r.Group("/ext/vas") extVasPayGroup.POST("alipay_callback", AlipayCallback) diff --git a/app/mix/controller/vas.go b/app/mix/controller/vas.go index b30ca5cf..2c4f4787 100644 --- a/app/mix/controller/vas.go +++ b/app/mix/controller/vas.go @@ -233,3 +233,15 @@ func WithdrawSendVerifycode(ctx *gin.Context) { } ReplyOk(ctx, nil) } + +// 提现申请 +func WithdrawApply(ctx *gin.Context) { + req := ctx.MustGet("client_req").(*vasproto.WithdrawApplyReq) + data, ec := service.DefaultService.WithdrawApply(ctx, req) + if ec != errcode.ErrCodeVasSrvOk { + logger.Error("WithdrawApply fail, req: %v, data: %v, ec: %v", util.ToJson(req), data, ec) + ReplyErrCodeMsg(ctx, ec) + return + } + ReplyOk(ctx, nil) +} diff --git a/app/mix/controller/vas_test.go b/app/mix/controller/vas_test.go index 48211390..b369bf98 100644 --- a/app/mix/controller/vas_test.go +++ b/app/mix/controller/vas_test.go @@ -78,6 +78,7 @@ func RsaDecrypt(ciphertext []byte) ([]byte, error) { func TestGen(t *testing.T) { data, _ := RsaEncrypt([]byte("lwl0608@foxmail.com")) + fmt.Println(string(data)) fmt.Println(base64.StdEncoding.EncodeToString(data)) origData, _ := RsaDecrypt(data) fmt.Println(string(origData)) diff --git a/app/mix/dao/mysql.go b/app/mix/dao/mysql.go index 2d3df773..ee7d58b7 100644 --- a/app/mix/dao/mysql.go +++ b/app/mix/dao/mysql.go @@ -62,14 +62,15 @@ 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" // 用增解锁 + 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" // 提现订单表 ) func (m *Mysql) ChTableName(ch *dbstruct.ConsumeHistory) (string, error) { @@ -325,6 +326,23 @@ func (m *Mysql) IncDiamonds(ctx *gin.Context, tx *sqlx.Tx, mid, dias int64) erro return err } +// 扣提现钻石 +func (m *Mysql) DecWithdrawDiamonds(ctx *gin.Context, tx *sqlx.Tx, mid, dias int64) error { + var err error + sqlStr := "update " + TableWallet + " set withdraw_diamonds=withdraw_diamonds-? where id=?" + if tx != nil { + _, err = tx.ExecContext(ctx, sqlStr, dias, mid) + } else { + db := m.getDBVas() + _, err = db.ExecContext(ctx, sqlStr, dias, mid) + } + if err != nil { + logger.Error("DecWithdrawDiamonds fail, mid: %v, dias: %v, err: %v", mid, dias, err) + return err + } + return err +} + // 创建金币订单 func (m *Mysql) CreateCoinOrder(ctx *gin.Context, tx *sqlx.Tx, order *dbstruct.CoinOrder) error { var err error @@ -597,3 +615,46 @@ func (m *Mysql) GetOrderCountGroupByStatus(ctx *gin.Context, tx *sqlx.Tx, orderS } return } + +// 创建提现订单 +func (m *Mysql) CreateWithdrawOrder(ctx *gin.Context, tx *sqlx.Tx, wOrder *dbstruct.WithdrawOrder) error { + var err error + sqlStr := "insert into " + TableOrder + + " (id, mid, did, apply_time, alipay_id, alipay_name, " + + " withdraw_dias, withdraw_money, ip, order_status, operator, op_time) " + + " values (?,?,?,?,?,?,?,?,?,?,?,?) " + if tx != nil { + _, err = tx.ExecContext(ctx, sqlStr, + wOrder.GetID(), wOrder.GetMid(), wOrder.GetDid(), wOrder.GetAlipayId(), wOrder.GetAlipayName(), + wOrder.GetWithdrawDias(), wOrder.GetWithdrawMoney(), wOrder.GetIp(), wOrder.GetOrderStatus(), wOrder.GetOperator(), wOrder.GetOpTime(), + ) + } else { + db := m.getDBVas() + _, err = db.ExecContext(ctx, sqlStr, + wOrder.GetID(), wOrder.GetMid(), wOrder.GetDid(), wOrder.GetAlipayId(), wOrder.GetAlipayName(), + wOrder.GetWithdrawDias(), wOrder.GetWithdrawMoney(), wOrder.GetIp(), wOrder.GetOrderStatus(), wOrder.GetOperator(), wOrder.GetOpTime(), + ) + } + if err != nil { + logger.Error("CreateOrder fail, wOrder: %v, err: %v", wOrder.ToString(), err) + return err + } + return err +} + +// 更新提现订单状态 +func (m *Mysql) UpdateWithdrawOrderStatus(ctx *gin.Context, tx *sqlx.Tx, orderId string, preStatus, aftStatus int32) error { + var err error + sqlStr := "update " + TableWithdrawOrder + " set order_status=? where id=? and order_status=?" + if tx != nil { + _, err = tx.ExecContext(ctx, sqlStr, aftStatus, orderId, preStatus) + } else { + db := m.getDBVas() + _, err = db.ExecContext(ctx, sqlStr, aftStatus, orderId, preStatus) + } + if err != nil { + logger.Error("UpdateWithdrawOrderStatus fail, orderId: %v, preStatus: %v, aftStatus: %v, err: %v", orderId, preStatus, aftStatus, err) + return err + } + return err +} diff --git a/app/mix/dao/redis.go b/app/mix/dao/redis.go new file mode 100644 index 00000000..07a0cc0f --- /dev/null +++ b/app/mix/dao/redis.go @@ -0,0 +1 @@ +package dao diff --git a/app/mix/service/logic/vas.go b/app/mix/service/logic/vas.go index 7e119882..8c22b0fe 100644 --- a/app/mix/service/logic/vas.go +++ b/app/mix/service/logic/vas.go @@ -1,9 +1,15 @@ package logic import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" "database/sql" + "encoding/base64" + "encoding/pem" "errors" "fmt" + "github.com/go-pay/gopay/alipay" "service/api/base" "service/api/errs" vasproto "service/api/proto/vas/proto" @@ -1676,3 +1682,232 @@ func (v *Vas) GetCoinOrderById(ctx *gin.Context, id string) (*dbstruct.CoinOrder 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 + ) + + // 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 +} diff --git a/app/mix/service/vasservice.go b/app/mix/service/vasservice.go index 2984c32c..959137a4 100644 --- a/app/mix/service/vasservice.go +++ b/app/mix/service/vasservice.go @@ -317,9 +317,12 @@ func (s *Service) chListWithdraw(ctx *gin.Context, chList []*dbstruct.ConsumeHis changeMark = "+" } switch chDB.GetSType() { - case dbstruct.CHSTypeWithdrawDiamond: - item.Desc = "提现" + case dbstruct.CHSTypeWithdrawDiamondAuto: + item.Desc = "自动提现" item.Change = changeMark + fmt.Sprintf("%d钻石", chDB.GetChange()) + if chDB.GetChange() < 0 { + item.Change = fmt.Sprintf("%d钻石", chDB.GetChange()) + } } list = append(list, item) } @@ -396,9 +399,50 @@ func (s *Service) WithdrawSendVerifycode(ctx *gin.Context, req *vasproto.Withdra RegionCode: util.DerefString(acnt.RegionCode), Trigger: vericodeproto.VerifyCodeTriggerWithdraw, }) + ec, err = errs.DealVasErr(err) if err != nil { logger.Error("OpSendVeriCode fail, req: %v, err: %v", util.ToJson(req), err) return } return } + +// 提现申请 +func (s *Service) WithdrawApply(ctx *gin.Context, req *vasproto.WithdrawApplyReq) (data *vasproto.WithdrawApplyData, ec errcode.ErrCode) { + // 获取用户 + acntMap, _ := _DefaultAccount.GetAccountMapByMids(ctx, []int64{req.Mid}) + acnt, has := acntMap[req.Mid] + if !has { + ec = errcode.ErrCodeVasSrvFail + return + } + + // 校验验证码 + vCode, _ := _DefaultVeriCode.OpListByPhoneHash(ctx, &vericodeproto.OpListByPhoneHashReq{ + BaseRequest: req.BaseRequest, + PhoneHash: util.DerefString(acnt.PhoneHash), + RegionCode: util.DerefString(acnt.RegionCode), + }) + if vCode == nil || vCode.VeriCode != req.VerifyCode { + ec = errcode.ErrCodeVasInvalidVerifycode + return + } + + // 检验完删除 + err := _DefaultVeriCode.OpDelete(ctx, vCode.Id) + if err != nil { + logger.Error("_DefaultVeriCode.OpDelete fail, req: %v, code: %v, err: %v", util.ToJson(req), util.ToJson(vCode), err) + ec = errcode.ErrCodeVasInvalidVerifycode + return + } + + // 提现 + data = new(vasproto.WithdrawApplyData) + data.TransferResp, err = _DefaultVas.WithdrawApply(ctx, req) + ec, err = errs.DealVasErr(err) + if err != nil { + logger.Error("WithdrawApply fail, req: %v, resp: %v, err: %v", util.ToJson(req), util.ToJson(data.TransferResp), err) + return + } + return +} diff --git a/dbstruct/vas_mysql.go b/dbstruct/vas_mysql.go index 19960de3..4760d466 100644 --- a/dbstruct/vas_mysql.go +++ b/dbstruct/vas_mysql.go @@ -500,7 +500,7 @@ const ( CHSTypeIncomeContact = 30001 // 收入明细,联系方式 CHSTypeIncomeInvite = 30002 // 收入明细,邀请分成 - CHSTypeWithdrawDiamond = 40001 // 提现明细 + CHSTypeWithdrawDiamondAuto = 40001 // 自动提现明细 ) type ConsumeHistory struct { @@ -658,3 +658,126 @@ type VasOrderStatusCount struct { OrderStatus *int32 `json:"order_status" db:"order_status"` Count *int32 `json:"count" db:"count"` } + +// 钱包 +const ( + VasWithdrawOrderStatusFail = -2 // 失败 + VasWithdrawOrderStatusNone = -1 // 零状态 + VasWithdrawOrderStatusInit = 0 // 初始化 + VasWithdrawOrderStatusWaitDeal = 1 // 等待运营处理 + VasWithdrawOrderStatusAuto = 2 // 小额自动提现 + VasWithdrawOrderStatusDeal = 3 // 已处理 +) + +var WithdrawOrderStatusDescMap = map[int32]string{ + VasWithdrawOrderStatusFail: "提现失败", + VasWithdrawOrderStatusNone: "零状态", + VasWithdrawOrderStatusInit: "初始化", + VasWithdrawOrderStatusWaitDeal: "等待运营处理", + VasWithdrawOrderStatusAuto: "小额自动提醒", + VasWithdrawOrderStatusDeal: "已处理", +} + +type WithdrawOrder struct { + ID *string `json:"id" db:"id"` + Mid *int64 `json:"mid" db:"mid"` // mid + Did *string `json:"did" db:"did"` // 设备id + ApplyTime *int64 `json:"apply_time" db:"apply_time"` // 申请时间 + AlipayId *string `json:"alipay_id" db:"alipay_id"` // 支付宝账号 + AlipayName *string `json:"alipay_name" db:"alipay_name"` // 支付宝姓名 + WithdrawDias *int64 `json:"withdraw_dias" db:"withdraw_dias"` // 提现钻石数 + WithdrawMoney *int64 `json:"withdraw_money" db:"withdraw_money"` // 提现金额 + Ip *string `json:"ip" db:"ip"` // ip + OrderStatus *int32 `json:"order_status" db:"order_status"` // 订单状态 + Operator *string `json:"operator" db:"operator"` // 操作的运营同学 + OpTime *int64 `json:"op_time" db:"op_time"` // op操作时间 +} + +func (p *WithdrawOrder) ToString() string { + bs, _ := json.Marshal(p) + return string(bs) +} + +func (p *WithdrawOrder) GetID() string { + if p != nil && p.ID != nil { + return *p.ID + } + return "" +} + +func (p *WithdrawOrder) GetMid() int64 { + if p != nil && p.Mid != nil { + return *p.Mid + } + return 0 +} + +func (p *WithdrawOrder) GetDid() string { + if p != nil && p.Did != nil { + return *p.Did + } + return "" +} + +func (p *WithdrawOrder) GetApplyTime() int64 { + if p != nil && p.ApplyTime != nil { + return *p.ApplyTime + } + return 0 +} + +func (p *WithdrawOrder) GetAlipayId() string { + if p != nil && p.AlipayId != nil { + return *p.AlipayId + } + return "" +} + +func (p *WithdrawOrder) GetAlipayName() string { + if p != nil && p.AlipayName != nil { + return *p.AlipayName + } + return "" +} + +func (p *WithdrawOrder) GetWithdrawDias() int64 { + if p != nil && p.WithdrawDias != nil { + return *p.WithdrawDias + } + return 0 +} + +func (p *WithdrawOrder) GetWithdrawMoney() int64 { + if p != nil && p.WithdrawMoney != nil { + return *p.WithdrawMoney + } + return 0 +} + +func (p *WithdrawOrder) GetIp() string { + if p != nil && p.Ip != nil { + return *p.Ip + } + return "" +} + +func (p *WithdrawOrder) GetOrderStatus() int32 { + if p != nil && p.OrderStatus != nil { + return *p.OrderStatus + } + return VasWithdrawOrderStatusNone +} + +func (p *WithdrawOrder) GetOperator() string { + if p != nil && p.Operator != nil { + return *p.Operator + } + return "" +} + +func (p *WithdrawOrder) GetOpTime() int64 { + if p != nil && p.OpTime != nil { + return *p.OpTime + } + return 0 +} diff --git a/library/idgenerator/genid.go b/library/idgenerator/genid.go index 909487c2..5603756c 100644 --- a/library/idgenerator/genid.go +++ b/library/idgenerator/genid.go @@ -45,6 +45,7 @@ const ( NodeTextAudit // node 文字审核 NodeTextAuditTask // node 文字审核任务 NodeDailyStatement // node 每日报表表 + NodeWithdrawOrderId // node 提现订单 ) func GenIdInt64(node int64) (int64, error) { @@ -196,3 +197,9 @@ func GenDailyStatementId() int64 { id, _ := GenIdInt64(NodeDailyStatement) return id } + +// withdraw_order +func GenWithdrawOrderId() string { + id, _ := GenIdString(NodeWithdrawOrderId) + return id +} diff --git a/library/payclients/alipaycli/client.go b/library/payclients/alipaycli/client.go index 641fd7b9..5f66ceed 100644 --- a/library/payclients/alipaycli/client.go +++ b/library/payclients/alipaycli/client.go @@ -120,3 +120,33 @@ func (c *AlipayClient) WapPay(ctx context.Context, param *WapPayParam) (alipayWa logger.Info("alipay TradeWapPay param: %v", alipayWapParamStr) return } + +// 支付宝单笔转账 +type UniTransferParam struct { + OutBizNo string // 商家订单id,我们自己的订单id + Amount int64 // 金额,单位:分 + Title string // 转账业务的标题,用于在支付宝用户的账单里显示。 + AlipayLoginId string // 支付宝登录账号 + AlipayName string // 支付宝真实姓名 +} + +func (c *AlipayClient) UniTransfer(ctx context.Context, param *UniTransferParam) (resp *alipay.FundTransUniTransferResponse, err error) { + bm := gopay.BodyMap{ + "out_biz_no": param.OutBizNo, + "trans_amount": fmt.Sprintf("%.2f", float64(param.Amount)/100.0), + "biz_scene": "DIRECT_TRANSFER", + "product_code": "TRANS_ACCOUNT_NO_PWD", + "order_title": param.Title, + "payee_info": map[string]interface{}{ + "identity": param.AlipayLoginId, + "identity_type": "ALIPAY_LOGON_ID", + "name": param.AlipayName, + }, + } + resp, err = c.FundTransUniTransfer(ctx, bm) + if err != nil { + return + } + logger.Info("alipay UniTransfer param: %v, resp: %v", bm.JsonBody(), util.ToJson(resp)) + return +}