diff --git a/app/hdl/hdl_wallet.go b/app/hdl/hdl_wallet.go index e3f5a4b..1c53d3e 100644 --- a/app/hdl/hdl_wallet.go +++ b/app/hdl/hdl_wallet.go @@ -4,10 +4,17 @@ import ( "applet/app/db" "applet/app/e" "applet/app/md" + "applet/app/svc" "applet/app/utils" + "applet/app/utils/cache" "code.fnuoos.com/EggPlanet/egg_models.git/src/implement" "code.fnuoos.com/EggPlanet/egg_models.git/src/model" + "code.fnuoos.com/EggPlanet/egg_system_rules.git/enum" + md2 "code.fnuoos.com/EggPlanet/egg_system_rules.git/md" + "code.fnuoos.com/EggPlanet/egg_system_rules.git/rule" + "fmt" "github.com/gin-gonic/gin" + "time" ) // GetAmountFlow @@ -127,3 +134,159 @@ func WithdrawGetAmount(c *gin.Context) { } e.OutSuc(c, resp, nil) } + +// WithdrawApply +// @Summary 蛋蛋星球-钱包-发起提现 +// @Tags 钱包 +// @Description 发起提现 +// @Accept json +// @Produce json +// @param Authorization header string true "验证参数Bearer和token空格拼接" +// @Param req body md.WithdrawApplyReq true "具体参数" +// @Success 200 {string} "success" +// @Failure 400 {object} md.Response "具体错误" +// @Router /api/v1/wallet/withdraw/apply [POST] +func WithdrawApply(c *gin.Context) { + var req md.WithdrawApplyReq + err := c.ShouldBindJSON(&req) + if err != nil { + err = svc.HandleValidateErr(err) + err1 := err.(e.E) + e.OutErr(c, err1.Code, err1.Error()) + return + } + user := svc.GetUser(c) + var userId, openId string + //sysCfgDb := implement.NewSysCfgDb(db.Db, cache.GetPool().Get()) + //sysCfgMap := sysCfgDb.SysCfgFindWithDb(enum.AlipayAppId, enum.WxAppId) + if req.Kind == int(enum.FinWithdrawApplyWithdrawKindForAli) { + alipayUserInfoDb := implement.NewAlipayUserInfoDb(db.Db) + aliInfo, err := alipayUserInfoDb.GetAlipayUserInfo(user.Id) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + if aliInfo == nil { + e.OutErr(c, e.ERR, "支付宝用户信息未授权") + return + } + //appId = sysCfgMap[enum.AlipayAppId] + userId = aliInfo.UserId + openId = aliInfo.OpenId + } else if req.Kind == int(enum.FinWithdrawApplyWithdrawKindForWx) { + wxUserInfoDb := implement.NewWxUserInfoDb(db.Db) + wxInfo, err := wxUserInfoDb.GetWxUserInfo(user.Id) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + if wxInfo == nil { + e.OutErr(c, e.ERR, "微信用户信息未授权") + return + } + //appId = sysCfgMap[enum.WxAppId] + userId = wxInfo.UserId + openId = wxInfo.OpenId + } else { + e.OutErr(c, e.ERR, "未知的提现类型") + return + } + + //1、判断是否可以提现 + err, realAmount, fee, isAuto, isFirst := svc.CheckWithdraw(c, req.Amount) + if err != nil { + e.OutErr(c, e.ERR, err.Error()) + return + } + + // 2、加锁 防止并发提取 + mutexKey := fmt.Sprintf("egg_app_withdraw_apply:%s", utils.Int64ToStr(user.Id)) + withdrawAvailable, err := cache.Do("SET", mutexKey, 1, "EX", 5, "NX") + if err != nil { + e.OutErr(c, e.ERR, err) + return + } + if withdrawAvailable != "OK" { + e.OutErr(c, e.ERR, e.NewErr(400000, "请求过于频繁,请稍后再试")) + return + } + + // 开启事务 + session := db.Db.NewSession() + defer session.Close() + err = session.Begin() + if err != nil { + session.Rollback() + e.OutErr(c, e.ERR_DB_ORM, err) + return + } + + //3、处理用户余额 + dealUserWalletReq := md2.DealUserWalletReq{ + Direction: "sub", + Kind: int(enum.UserWithdrawApply), + Title: enum.UserWithdrawApply.String(), + Uid: user.Id, + Amount: utils.StrToFloat64(req.Amount), + } + err = rule.DealUserWallet(session, dealUserWalletReq) + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + session.Rollback() + return + } + + //4、新增提现记录 + now := time.Now() + finWithdrawApplyDb := implement.NewFinWithdrawApplyDb(db.Db) + insertAffected, err := finWithdrawApplyDb.FinWithdrawApplyInsertOneBySession(session, &model.FinWithdrawApply{ + Uid: user.Id, + AdmId: 0, + Amount: req.Amount, + RealAmount: realAmount, + Fee: fee, + Type: func(isAuto bool) int { + if isAuto { + return 2 + } + return 1 + }(isAuto), + WithdrawAccount: userId, + WithdrawName: openId, + Reason: 0, + PaymentDate: "", + State: 0, + WithdrawKind: req.Kind, + IsFirst: func(isFirst bool) int { + if isFirst { + return 1 + } + return 0 + }(isFirst), + Memo: "", + UpdateAt: now.Format("2006-01-02 15:04:05"), + CreateAt: now.Format("2006-01-02 15:04:05"), + }) + if err != nil { + session.Rollback() + e.OutErr(c, e.ERR_DB_ORM, err) + return + } + if insertAffected <= 0 { + session.Rollback() + e.OutErr(c, e.ERR_DB_ORM, "生成提现单失败") + return + } + err = session.Begin() + if err != nil { + session.Rollback() + e.OutErr(c, e.ERR_DB_ORM, err) + return + } + + //5、推入mq + if isAuto { + + } + e.OutSuc(c, "success", nil) +} diff --git a/app/md/md.wallet.go b/app/md/md.wallet.go index da469b4..4d8c120 100644 --- a/app/md/md.wallet.go +++ b/app/md/md.wallet.go @@ -25,3 +25,8 @@ type GetAmountFlowResp struct { type WithdrawGetAmountResp struct { Amount string `json:"amount"` // 余额 } + +type WithdrawApplyReq struct { + Amount string `json:"amount"` // 金额 + Kind int `json:"kind"` // 提现方式(1:支付宝 2:微信) +} diff --git a/app/router/router.go b/app/router/router.go index b40d378..05bbe66 100644 --- a/app/router/router.go +++ b/app/router/router.go @@ -124,6 +124,7 @@ func route(r *gin.RouterGroup) { rWithdraw := r.Group("/withdraw") { rWithdraw.GET("/index", hdl.WithdrawGetAmount) + rWithdraw.POST("/apply", hdl.WithdrawApply) } } rCollege := r.Group("/college") //学院 diff --git a/app/svc/svc_withdraw_apply.go b/app/svc/svc_withdraw_apply.go new file mode 100644 index 0000000..33eb079 --- /dev/null +++ b/app/svc/svc_withdraw_apply.go @@ -0,0 +1,167 @@ +package svc + +import ( + "applet/app/db" + "applet/app/e" + "applet/app/utils" + "code.fnuoos.com/EggPlanet/egg_models.git/src/implement" + "code.fnuoos.com/EggPlanet/egg_models.git/src/model" + "code.fnuoos.com/EggPlanet/egg_system_rules.git/rule/egg_energy/md" + "errors" + "github.com/gin-gonic/gin" + "github.com/shopspring/decimal" + "math" + "strings" + "time" +) + +func CheckWithdraw(c *gin.Context, amount string) (err error, realAmount, fee string, isAuto, isFirst bool) { + realAmount = amount + user := GetUser(c) + now := time.Now() + amountValue, _ := decimal.NewFromString(amount) + //1、查询 fin_withdraw_setting 提现设置表 + finWithdrawSettingDb := implement.NewFinWithdrawSettingDb(db.Db) + withdrawSetting, err := finWithdrawSettingDb.FinWithdrawSettingGetOne() + if err != nil { + e.OutErr(c, e.ERR_DB_ORM, err.Error()) + return + } + + //2、判断“是否实名” + if withdrawSetting.IsRealName == 1 && user.IsRealName != 1 { + return errors.New("非实名用户不可提现"), realAmount, fee, isAuto, isFirst + } + + //3、判断“提现金额” + if utils.StrToFloat64(withdrawSetting.WithdrawAmountLimit) > 0 { + withdrawAmountLimitValue, _ := decimal.NewFromString(withdrawSetting.WithdrawAmountLimit) + if amountValue.GreaterThan(withdrawAmountLimitValue) { + return errors.New("非可提现金额"), realAmount, fee, isAuto, isFirst + } + } + + //4、判断“提现倍数” + if utils.StrToFloat64(withdrawSetting.WithdrawMultipleLimit) > 0 { + result := utils.StrToFloat64(amount) / utils.StrToFloat64(withdrawSetting.WithdrawMultipleLimit) + // 检查结果是否非常接近一个整数 + roundedResult := math.Round(result) + if !(math.Abs(roundedResult-result) < 1e-9) { + return errors.New("非可提现倍数"), realAmount, fee, isAuto, isFirst + } + } + + //5、验证会员等级 + if withdrawSetting.VipLevelLimit > 0 && withdrawSetting.VipLevelLimit > user.Level { + return errors.New("非可提现会员等级"), realAmount, fee, isAuto, isFirst + } + + //6、 验证小数点 + if withdrawSetting.IsSupportDecimalPoint > 0 && strings.Contains(amount, ".") { + return errors.New("不支持的提现金额"), realAmount, fee, isAuto, isFirst + } + + //7、验证时段 + if withdrawSetting.WithdrawTimeInterval != "" { + withdrawTimeInterval := strings.Split(withdrawSetting.WithdrawTimeInterval, "-") + // 定义要比较的时间格式 + layout := "15:04" + // 解析给定的时间字符串 + start, _ := time.Parse(layout, withdrawTimeInterval[0]) + end, _ := time.Parse(layout, withdrawTimeInterval[1]) + // 设置为今天的日期 + start = time.Date(now.Year(), now.Month(), now.Day(), start.Hour(), start.Minute(), 0, 0, now.Location()) + end = time.Date(now.Year(), now.Month(), now.Day(), end.Hour(), end.Minute(), 0, 0, now.Location()) + + // 如果结束时间小于开始时间,说明跨天了 + if end.Before(start) { + if !(now.After(start) || now.Before(end)) { + return errors.New("非可提现时间段"), realAmount, fee, isAuto, isFirst + } + } else { // 否则,在同一天内比较 + if !(now.After(start) && now.Before(end)) { + return errors.New("非可提现时间段"), realAmount, fee, isAuto, isFirst + } + } + } + + //8、验证“提现频率” + var frequency md.WithdrawFrequencySettingStruct + utils.Unserialize([]byte(withdrawSetting.FrequencySet), &frequency) + if frequency.Duration == 2 { + day := now.Weekday() + if !utils.InArr(utils.IntToStr(int(day)), frequency.Num) { + return errors.New("非可提现日期"), realAmount, fee, isAuto, isFirst + } + } + if frequency.Duration == 3 { + day := now.Day() + if !utils.InArr(utils.IntToStr(day), frequency.Num) { + return errors.New("非可提现日期"), realAmount, fee, isAuto, isFirst + } + } + + if withdrawSetting.WithdrawNumsLimit > 0 { + var withdrawNums int64 + var startOfDay, endOfDay time.Time + if frequency.Duration == 1 { //按天 + startOfDay = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) + endOfDay = startOfDay.Add(24 * time.Hour) + } + if frequency.Duration == 2 { //按周 + startOfDay = utils.GetStartOfWeek(now) + endOfDay = startOfDay.Add(7 * 24 * time.Hour) + } + if frequency.Duration == 3 { //按月 + startOfDay = utils.GetFirstDateOfMonth(now) + endOfDay = utils.GetLastDateOfMonth(now) + } + withdrawNums, err = db.Db.Where("create_at >= ?", startOfDay.Format("2006-01-02 15:04:05")). + And("create_at < ?", endOfDay.Format("2006-01-02 15:04:05")). + And("uid =?", user.Id). + And("state != 3"). //失败不计入 + Count(&model.FinWithdrawApply{}) + if err != nil { + return err, realAmount, fee, isAuto, isFirst + } + if int(withdrawNums) >= withdrawSetting.WithdrawNumsLimit { + return errors.New("当前已无可提现次数"), realAmount, fee, isAuto, isFirst + } + } + + //9、计算手续费 + var feeSet md.WithdrawFeeSetStruct + utils.Unserialize([]byte(withdrawSetting.WithdrawFeeSet), &feeSet) + if feeSet.Value > 0 { + feeSerValue := decimal.NewFromInt(int64(feeSet.Value)) + if feeSet.Kind == 2 { //固定比例 + feeSerValue = amountValue.Mul(feeSerValue.Div(decimal.NewFromInt(100))) + fee = amountValue.Mul(feeSerValue.Div(decimal.NewFromInt(100))).String() + } + + fee = feeSerValue.String() + realAmount = amountValue.Sub(feeSerValue).String() + } + + //10、判断是否为自动提现 + if withdrawSetting.IsAuto > 0 { + autoAmountLimit, _ := decimal.NewFromString(withdrawSetting.IsAutoAmountLimit) + if amountValue.GreaterThanOrEqual(autoAmountLimit) { + isAuto = true + } + } + + //11、判断是否为第一次提现 + has, err := db.Db.Where("uid = ? and ", user.Id). + And("uid =?", user.Id). + Get(model.FinWithdrawApply{}) + if has { + isFirst = true + } + + if utils.StrToFloat64(realAmount) <= 0 { + return errors.New("当前提现金额有误"), realAmount, fee, isAuto, isFirst + } + + return +} diff --git a/app/utils/time.go b/app/utils/time.go index 6860a57..f3395e6 100644 --- a/app/utils/time.go +++ b/app/utils/time.go @@ -209,18 +209,31 @@ func GetDateTimeRangeStr(s string) (string, string) { return stime.Format("2006-01-02 15:04:05"), etime.Format("2006-01-02 15:04:05") } -//获取传入的时间所在月份的第一天,即某月第一天的0点。如传入time.Now(), 返回当前月份的第一天0点时间。 +// 获取传入的时间所在月份的第一天,即某月第一天的0点。如传入time.Now(), 返回当前月份的第一天0点时间。 func GetFirstDateOfMonth(d time.Time) time.Time { d = d.AddDate(0, 0, -d.Day()+1) return GetZeroTime(d) } -//获取传入的时间所在月份的最后一天,即某月最后一天的0点。如传入time.Now(), 返回当前月份的最后一天0点时间。 +// 获取传入的时间所在月份的最后一天,即某月最后一天的0点。如传入time.Now(), 返回当前月份的最后一天0点时间。 func GetLastDateOfMonth(d time.Time) time.Time { return GetFirstDateOfMonth(d).AddDate(0, 1, -1) } -//获取某一天的0点时间 +// GetStartOfWeek 返回给定时间所在周的周一0点时间 +func GetStartOfWeek(t time.Time) time.Time { + // 调整到当天的0点 + t = t.Truncate(24 * time.Hour) + // 计算与周一相差的天数 + offset := int(time.Monday - t.Weekday()) + if offset > 0 { + offset -= 7 + } + // 调整到本周一 + return t.AddDate(0, 0, offset) +} + +// 获取某一天的0点时间 func GetZeroTime(d time.Time) time.Time { return time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, d.Location()) } diff --git a/go.mod b/go.mod index 064f1f1..e877f79 100644 --- a/go.mod +++ b/go.mod @@ -2,9 +2,10 @@ module applet go 1.19 -//replace code.fnuoos.com/EggPlanet/egg_models.git => E:/company/Egg/egg_models +replace code.fnuoos.com/EggPlanet/egg_models.git => E:/company/Egg/egg_models + // -//replace code.fnuoos.com/EggPlanet/egg_system_rules.git => E:/company/Egg/egg_system_rules +replace code.fnuoos.com/EggPlanet/egg_system_rules.git => E:/company/Egg/egg_system_rules require ( github.com/boombuler/barcode v1.0.1 @@ -32,7 +33,7 @@ require ( ) require ( - code.fnuoos.com/EggPlanet/egg_models.git v0.2.1-0.20241128030209-743f36ef9dad + code.fnuoos.com/EggPlanet/egg_models.git v0.2.1-0.20241128102555-fc839292d728 code.fnuoos.com/EggPlanet/egg_system_rules.git v0.0.4-0.20241128063602-ed11b9dad994 code.fnuoos.com/go_rely_warehouse/zyos_go_es.git v1.0.1-0.20241118083738-0f22da9ba0be code.fnuoos.com/go_rely_warehouse/zyos_go_mq.git v0.0.5