@@ -4,10 +4,17 @@ import ( | |||||
"applet/app/db" | "applet/app/db" | ||||
"applet/app/e" | "applet/app/e" | ||||
"applet/app/md" | "applet/app/md" | ||||
"applet/app/svc" | |||||
"applet/app/utils" | "applet/app/utils" | ||||
"applet/app/utils/cache" | |||||
"code.fnuoos.com/EggPlanet/egg_models.git/src/implement" | "code.fnuoos.com/EggPlanet/egg_models.git/src/implement" | ||||
"code.fnuoos.com/EggPlanet/egg_models.git/src/model" | "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" | "github.com/gin-gonic/gin" | ||||
"time" | |||||
) | ) | ||||
// GetAmountFlow | // GetAmountFlow | ||||
@@ -127,3 +134,159 @@ func WithdrawGetAmount(c *gin.Context) { | |||||
} | } | ||||
e.OutSuc(c, resp, nil) | 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) | |||||
} |
@@ -25,3 +25,8 @@ type GetAmountFlowResp struct { | |||||
type WithdrawGetAmountResp struct { | type WithdrawGetAmountResp struct { | ||||
Amount string `json:"amount"` // 余额 | Amount string `json:"amount"` // 余额 | ||||
} | } | ||||
type WithdrawApplyReq struct { | |||||
Amount string `json:"amount"` // 金额 | |||||
Kind int `json:"kind"` // 提现方式(1:支付宝 2:微信) | |||||
} |
@@ -124,6 +124,7 @@ func route(r *gin.RouterGroup) { | |||||
rWithdraw := r.Group("/withdraw") | rWithdraw := r.Group("/withdraw") | ||||
{ | { | ||||
rWithdraw.GET("/index", hdl.WithdrawGetAmount) | rWithdraw.GET("/index", hdl.WithdrawGetAmount) | ||||
rWithdraw.POST("/apply", hdl.WithdrawApply) | |||||
} | } | ||||
} | } | ||||
rCollege := r.Group("/college") //学院 | rCollege := r.Group("/college") //学院 | ||||
@@ -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 | |||||
} |
@@ -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") | 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 { | func GetFirstDateOfMonth(d time.Time) time.Time { | ||||
d = d.AddDate(0, 0, -d.Day()+1) | d = d.AddDate(0, 0, -d.Day()+1) | ||||
return GetZeroTime(d) | return GetZeroTime(d) | ||||
} | } | ||||
//获取传入的时间所在月份的最后一天,即某月最后一天的0点。如传入time.Now(), 返回当前月份的最后一天0点时间。 | |||||
// 获取传入的时间所在月份的最后一天,即某月最后一天的0点。如传入time.Now(), 返回当前月份的最后一天0点时间。 | |||||
func GetLastDateOfMonth(d time.Time) time.Time { | func GetLastDateOfMonth(d time.Time) time.Time { | ||||
return GetFirstDateOfMonth(d).AddDate(0, 1, -1) | 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 { | func GetZeroTime(d time.Time) time.Time { | ||||
return time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, d.Location()) | return time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, d.Location()) | ||||
} | } |
@@ -2,9 +2,10 @@ module applet | |||||
go 1.19 | 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 ( | require ( | ||||
github.com/boombuler/barcode v1.0.1 | github.com/boombuler/barcode v1.0.1 | ||||
@@ -32,7 +33,7 @@ require ( | |||||
) | ) | ||||
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/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_es.git v1.0.1-0.20241118083738-0f22da9ba0be | ||||
code.fnuoos.com/go_rely_warehouse/zyos_go_mq.git v0.0.5 | code.fnuoos.com/go_rely_warehouse/zyos_go_mq.git v0.0.5 | ||||