@@ -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) | |||
} |
@@ -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:微信) | |||
} |
@@ -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") //学院 | |||
@@ -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") | |||
} | |||
//获取传入的时间所在月份的第一天,即某月第一天的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()) | |||
} |
@@ -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 | |||