t
This commit is contained in:
113
app/main/api/internal/logic/agent/agentrealnamelogic.go
Normal file
113
app/main/api/internal/logic/agent/agentrealnamelogic.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"bd-server/app/main/api/internal/service"
|
||||
"bd-server/app/main/api/internal/svc"
|
||||
"bd-server/app/main/api/internal/types"
|
||||
"bd-server/app/main/model"
|
||||
"bd-server/common/ctxdata"
|
||||
"bd-server/common/xerr"
|
||||
"bd-server/pkg/lzkit/crypto"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||
)
|
||||
|
||||
type AgentRealNameLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewAgentRealNameLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AgentRealNameLogic {
|
||||
return &AgentRealNameLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *AgentRealNameLogic) AgentRealName(req *types.AgentRealNameReq) (resp *types.AgentRealNameResp, err error) {
|
||||
userID, err := ctxdata.GetUidFromCtx(l.ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户ID失败, %v", err)
|
||||
}
|
||||
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
||||
encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "代理实名, 加密手机号失败: %v", err)
|
||||
}
|
||||
// 查询用户,判断是否为内部用户
|
||||
currentUser, findUserErr := l.svcCtx.UserModel.FindOne(l.ctx, userID)
|
||||
if findUserErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "代理实名, 获取用户信息失败: %v", findUserErr)
|
||||
}
|
||||
|
||||
// 内部用户跳过验证码验证
|
||||
needVerifyCode := os.Getenv("ENV") != "development"
|
||||
if needVerifyCode && currentUser.Inside == 1 {
|
||||
needVerifyCode = false
|
||||
}
|
||||
if needVerifyCode {
|
||||
// 检查手机号是否在一分钟内已发送过验证码
|
||||
redisKey := fmt.Sprintf("%s:%s", "realName", encryptedMobile)
|
||||
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
|
||||
if err != nil {
|
||||
if errors.Is(err, redis.Nil) {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "代理实名, 验证码过期: %s", encryptedMobile)
|
||||
}
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理实名, 读取验证码redis缓存失败, mobile: %s, err: %+v", encryptedMobile, err)
|
||||
}
|
||||
if cacheCode != req.Code {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "代理实名, 验证码不正确: %s", encryptedMobile)
|
||||
}
|
||||
}
|
||||
agent, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理信息失败, %v", err)
|
||||
}
|
||||
|
||||
agentRealName, err := l.svcCtx.AgentRealNameModel.FindOneByAgentId(l.ctx, agent.Id)
|
||||
if err != nil && !errors.Is(err, model.ErrNotFound) {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理实名信息失败, %v", err)
|
||||
}
|
||||
|
||||
if agentRealName != nil && agentRealName.Status == model.AgentRealNameStatusApproved {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("代理实名信息已审核通过"), "代理实名信息已审核通过")
|
||||
}
|
||||
// 三要素验证
|
||||
threeVerification := service.ThreeFactorVerificationRequest{
|
||||
Name: req.Name,
|
||||
IDCard: req.IDCard,
|
||||
Mobile: req.Mobile,
|
||||
}
|
||||
verification, err := l.svcCtx.VerificationService.ThreeFactorVerification(threeVerification)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "三要素验证失败: %v", err)
|
||||
}
|
||||
if !verification.Passed {
|
||||
return nil, errors.Wrapf(xerr.NewErrCodeMsg(xerr.SERVER_COMMON_ERROR, verification.Err.Error()), "三要素验证不通过: %v", err)
|
||||
}
|
||||
agentRealName = &model.AgentRealName{
|
||||
AgentId: agent.Id,
|
||||
Status: model.AgentRealNameStatusApproved,
|
||||
Name: req.Name,
|
||||
IdCard: req.IDCard,
|
||||
ApproveTime: sql.NullTime{Time: time.Now(), Valid: true},
|
||||
}
|
||||
_, err = l.svcCtx.AgentRealNameModel.Insert(l.ctx, nil, agentRealName)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "添加代理实名信息失败, %v", err)
|
||||
}
|
||||
|
||||
return &types.AgentRealNameResp{
|
||||
Status: agentRealName.Status,
|
||||
}, nil
|
||||
}
|
||||
379
app/main/api/internal/logic/agent/agentwithdrawallogic.go
Normal file
379
app/main/api/internal/logic/agent/agentwithdrawallogic.go
Normal file
@@ -0,0 +1,379 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"bd-server/app/main/model"
|
||||
"bd-server/common/ctxdata"
|
||||
"bd-server/common/xerr"
|
||||
"bd-server/pkg/lzkit/lzUtils"
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/smartwalle/alipay/v3"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
|
||||
"bd-server/app/main/api/internal/svc"
|
||||
"bd-server/app/main/api/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
// 状态常量
|
||||
const (
|
||||
StatusProcessing = 1 // 处理中
|
||||
StatusSuccess = 2 // 成功
|
||||
StatusFailed = 3 // 失败
|
||||
)
|
||||
|
||||
// 前端响应状态
|
||||
const (
|
||||
WithdrawStatusProcessing = 1
|
||||
WithdrawStatusSuccess = 2
|
||||
WithdrawStatusFailed = 3
|
||||
)
|
||||
|
||||
type AgentWithdrawalLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewAgentWithdrawalLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AgentWithdrawalLogic {
|
||||
return &AgentWithdrawalLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *AgentWithdrawalLogic) AgentWithdrawal(req *types.WithdrawalReq) (*types.WithdrawalResp, error) {
|
||||
var (
|
||||
outBizNo string
|
||||
withdrawRes = &types.WithdrawalResp{}
|
||||
agentID int64
|
||||
)
|
||||
var finalWithdrawAmount float64 // 实际到账金额
|
||||
withdrawAmount := roundMoney(req.Amount)
|
||||
|
||||
// 使用事务处理核心操作
|
||||
err := l.svcCtx.AgentModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
|
||||
userID, err := ctxdata.GetUidFromCtx(l.ctx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户ID失败: %v", err)
|
||||
}
|
||||
|
||||
// 查询代理信息
|
||||
agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
|
||||
if err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败: %v", err)
|
||||
}
|
||||
agentID = agentModel.Id // 保存agentId用于日志
|
||||
agentRealName, err := l.svcCtx.AgentRealNameModel.FindOneByAgentId(l.ctx, agentModel.Id)
|
||||
if err != nil {
|
||||
if errors.Is(err, model.ErrNotFound) {
|
||||
return errors.Wrapf(xerr.NewErrMsg("您未进行实名认证, 无法提现"), "您未进行实名认证")
|
||||
}
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询代理实名信息失败: %v", err)
|
||||
}
|
||||
if agentRealName.Status != model.AgentRealNameStatusApproved {
|
||||
return errors.Wrapf(xerr.NewErrMsg("您的实名认证未通过, 无法提现"), "您的实名认证未通过")
|
||||
}
|
||||
if agentRealName.Name != req.PayeeName {
|
||||
return errors.Wrapf(xerr.NewErrMsg("您的实名认证信息不匹配, 无法提现"), "您的实名认证信息不匹配")
|
||||
}
|
||||
// 查询钱包
|
||||
agentWallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(l.ctx, agentModel.Id)
|
||||
if err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理钱包失败: %v", err)
|
||||
}
|
||||
|
||||
// 校验可提现金额
|
||||
if withdrawAmount > roundMoney(agentWallet.Balance) {
|
||||
return errors.Wrapf(xerr.NewErrMsg("您可提现的余额不足"), "获取用户ID失败")
|
||||
}
|
||||
|
||||
// 生成交易号
|
||||
outBizNo = "W_" + l.svcCtx.AlipayService.GenerateOutTradeNo()
|
||||
|
||||
// 冻结资金(事务内操作)
|
||||
if err = l.freezeFunds(session, agentWallet, withdrawAmount); err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "资金冻结失败: %v", err)
|
||||
}
|
||||
yearMonth := int64(time.Now().Year()*100 + int(time.Now().Month()))
|
||||
// 统一按6%收取税收,不再有免税额度
|
||||
taxRate := l.svcCtx.Config.TaxConfig.TaxRate
|
||||
var (
|
||||
taxAmount float64 // 应缴税费
|
||||
taxDeductionPart float64 // 应税金额
|
||||
TaxStatus int64 // 扣税状态
|
||||
exemptionAmount float64 // 免税金额(固定为0)
|
||||
)
|
||||
|
||||
// 统一扣税逻辑:所有提现都按6%收取税收
|
||||
exemptionAmount = 0 // 免税金额 = 0
|
||||
TaxStatus = model.TaxStatusPending // 扣税状态 = 待扣税
|
||||
taxDeductionPart = withdrawAmount // 应税金额 = 提现金额
|
||||
taxAmount = roundMoney(taxDeductionPart * taxRate) // 应缴税费 = 应税金额 * 税率
|
||||
finalWithdrawAmount = roundMoney(withdrawAmount - taxAmount) // 实际到账金额 = 提现金额 - 应缴税费
|
||||
|
||||
// 创建提现记录(初始状态为处理中)
|
||||
withdrawalID, err := l.createWithdrawalRecord(session, agentModel.Id, req.PayeeAccount, req.PayeeName, withdrawAmount, finalWithdrawAmount, taxAmount, outBizNo)
|
||||
if err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建提现记录失败: %v", err)
|
||||
}
|
||||
// 扣税记录
|
||||
taxModel := &model.AgentWithdrawalTax{
|
||||
AgentId: agentModel.Id,
|
||||
YearMonth: yearMonth,
|
||||
WithdrawalId: withdrawalID,
|
||||
WithdrawalAmount: withdrawAmount,
|
||||
ExemptionAmount: exemptionAmount,
|
||||
TaxableAmount: taxDeductionPart,
|
||||
TaxRate: taxRate,
|
||||
TaxAmount: taxAmount,
|
||||
ActualAmount: finalWithdrawAmount,
|
||||
TaxStatus: TaxStatus,
|
||||
Remark: sql.NullString{String: "提现成功自动扣税", Valid: true},
|
||||
ExemptionRecordId: 0, // 不再使用免税额度记录
|
||||
}
|
||||
_, err = l.svcCtx.AgentWithdrawalTaxModel.Insert(ctx, session, taxModel)
|
||||
if err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建扣税记录失败: %v", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 支付宝提现不再直接调用转账接口,改为先申请后审核
|
||||
// 直接返回申请中状态,等待管理员审核
|
||||
withdrawRes.Status = WithdrawStatusProcessing
|
||||
withdrawRes.FailMsg = ""
|
||||
|
||||
l.Logger.Infof("支付宝提现申请成功 outBizNo:%s agentId:%d amount:%f", outBizNo, agentID, withdrawAmount)
|
||||
return withdrawRes, nil
|
||||
}
|
||||
|
||||
// 错误类型映射
|
||||
func (l *AgentWithdrawalLogic) mapAlipayError(code string) string {
|
||||
errorMapping := map[string]string{
|
||||
// 账户存在性错误
|
||||
"PAYEE_ACCOUNT_NOT_EXSIT": "收款账户不存在,请检查账号是否正确",
|
||||
"PAYEE_NOT_EXIST": "收款账户不存在或姓名有误,请核实信息",
|
||||
"PAYEE_ACC_OCUPIED": "收款账号存在多个账户,无法确认唯一性",
|
||||
"PAYEE_MID_CANNOT_SAME": "收款方和中间方不能是同一个人,请修改收款方或者中间方信息",
|
||||
|
||||
// 实名认证问题
|
||||
"PAYEE_CERTIFY_LEVEL_LIMIT": "收款方未完成实名认证",
|
||||
"PAYEE_NOT_RELNAME_CERTIFY": "收款方未完成实名认证",
|
||||
"PAYEE_CERT_INFO_ERROR": "收款方证件信息不匹配",
|
||||
|
||||
// 账户状态异常
|
||||
"PAYEE_ACCOUNT_STATUS_ERROR": "收款账户状态异常,请更换账号",
|
||||
"PAYEE_USERINFO_STATUS_ERROR": "收款账户状态异常,无法收款",
|
||||
"PERMIT_LIMIT_PAYEE": "收款账户异常,请更换账号",
|
||||
"BLOCK_USER_FORBBIDEN_RECIEVE": "账户冻结无法收款",
|
||||
"PAYEE_TRUSTEESHIP_ACC_OVER_LIMIT": "收款方托管子户累计收款金额超限",
|
||||
|
||||
// 账户信息错误
|
||||
"PAYEE_USERINFO_ERROR": "收款方姓名或信息不匹配",
|
||||
"PAYEE_CARD_INFO_ERROR": "收款支付宝账号及户名不一致",
|
||||
"PAYEE_IDENTITY_NOT_MATCH": "收款方身份信息不匹配",
|
||||
"PAYEE_USER_IS_INST": "收款方为金融机构,不能使用提现功能,请更换收款账号",
|
||||
"PAYEE_USER_TYPE_ERROR": "该支付宝账号类型不支持提现,请更换收款账号",
|
||||
|
||||
// 权限与限制
|
||||
"PAYEE_RECEIVE_COUNT_EXCEED_LIMIT": "收款次数超限,请明日再试",
|
||||
"PAYEE_OUT_PERMLIMIT_CHECK_FAILURE": "收款方权限校验不通过",
|
||||
"PERMIT_NON_BANK_LIMIT_PAYEE": "收款方未完善身份信息,无法收款",
|
||||
}
|
||||
if msg, ok := errorMapping[code]; ok {
|
||||
return msg
|
||||
}
|
||||
l.Logger.Infof("未匹配到支付宝错误码 code:%s", code)
|
||||
return "系统错误,请联系客服"
|
||||
}
|
||||
|
||||
// 创建提现记录(事务内操作)
|
||||
func (l *AgentWithdrawalLogic) createWithdrawalRecord(session sqlx.Session, agentID int64, payeeAccount string, payeeName string, amount float64, finalWithdrawAmount float64, taxAmount float64, outBizNo string) (int64, error) {
|
||||
record := &model.AgentWithdrawal{
|
||||
AgentId: agentID,
|
||||
WithdrawType: 1, // 支付宝提现
|
||||
WithdrawNo: outBizNo,
|
||||
PayeeAccount: payeeAccount,
|
||||
PayeeName: sql.NullString{String: payeeName, Valid: true}, // 设置收款人姓名
|
||||
Amount: roundMoney(amount),
|
||||
ActualAmount: roundMoney(finalWithdrawAmount),
|
||||
TaxAmount: roundMoney(taxAmount),
|
||||
Status: StatusProcessing,
|
||||
}
|
||||
|
||||
result, err := l.svcCtx.AgentWithdrawalModel.Insert(l.ctx, session, record)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.LastInsertId()
|
||||
}
|
||||
|
||||
// 冻结资金(事务内操作)
|
||||
func (l *AgentWithdrawalLogic) freezeFunds(session sqlx.Session, wallet *model.AgentWallet, amount float64) error {
|
||||
wallet.Balance = roundMoney(wallet.Balance - amount)
|
||||
wallet.FrozenBalance = roundMoney(wallet.FrozenBalance + amount)
|
||||
err := l.svcCtx.AgentWalletModel.UpdateWithVersion(l.ctx, session, wallet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 处理异步轮询
|
||||
func (l *AgentWithdrawalLogic) startAsyncPolling(outBizNo string) {
|
||||
go func() {
|
||||
detachedCtx := context.WithoutCancel(l.ctx)
|
||||
retryConfig := &backoff.ExponentialBackOff{
|
||||
InitialInterval: 10 * time.Second,
|
||||
RandomizationFactor: 0.5, // 增加随机因子防止惊群
|
||||
Multiplier: 2,
|
||||
MaxInterval: 30 * time.Second,
|
||||
MaxElapsedTime: 5 * time.Minute, // 缩短总超时
|
||||
Clock: backoff.SystemClock,
|
||||
}
|
||||
retryConfig.Reset()
|
||||
operation := func() error {
|
||||
statusRsp, err := l.svcCtx.AlipayService.QueryTransferStatus(detachedCtx, outBizNo)
|
||||
if err != nil {
|
||||
return err // 触发重试
|
||||
}
|
||||
|
||||
switch statusRsp.Status {
|
||||
case "SUCCESS":
|
||||
l.handleTransferSuccess(outBizNo, statusRsp)
|
||||
return nil
|
||||
case "FAIL":
|
||||
l.handleTransferFailure(outBizNo, statusRsp)
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("转账处理中")
|
||||
}
|
||||
}
|
||||
|
||||
err := backoff.RetryNotify(operation,
|
||||
backoff.WithContext(retryConfig, detachedCtx),
|
||||
func(err error, duration time.Duration) {
|
||||
l.Logger.Infof("轮询延迟 outBizNo:%s 等待:%v", outBizNo, duration)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
l.handleTransferTimeout(outBizNo)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// 统一状态更新
|
||||
func (l *AgentWithdrawalLogic) updateWithdrawalStatus(outBizNo string, status int64, errorMsg string) {
|
||||
detachedCtx := context.WithoutCancel(l.ctx)
|
||||
|
||||
err := l.svcCtx.AgentModel.Trans(detachedCtx, func(ctx context.Context, session sqlx.Session) error {
|
||||
// 获取提现记录
|
||||
record, err := l.svcCtx.AgentWithdrawalModel.FindOneByWithdrawNo(l.ctx, outBizNo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新状态
|
||||
record.Status = status
|
||||
record.Remark = lzUtils.StringToNullString(errorMsg)
|
||||
if _, err = l.svcCtx.AgentWithdrawalModel.Update(ctx, session, record); err != nil {
|
||||
return err
|
||||
}
|
||||
// 失败时解冻资金
|
||||
if status == StatusFailed {
|
||||
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(ctx, record.AgentId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wallet.Balance = roundMoney(wallet.Balance + record.Amount)
|
||||
wallet.FrozenBalance = roundMoney(wallet.FrozenBalance - record.Amount)
|
||||
if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(ctx, session, wallet); err != nil {
|
||||
return err
|
||||
}
|
||||
taxModel, err := l.svcCtx.AgentWithdrawalTaxModel.FindOneByWithdrawalId(ctx, record.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if taxModel.TaxStatus == model.TaxStatusPending {
|
||||
taxModel.TaxStatus = model.TaxStatusFailed // 扣税状态 = 失败
|
||||
taxModel.TaxTime = sql.NullTime{Time: time.Now(), Valid: true}
|
||||
if err := l.svcCtx.AgentWithdrawalTaxModel.UpdateWithVersion(ctx, session, taxModel); err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新扣税记录失败: %v", err)
|
||||
}
|
||||
}
|
||||
// 不再需要恢复免税额度,因为统一按6%收取税收
|
||||
}
|
||||
if status == StatusSuccess {
|
||||
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(ctx, record.AgentId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wallet.FrozenBalance = roundMoney(wallet.FrozenBalance - record.Amount)
|
||||
if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(ctx, session, wallet); err != nil {
|
||||
return err
|
||||
}
|
||||
taxModel, err := l.svcCtx.AgentWithdrawalTaxModel.FindOneByWithdrawalId(ctx, record.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if taxModel.TaxStatus == model.TaxStatusPending {
|
||||
taxModel.TaxStatus = model.TaxStatusSuccess // 扣税状态 = 成功
|
||||
taxModel.TaxTime = sql.NullTime{Time: time.Now(), Valid: true}
|
||||
if err := l.svcCtx.AgentWithdrawalTaxModel.UpdateWithVersion(ctx, session, taxModel); err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新扣税记录失败: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 提现成功
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
l.Logger.Errorf("状态更新失败 outBizNo:%s error:%v", outBizNo, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 成功处理
|
||||
func (l *AgentWithdrawalLogic) handleTransferSuccess(outBizNo string, rsp interface{}) {
|
||||
l.updateWithdrawalStatus(outBizNo, StatusSuccess, "")
|
||||
l.Logger.Infof("提现成功 outBizNo:%s", outBizNo)
|
||||
}
|
||||
|
||||
// 失败处理
|
||||
func (l *AgentWithdrawalLogic) handleTransferFailure(outBizNo string, rsp interface{}) {
|
||||
var errorMsg string
|
||||
if resp, ok := rsp.(*alipay.FundTransUniTransferRsp); ok {
|
||||
errorMsg = l.mapAlipayError(resp.SubCode)
|
||||
}
|
||||
l.updateWithdrawalStatus(outBizNo, StatusFailed, errorMsg)
|
||||
l.Logger.Errorf("提现失败 outBizNo:%s reason:%s", outBizNo, errorMsg)
|
||||
}
|
||||
|
||||
// 超时处理
|
||||
func (l *AgentWithdrawalLogic) handleTransferTimeout(outBizNo string) {
|
||||
l.updateWithdrawalStatus(outBizNo, StatusFailed, "系统处理超时")
|
||||
l.Logger.Errorf("轮询超时 outBizNo:%s", outBizNo)
|
||||
}
|
||||
|
||||
// 错误处理
|
||||
func (l *AgentWithdrawalLogic) handleTransferError(outBizNo string, err error, contextMsg string) {
|
||||
l.updateWithdrawalStatus(outBizNo, StatusFailed, "系统处理异常")
|
||||
l.Logger.Errorf("%s outBizNo:%s error:%v", contextMsg, outBizNo, err)
|
||||
}
|
||||
|
||||
170
app/main/api/internal/logic/agent/applyforagentlogic.go
Normal file
170
app/main/api/internal/logic/agent/applyforagentlogic.go
Normal file
@@ -0,0 +1,170 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"bd-server/app/main/model"
|
||||
"bd-server/common/ctxdata"
|
||||
"bd-server/common/xerr"
|
||||
"bd-server/pkg/lzkit/crypto"
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
|
||||
"bd-server/app/main/api/internal/svc"
|
||||
"bd-server/app/main/api/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type ApplyForAgentLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewApplyForAgentLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ApplyForAgentLogic {
|
||||
return &ApplyForAgentLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *ApplyForAgentLogic) ApplyForAgent(req *types.AgentApplyReq) (resp *types.AgentApplyResp, err error) {
|
||||
claims, err := ctxdata.GetClaimsFromCtx(l.ctx)
|
||||
if err != nil && !errors.Is(err, ctxdata.ErrNoInCtx) {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "代理申请, %v", err)
|
||||
}
|
||||
secretKey := l.svcCtx.Config.Encrypt.SecretKey
|
||||
encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "加密手机号失败: %v", err)
|
||||
}
|
||||
// 先通过手机号查询用户,用于判断是否为内部用户
|
||||
existUser, findExistUserErr := l.svcCtx.UserModel.FindOneByMobile(l.ctx, sql.NullString{String: encryptedMobile, Valid: true})
|
||||
if findExistUserErr != nil && !errors.Is(findExistUserErr, model.ErrNotFound) {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理申请, 读取数据库获取用户失败, mobile: %s, err: %+v", encryptedMobile, findExistUserErr)
|
||||
}
|
||||
|
||||
// 内部用户跳过验证码验证
|
||||
needVerifyCode := os.Getenv("ENV") != "development"
|
||||
if needVerifyCode && existUser != nil && existUser.Inside == 1 {
|
||||
needVerifyCode = false
|
||||
}
|
||||
if needVerifyCode {
|
||||
// 校验验证码
|
||||
redisKey := fmt.Sprintf("%s:%s", "agentApply", encryptedMobile)
|
||||
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
|
||||
if err != nil {
|
||||
if errors.Is(err, redis.Nil) {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "代理申请, 验证码过期: %s", encryptedMobile)
|
||||
}
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理申请, 读取验证码redis缓存失败, mobile: %s, err: %+v", encryptedMobile, err)
|
||||
}
|
||||
if cacheCode != req.Code {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "代理申请, 验证码不正确: %s", encryptedMobile)
|
||||
}
|
||||
}
|
||||
|
||||
var userID int64
|
||||
transErr := l.svcCtx.AgentAuditModel.Trans(l.ctx, func(transCtx context.Context, session sqlx.Session) error {
|
||||
// 两种情况,1. 已注册账号然后申请代理 2. 未注册账号申请代理
|
||||
user, findUserErr := l.svcCtx.UserModel.FindOneByMobile(l.ctx, sql.NullString{String: encryptedMobile, Valid: true})
|
||||
if findUserErr != nil && !errors.Is(findUserErr, model.ErrNotFound) {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理申请, 读取数据库获取用户失败, mobile: %s, err: %+v", encryptedMobile, findUserErr)
|
||||
}
|
||||
if user == nil {
|
||||
if claims != nil && claims.UserType == model.UserTypeNormal {
|
||||
return errors.Wrapf(xerr.NewErrMsg("当前用户已注册,请输入注册的手机号"), "代理申请, 当前用户已注册")
|
||||
}
|
||||
userID, err = l.svcCtx.UserService.RegisterUser(l.ctx, encryptedMobile)
|
||||
if err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "代理申请, 注册用户失败: %+v", err)
|
||||
}
|
||||
} else {
|
||||
// 被封禁用户禁止登录/申请
|
||||
if user.Disable == 1 {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.USER_DISABLED), "账号已被封禁")
|
||||
}
|
||||
if user.CancelledAt.Valid {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.USER_CANCELLED), "账号已注销")
|
||||
}
|
||||
if claims != nil && claims.UserType == model.UserTypeTemp {
|
||||
// 临时用户,转为正式用户
|
||||
err = l.svcCtx.UserService.TempUserBindUser(l.ctx, session, user.Id)
|
||||
if err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "代理申请, 注册用户失败: %+v", err)
|
||||
}
|
||||
}
|
||||
userID = user.Id
|
||||
}
|
||||
|
||||
// 使用SelectBuilder构建查询,查找符合user_id的记录并按创建时间降序排序获取最新一条
|
||||
builder := l.svcCtx.AgentAuditModel.SelectBuilder().Where("user_id = ?", userID).OrderBy("create_time DESC").Limit(1)
|
||||
agentAuditList, findAgentAuditErr := l.svcCtx.AgentAuditModel.FindAll(transCtx, builder, "")
|
||||
if findAgentAuditErr != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理申请, 查找审核列表失败%+v", findAgentAuditErr)
|
||||
}
|
||||
|
||||
if len(agentAuditList) > 0 {
|
||||
agentAuditModel := agentAuditList[0]
|
||||
if agentAuditModel.Status == 0 {
|
||||
return errors.Wrapf(xerr.NewErrMsg("您的代理申请中"), "代理申请, 代理申请中")
|
||||
} else {
|
||||
return errors.Wrapf(xerr.NewErrMsg("您已申请过代理"), "代理申请, 代理已申请过")
|
||||
}
|
||||
}
|
||||
|
||||
var agentAudit model.AgentAudit
|
||||
agentAudit.UserId = userID
|
||||
agentAudit.Mobile = encryptedMobile
|
||||
agentAudit.Region = req.Region
|
||||
agentAudit.Status = 1
|
||||
_, insetAgentAuditErr := l.svcCtx.AgentAuditModel.Insert(transCtx, session, &agentAudit)
|
||||
if insetAgentAuditErr != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "代理申请, 保存代理审核信息失败: %v", insetAgentAuditErr)
|
||||
}
|
||||
|
||||
// 新增代理
|
||||
var agentModel model.Agent
|
||||
agentModel.Mobile = agentAudit.Mobile
|
||||
agentModel.Region = agentAudit.Region
|
||||
agentModel.UserId = agentAudit.UserId
|
||||
agentModel.LevelName = model.AgentLeveNameNormal
|
||||
agentModelInsert, insertAgentModelErr := l.svcCtx.AgentModel.Insert(transCtx, session, &agentModel)
|
||||
if insertAgentModelErr != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理申请, 新增代理失败: %+v", insertAgentModelErr)
|
||||
}
|
||||
agentID, _ := agentModelInsert.LastInsertId()
|
||||
|
||||
// 新增代理钱包
|
||||
var agentWallet model.AgentWallet
|
||||
agentWallet.AgentId = agentID
|
||||
_, insertAgentWalletModelErr := l.svcCtx.AgentWalletModel.Insert(transCtx, session, &agentWallet)
|
||||
if insertAgentWalletModelErr != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "代理申请, 新增代理钱包失败: %+v", insertAgentWalletModelErr)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if transErr != nil {
|
||||
return nil, transErr
|
||||
}
|
||||
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, model.UserTypeNormal)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 生成token失败 : %d", userID)
|
||||
}
|
||||
|
||||
// 获取当前时间戳
|
||||
now := time.Now().Unix()
|
||||
return &types.AgentApplyResp{
|
||||
AccessToken: token,
|
||||
AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire,
|
||||
RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter,
|
||||
}, nil
|
||||
}
|
||||
226
app/main/api/internal/logic/agent/bankcardwithdrawallogic.go
Normal file
226
app/main/api/internal/logic/agent/bankcardwithdrawallogic.go
Normal file
@@ -0,0 +1,226 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"bd-server/app/main/model"
|
||||
"bd-server/common/ctxdata"
|
||||
"bd-server/common/xerr"
|
||||
"context"
|
||||
"database/sql"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
|
||||
"bd-server/app/main/api/internal/svc"
|
||||
"bd-server/app/main/api/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
// 提现类型常量
|
||||
const (
|
||||
WithdrawTypeAlipay = 1 // 支付宝提现
|
||||
WithdrawTypeBankCard = 2 // 银行卡提现
|
||||
)
|
||||
|
||||
type BankCardWithdrawalLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewBankCardWithdrawalLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BankCardWithdrawalLogic {
|
||||
return &BankCardWithdrawalLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *BankCardWithdrawalLogic) BankCardWithdrawal(req *types.BankCardWithdrawalReq) (resp *types.WithdrawalResp, err error) {
|
||||
var (
|
||||
outBizNo string
|
||||
withdrawRes = &types.WithdrawalResp{}
|
||||
agentID int64
|
||||
)
|
||||
var finalWithdrawAmount float64 // 实际到账金额
|
||||
|
||||
// 验证银行卡号格式(16-19位数字)
|
||||
bankCardNoRegex := regexp.MustCompile(`^\d{16,19}$`)
|
||||
if !bankCardNoRegex.MatchString(req.BankCardNo) {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("银行卡号格式不正确,请输入16-19位数字"), "银行卡号格式验证失败")
|
||||
}
|
||||
|
||||
// 验证开户支行不能为空
|
||||
if req.BankName == "" {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("开户支行不能为空"), "开户支行验证失败")
|
||||
}
|
||||
|
||||
withdrawAmount := roundMoney(req.Amount)
|
||||
|
||||
// 使用事务处理核心操作
|
||||
err = l.svcCtx.AgentModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
|
||||
userID, err := ctxdata.GetUidFromCtx(l.ctx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户ID失败: %v", err)
|
||||
}
|
||||
|
||||
// 查询代理信息
|
||||
agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
|
||||
if err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败: %v", err)
|
||||
}
|
||||
agentID = agentModel.Id // 保存agentId用于日志
|
||||
|
||||
// 查询实名认证信息
|
||||
agentRealName, err := l.svcCtx.AgentRealNameModel.FindOneByAgentId(l.ctx, agentModel.Id)
|
||||
if err != nil {
|
||||
if errors.Is(err, model.ErrNotFound) {
|
||||
return errors.Wrapf(xerr.NewErrMsg("您未进行实名认证, 无法提现"), "您未进行实名认证")
|
||||
}
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询代理实名信息失败: %v", err)
|
||||
}
|
||||
if agentRealName.Status != model.AgentRealNameStatusApproved {
|
||||
return errors.Wrapf(xerr.NewErrMsg("您的实名认证未通过, 无法提现"), "您的实名认证未通过")
|
||||
}
|
||||
|
||||
// 查询钱包
|
||||
agentWallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(l.ctx, agentModel.Id)
|
||||
if err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理钱包失败: %v", err)
|
||||
}
|
||||
|
||||
// 校验可提现金额
|
||||
if withdrawAmount > roundMoney(agentWallet.Balance) {
|
||||
return errors.Wrapf(xerr.NewErrMsg("您可提现的余额不足"), "余额不足")
|
||||
}
|
||||
|
||||
// 最低提现金额验证
|
||||
if withdrawAmount < 50 {
|
||||
return errors.Wrapf(xerr.NewErrMsg("提现金额不能低于50元"), "金额验证失败")
|
||||
}
|
||||
|
||||
// 生成交易号
|
||||
outBizNo = "BC_" + l.svcCtx.AlipayService.GenerateOutTradeNo()
|
||||
|
||||
// 冻结资金(事务内操作)
|
||||
if err = l.freezeFunds(session, agentWallet, withdrawAmount); err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "资金冻结失败: %v", err)
|
||||
}
|
||||
|
||||
yearMonth := int64(time.Now().Year()*100 + int(time.Now().Month()))
|
||||
// 统一按6%收取税收
|
||||
taxRate := l.svcCtx.Config.TaxConfig.TaxRate
|
||||
var (
|
||||
taxAmount float64 // 应缴税费
|
||||
taxDeductionPart float64 // 应税金额
|
||||
TaxStatus int64 // 扣税状态
|
||||
exemptionAmount float64 // 免税金额(固定为0)
|
||||
)
|
||||
|
||||
// 统一扣税逻辑:所有提现都按6%收取税收
|
||||
exemptionAmount = 0 // 免税金额 = 0
|
||||
TaxStatus = model.TaxStatusPending // 扣税状态 = 待扣税
|
||||
taxDeductionPart = withdrawAmount // 应税金额 = 提现金额
|
||||
taxAmount = roundMoney(taxDeductionPart * taxRate) // 应缴税费 = 应税金额 * 税率
|
||||
finalWithdrawAmount = roundMoney(withdrawAmount - taxAmount) // 实际到账金额 = 提现金额 - 应缴税费
|
||||
|
||||
// 创建提现记录(初始状态为申请中,提现类型为银行卡)
|
||||
withdrawalID, err := l.createBankCardWithdrawalRecord(session, agentModel.Id, req.BankCardNo, req.BankName, agentRealName.Name, withdrawAmount, finalWithdrawAmount, taxAmount, outBizNo)
|
||||
if err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建提现记录失败: %v", err)
|
||||
}
|
||||
|
||||
// 扣税记录
|
||||
taxModel := &model.AgentWithdrawalTax{
|
||||
AgentId: agentModel.Id,
|
||||
YearMonth: yearMonth,
|
||||
WithdrawalId: withdrawalID,
|
||||
WithdrawalAmount: withdrawAmount,
|
||||
ExemptionAmount: exemptionAmount,
|
||||
TaxableAmount: taxDeductionPart,
|
||||
TaxRate: taxRate,
|
||||
TaxAmount: taxAmount,
|
||||
ActualAmount: finalWithdrawAmount,
|
||||
TaxStatus: TaxStatus,
|
||||
Remark: sql.NullString{String: "银行卡提现申请,待审核扣税", Valid: true},
|
||||
ExemptionRecordId: 0, // 不再使用免税额度记录
|
||||
}
|
||||
_, err = l.svcCtx.AgentWithdrawalTaxModel.Insert(ctx, session, taxModel)
|
||||
if err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建扣税记录失败: %v", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 银行卡提现不需要调用支付接口,直接返回申请中状态
|
||||
withdrawRes.Status = WithdrawStatusProcessing
|
||||
withdrawRes.FailMsg = ""
|
||||
|
||||
l.Logger.Infof("银行卡提现申请成功 outBizNo:%s agentId:%d amount:%f", outBizNo, agentID, withdrawAmount)
|
||||
return withdrawRes, nil
|
||||
}
|
||||
|
||||
// 创建银行卡提现记录(事务内操作)
|
||||
func (l *BankCardWithdrawalLogic) createBankCardWithdrawalRecord(session sqlx.Session, agentID int64, bankCardNo string, bankName string, payeeName string, amount float64, finalWithdrawAmount float64, taxAmount float64, outBizNo string) (int64, error) {
|
||||
record := &model.AgentWithdrawal{
|
||||
AgentId: agentID,
|
||||
WithdrawType: WithdrawTypeBankCard, // 银行卡提现
|
||||
WithdrawNo: outBizNo,
|
||||
PayeeAccount: bankCardNo, // 银行卡号存储在PayeeAccount字段
|
||||
Amount: roundMoney(amount),
|
||||
ActualAmount: roundMoney(finalWithdrawAmount),
|
||||
TaxAmount: roundMoney(taxAmount),
|
||||
Status: StatusProcessing, // 申请中状态
|
||||
BankCardNo: sql.NullString{String: bankCardNo, Valid: true},
|
||||
BankName: sql.NullString{String: bankName, Valid: true},
|
||||
PayeeName: sql.NullString{String: payeeName, Valid: true},
|
||||
}
|
||||
|
||||
result, err := l.svcCtx.AgentWithdrawalModel.Insert(l.ctx, session, record)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.LastInsertId()
|
||||
}
|
||||
|
||||
// 冻结资金(事务内操作)
|
||||
func (l *BankCardWithdrawalLogic) freezeFunds(session sqlx.Session, wallet *model.AgentWallet, amount float64) error {
|
||||
// 记录变动前的余额
|
||||
balanceBefore := wallet.Balance
|
||||
frozenBalanceBefore := wallet.FrozenBalance
|
||||
|
||||
// 更新钱包余额
|
||||
wallet.Balance = roundMoney(wallet.Balance - amount)
|
||||
wallet.FrozenBalance = roundMoney(wallet.FrozenBalance + amount)
|
||||
err := l.svcCtx.AgentWalletModel.UpdateWithVersion(l.ctx, session, wallet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 记录交易流水(冻结操作)
|
||||
err = l.svcCtx.AgentService.CreateWalletTransaction(
|
||||
l.ctx,
|
||||
session,
|
||||
wallet.AgentId,
|
||||
model.WalletTransactionTypeFreeze,
|
||||
roundMoney(amount), // 变动金额
|
||||
balanceBefore, // 变动前余额
|
||||
wallet.Balance, // 变动后余额
|
||||
frozenBalanceBefore, // 变动前冻结余额
|
||||
wallet.FrozenBalance, // 变动后冻结余额
|
||||
"", // 关联交易ID(暂时为空,创建提现记录后可以更新)
|
||||
0, // 关联用户ID
|
||||
"提现申请冻结资金", // 备注
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
111
app/main/api/internal/logic/agent/generatinglinklogic.go
Normal file
111
app/main/api/internal/logic/agent/generatinglinklogic.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"bd-server/app/main/model"
|
||||
"bd-server/common/ctxdata"
|
||||
"bd-server/common/xerr"
|
||||
"bd-server/pkg/lzkit/crypto"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"bd-server/app/main/api/internal/svc"
|
||||
"bd-server/app/main/api/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type GeneratingLinkLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewGeneratingLinkLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GeneratingLinkLogic {
|
||||
return &GeneratingLinkLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GeneratingLinkLogic) GeneratingLink(req *types.AgentGeneratingLinkReq) (resp *types.AgentGeneratingLinkResp, err error) {
|
||||
userID, err := ctxdata.GetUidFromCtx(l.ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成代理链接, %v", err)
|
||||
}
|
||||
productModel, err := l.svcCtx.ProductModel.FindOneByProductEn(l.ctx, req.Product)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成代理链接, %v", err)
|
||||
}
|
||||
|
||||
agentProductConfig, err := l.svcCtx.AgentProductConfigModel.FindOneByProductId(l.ctx, productModel.Id)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成代理链接, %v", err)
|
||||
}
|
||||
price, err := strconv.ParseFloat(req.Price, 64)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成代理链接, %v", err)
|
||||
}
|
||||
if price < agentProductConfig.PriceRangeMin || price > agentProductConfig.PriceRangeMax {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("请设定范围区间内的价格"), "")
|
||||
}
|
||||
agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
build := l.svcCtx.AgentLinkModel.SelectBuilder().Where(squirrel.And{
|
||||
squirrel.Eq{"user_id": userID},
|
||||
squirrel.Eq{"product_id": productModel.Id}, // 添加 product_id 的匹配条件
|
||||
squirrel.Eq{"price": price}, // 添加 price 的匹配条件
|
||||
squirrel.Eq{"agent_id": agentModel.Id}, // 添加 agent_id 的匹配条件
|
||||
})
|
||||
|
||||
agentLinkModel, err := l.svcCtx.AgentLinkModel.FindAll(l.ctx, build, "")
|
||||
if err != nil && !errors.Is(err, model.ErrNotFound) {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成代理链接, %v", err)
|
||||
}
|
||||
if len(agentLinkModel) > 0 {
|
||||
return &types.AgentGeneratingLinkResp{
|
||||
LinkIdentifier: agentLinkModel[0].LinkIdentifier,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var agentIdentifier types.AgentIdentifier
|
||||
agentIdentifier.AgentID = agentModel.Id
|
||||
agentIdentifier.Product = req.Product
|
||||
agentIdentifier.Price = req.Price
|
||||
agentIdentifierByte, err := json.Marshal(agentIdentifier)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单,序列化标识失败, %v", err)
|
||||
}
|
||||
key, decodeErr := hex.DecodeString("8e3e7a2f60edb49221e953b9c029ed10")
|
||||
if decodeErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 获取AES密钥失败: %+v", decodeErr)
|
||||
}
|
||||
|
||||
// Encrypt the params
|
||||
encrypted, err := crypto.AesEncryptURL(agentIdentifierByte, key)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成代理链接, %v", err)
|
||||
}
|
||||
|
||||
var agentLink model.AgentLink
|
||||
agentLink.AgentId = agentModel.Id
|
||||
agentLink.UserId = userID
|
||||
agentLink.LinkIdentifier = encrypted
|
||||
agentLink.ProductId = productModel.Id
|
||||
agentLink.Price = price
|
||||
_, err = l.svcCtx.AgentLinkModel.Insert(l.ctx, nil, &agentLink)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "生成代理链接, %v", err)
|
||||
}
|
||||
return &types.AgentGeneratingLinkResp{
|
||||
LinkIdentifier: encrypted,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"bd-server/common/ctxdata"
|
||||
"bd-server/common/xerr"
|
||||
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"bd-server/app/main/api/internal/svc"
|
||||
"bd-server/app/main/api/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type GetAgentAuditStatusLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewGetAgentAuditStatusLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentAuditStatusLogic {
|
||||
return &GetAgentAuditStatusLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GetAgentAuditStatusLogic) GetAgentAuditStatus() (resp *types.AgentAuditStatusResp, err error) {
|
||||
userID, err := ctxdata.GetUidFromCtx(l.ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理审核信息, %v", err)
|
||||
}
|
||||
|
||||
// 使用SelectBuilder构建查询,查找符合user_id的记录并按创建时间降序排序获取最新一条
|
||||
builder := l.svcCtx.AgentAuditModel.SelectBuilder().Where("user_id = ?", userID).OrderBy("create_time DESC").Limit(1)
|
||||
agentAuditList, err := l.svcCtx.AgentAuditModel.FindAll(l.ctx, builder, "")
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理审核信息, %v", err)
|
||||
}
|
||||
|
||||
if len(agentAuditList) == 0 {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "未找到代理审核信息")
|
||||
}
|
||||
|
||||
agentAuditModel := agentAuditList[0]
|
||||
var agentAuditStautsResp types.AgentAuditStatusResp
|
||||
copier.Copy(&agentAuditStautsResp, agentAuditModel)
|
||||
return &agentAuditStautsResp, nil
|
||||
}
|
||||
114
app/main/api/internal/logic/agent/getagentcommissionlogic.go
Normal file
114
app/main/api/internal/logic/agent/getagentcommissionlogic.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"bd-server/common/ctxdata"
|
||||
"bd-server/common/xerr"
|
||||
"bd-server/pkg/lzkit/crypto"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"bd-server/app/main/api/internal/svc"
|
||||
"bd-server/app/main/api/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type GetAgentCommissionLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewGetAgentCommissionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentCommissionLogic {
|
||||
return &GetAgentCommissionLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GetAgentCommissionLogic) GetAgentCommission(req *types.GetCommissionReq) (resp *types.GetCommissionResp, err error) {
|
||||
userID, err := ctxdata.GetUidFromCtx(l.ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理佣金列表, %v", err)
|
||||
}
|
||||
agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理佣金列表, %v", err)
|
||||
}
|
||||
builder := l.svcCtx.AgentCommissionModel.SelectBuilder().Where(squirrel.Eq{
|
||||
"agent_id": agentModel.Id,
|
||||
})
|
||||
agentCommissionModelList, total, err := l.svcCtx.AgentCommissionModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "create_time DESC")
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理佣金列表, 查找列表错误, %v", err)
|
||||
}
|
||||
|
||||
var list = make([]types.Commission, 0)
|
||||
|
||||
if len(agentCommissionModelList) > 0 {
|
||||
for _, agentCommissionModel := range agentCommissionModelList {
|
||||
var commission types.Commission
|
||||
copyErr := copier.Copy(&commission, agentCommissionModel)
|
||||
if copyErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理佣金列表, %v", err)
|
||||
}
|
||||
// 显式设置 status 字段和已退款金额
|
||||
commission.Status = agentCommissionModel.Status
|
||||
commission.RefundedAmount = agentCommissionModel.RefundedAmount
|
||||
// 计算净佣金金额(防御性处理,避免出现负数)
|
||||
netAmount := agentCommissionModel.Amount - agentCommissionModel.RefundedAmount
|
||||
if netAmount < 0 {
|
||||
netAmount = 0
|
||||
}
|
||||
commission.NetAmount = netAmount
|
||||
product, findProductErr := l.svcCtx.ProductModel.FindOne(l.ctx, agentCommissionModel.ProductId)
|
||||
if findProductErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理佣金列表, %v", err)
|
||||
}
|
||||
commission.CreateTime = agentCommissionModel.CreateTime.Format("2006-01-02 15:04:05")
|
||||
commission.ProductName = product.ProductName
|
||||
|
||||
// 从 order 表获取 platform_order_id
|
||||
orderModel, findOrderErr := l.svcCtx.OrderModel.FindOne(l.ctx, agentCommissionModel.OrderId)
|
||||
if findOrderErr == nil && orderModel != nil && orderModel.PlatformOrderId.Valid {
|
||||
commission.OrderId = orderModel.PlatformOrderId.String
|
||||
} else {
|
||||
commission.OrderId = ""
|
||||
}
|
||||
|
||||
queryModel, queryErr := l.svcCtx.QueryModel.FindOneByOrderId(l.ctx, agentCommissionModel.OrderId)
|
||||
if queryErr == nil && queryModel != nil {
|
||||
|
||||
key := l.svcCtx.Config.Encrypt.SecretKey
|
||||
keyBytes, decodeErr := hex.DecodeString(key)
|
||||
if decodeErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理佣金列表, %v", err)
|
||||
}
|
||||
// 根据订单号查询query表获取query_params
|
||||
decryptedData, decryptErr := crypto.AesDecrypt(queryModel.QueryParams, keyBytes)
|
||||
if decryptErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理佣金列表, %v", err)
|
||||
}
|
||||
// 解析query_params,从JSON字符串转换为map
|
||||
if queryModel.QueryParams != "" {
|
||||
|
||||
var queryParamsMap map[string]interface{}
|
||||
if unmarshalErr := json.Unmarshal(decryptedData, &queryParamsMap); unmarshalErr == nil {
|
||||
commission.QueryParams = queryParamsMap
|
||||
}
|
||||
}
|
||||
}
|
||||
list = append(list, commission)
|
||||
}
|
||||
}
|
||||
return &types.GetCommissionResp{
|
||||
Total: total,
|
||||
List: list,
|
||||
}, nil
|
||||
}
|
||||
92
app/main/api/internal/logic/agent/getagentinfologic.go
Normal file
92
app/main/api/internal/logic/agent/getagentinfologic.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"bd-server/common/ctxdata"
|
||||
"bd-server/common/xerr"
|
||||
"bd-server/pkg/lzkit/crypto"
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"bd-server/app/main/api/internal/svc"
|
||||
"bd-server/app/main/api/internal/types"
|
||||
"bd-server/app/main/model"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type GetAgentInfoLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewGetAgentInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentInfoLogic {
|
||||
return &GetAgentInfoLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GetAgentInfoLogic) GetAgentInfo() (resp *types.AgentInfoResp, err error) {
|
||||
claims, err := ctxdata.GetClaimsFromCtx(l.ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理信息, %v", err)
|
||||
}
|
||||
userID := claims.UserId
|
||||
userType := claims.UserType
|
||||
if userType == model.UserTypeTemp {
|
||||
return &types.AgentInfoResp{
|
||||
IsAgent: false,
|
||||
Status: 3,
|
||||
}, nil
|
||||
}
|
||||
agent, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
builder := l.svcCtx.AgentAuditModel.SelectBuilder().Where("user_id = ?", userID).OrderBy("create_time DESC").Limit(1)
|
||||
agentAuditList, findAgentAuditErr := l.svcCtx.AgentAuditModel.FindAll(l.ctx, builder, "")
|
||||
|
||||
if findAgentAuditErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理信息, %v", findAgentAuditErr)
|
||||
}
|
||||
|
||||
if len(agentAuditList) == 0 {
|
||||
return &types.AgentInfoResp{
|
||||
IsAgent: false,
|
||||
Status: 3,
|
||||
}, nil
|
||||
}
|
||||
|
||||
agentAuditModel := agentAuditList[0]
|
||||
return &types.AgentInfoResp{
|
||||
IsAgent: false,
|
||||
Status: agentAuditModel.Status,
|
||||
}, nil
|
||||
}
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理信息, %v", err)
|
||||
}
|
||||
agent.Mobile, err = crypto.DecryptMobile(agent.Mobile, l.svcCtx.Config.Encrypt.SecretKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理信息, 解密手机号失败: %v", err)
|
||||
}
|
||||
|
||||
IsRealName := false
|
||||
agentRealName, err := l.svcCtx.AgentRealNameModel.FindOneByAgentId(l.ctx, agent.Id)
|
||||
if err != nil && !errors.Is(err, model.ErrNotFound) {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理实名信息失败, %v", err)
|
||||
}
|
||||
if agentRealName != nil {
|
||||
IsRealName = true
|
||||
}
|
||||
return &types.AgentInfoResp{
|
||||
AgentID: agent.Id,
|
||||
IsAgent: true,
|
||||
Status: 1,
|
||||
Region: agent.Region,
|
||||
Mobile: agent.Mobile,
|
||||
IsRealName: IsRealName,
|
||||
}, nil
|
||||
}
|
||||
112
app/main/api/internal/logic/agent/getagentproductconfiglogic.go
Normal file
112
app/main/api/internal/logic/agent/getagentproductconfiglogic.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"bd-server/app/main/model"
|
||||
"bd-server/common/ctxdata"
|
||||
"bd-server/common/xerr"
|
||||
"context"
|
||||
"math"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zeromicro/go-zero/core/mr"
|
||||
|
||||
"bd-server/app/main/api/internal/svc"
|
||||
"bd-server/app/main/api/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type GetAgentProductConfigLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewGetAgentProductConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentProductConfigLogic {
|
||||
return &GetAgentProductConfigLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
type AgentProductConfigResp struct {
|
||||
}
|
||||
|
||||
func roundMoney(v float64) float64 {
|
||||
return math.Round(v*100) / 100
|
||||
}
|
||||
|
||||
func (l *GetAgentProductConfigLogic) GetAgentProductConfig() (resp *types.AgentProductConfigResp, err error) {
|
||||
userID, err := ctxdata.GetUidFromCtx(l.ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取推广项目配置失败, %v", err)
|
||||
}
|
||||
_, err = l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
|
||||
if err != nil {
|
||||
if errors.Is(err, model.ErrNotFound) {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("您不是代理"), "")
|
||||
}
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取推广项目配置失败, %v", err)
|
||||
}
|
||||
// 1. 查询推广项目配置数据
|
||||
builder := l.svcCtx.AgentProductConfigModel.SelectBuilder()
|
||||
agentProductConfigModelAll, err := l.svcCtx.AgentProductConfigModel.FindAll(l.ctx, builder, "")
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取推广项目配置失败, %v", err)
|
||||
}
|
||||
|
||||
// 用于存放最终组装好的响应数据
|
||||
var respList []types.AgentProductConfig
|
||||
|
||||
// 2. 使用 mr.MapReduceVoid 并行处理每个推广项目配置项
|
||||
mrMapErr := mr.MapReduceVoid(
|
||||
// source 函数:遍历所有推广项目配置,将每个配置项发送到 channel 中
|
||||
func(source chan<- interface{}) {
|
||||
for _, config := range agentProductConfigModelAll {
|
||||
source <- config
|
||||
}
|
||||
},
|
||||
// map 函数:处理每个推广项目配置项,根据 ProductId 查询会员用户配置,并组装响应数据
|
||||
func(item interface{}, writer mr.Writer[*types.AgentProductConfig], cancel func(error)) {
|
||||
// 将 item 转换为推广项目配置模型
|
||||
config := item.(*model.AgentProductConfig)
|
||||
var agentProductConfig types.AgentProductConfig
|
||||
productModel, findProductErr := l.svcCtx.ProductModel.FindOne(l.ctx, config.ProductId)
|
||||
if findProductErr != nil {
|
||||
cancel(findProductErr)
|
||||
return
|
||||
}
|
||||
agentProductConfig.ProductName = productModel.ProductName
|
||||
agentProductConfig.ProductEn = productModel.ProductEn
|
||||
// 配置平台成本价和定价成本
|
||||
agentProductConfigModel, findAgentProductConfigErr := l.svcCtx.AgentProductConfigModel.FindOneByProductId(l.ctx, config.ProductId)
|
||||
if findAgentProductConfigErr != nil {
|
||||
cancel(findAgentProductConfigErr)
|
||||
return
|
||||
}
|
||||
agentProductConfig.ProductID = config.ProductId
|
||||
agentProductConfig.CostPrice = roundMoney(agentProductConfigModel.CostPrice)
|
||||
agentProductConfig.PriceRangeMin = roundMoney(agentProductConfigModel.PriceRangeMin)
|
||||
agentProductConfig.PriceRangeMax = roundMoney(agentProductConfigModel.PriceRangeMax)
|
||||
agentProductConfig.PPricingStandard = roundMoney(agentProductConfigModel.PricingStandard)
|
||||
agentProductConfig.POverpricingRatio = agentProductConfigModel.OverpricingRatio
|
||||
|
||||
writer.Write(&agentProductConfig)
|
||||
},
|
||||
// reduce 函数:收集 map 阶段写入的响应数据,并汇总到 respList 中
|
||||
func(pipe <-chan *types.AgentProductConfig, cancel func(error)) {
|
||||
for item := range pipe {
|
||||
respList = append(respList, *item)
|
||||
}
|
||||
},
|
||||
)
|
||||
if mrMapErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取推广项目配置失败, %+v", mrMapErr)
|
||||
}
|
||||
|
||||
// 3. 组装最终响应返回
|
||||
return &types.AgentProductConfigResp{
|
||||
AgentProductConfig: respList,
|
||||
}, nil
|
||||
}
|
||||
128
app/main/api/internal/logic/agent/getagentrevenueinfologic.go
Normal file
128
app/main/api/internal/logic/agent/getagentrevenueinfologic.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"bd-server/app/main/model"
|
||||
"bd-server/common/ctxdata"
|
||||
"bd-server/common/xerr"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"bd-server/app/main/api/internal/svc"
|
||||
"bd-server/app/main/api/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type GetAgentRevenueInfoLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewGetAgentRevenueInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentRevenueInfoLogic {
|
||||
return &GetAgentRevenueInfoLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GetAgentRevenueInfoLogic) GetAgentRevenueInfo(req *types.GetAgentRevenueInfoReq) (resp *types.GetAgentRevenueInfoResp, err error) {
|
||||
claims, err := ctxdata.GetClaimsFromCtx(l.ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理信息, %v", err)
|
||||
}
|
||||
userID := claims.UserId
|
||||
userType := claims.UserType
|
||||
if userType == model.UserTypeTemp {
|
||||
return &types.GetAgentRevenueInfoResp{
|
||||
Balance: 0,
|
||||
TotalEarnings: 0,
|
||||
FrozenBalance: 0,
|
||||
DirectPush: types.DirectPushReport{
|
||||
TotalCommission: 0,
|
||||
TotalReport: 0,
|
||||
Today: types.TimeRangeReport{},
|
||||
Last7D: types.TimeRangeReport{},
|
||||
Last30D: types.TimeRangeReport{},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理信息, %v", err)
|
||||
}
|
||||
agentWalletModel, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(l.ctx, agentModel.Id)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理钱包, %v", err)
|
||||
}
|
||||
resp = &types.GetAgentRevenueInfoResp{}
|
||||
resp.Balance = agentWalletModel.Balance
|
||||
resp.TotalEarnings = agentWalletModel.TotalEarnings
|
||||
resp.FrozenBalance = agentWalletModel.FrozenBalance
|
||||
|
||||
agentCommissionModelBuild := l.svcCtx.AgentCommissionModel.SelectBuilder().
|
||||
Where(squirrel.Eq{"agent_id": agentModel.Id})
|
||||
|
||||
agentCommissionsModel, err := l.svcCtx.AgentCommissionModel.FindAll(l.ctx, agentCommissionModelBuild, "")
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理佣金, %v", err)
|
||||
}
|
||||
directPush, err := calculateDirectPushReport(agentCommissionsModel, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "计算直推报告, %v", err)
|
||||
}
|
||||
resp.DirectPush = directPush
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func calculateDirectPushReport(commissions []*model.AgentCommission, loc *time.Location) (types.DirectPushReport, error) {
|
||||
report := types.DirectPushReport{
|
||||
Today: types.TimeRangeReport{},
|
||||
Last7D: types.TimeRangeReport{},
|
||||
Last30D: types.TimeRangeReport{},
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
todayStart := now.Add(-24 * time.Hour)
|
||||
last7dStart := now.AddDate(0, 0, -7)
|
||||
last30dStart := now.AddDate(0, 0, -30)
|
||||
|
||||
for _, c := range commissions {
|
||||
createTime := c.CreateTime
|
||||
|
||||
if c.Status == 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
netAmount := c.Amount - c.RefundedAmount
|
||||
if netAmount <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
report.TotalCommission += netAmount
|
||||
report.TotalReport++
|
||||
|
||||
if createTime.After(todayStart) {
|
||||
report.Today.Commission += netAmount
|
||||
report.Today.Report++
|
||||
}
|
||||
|
||||
if createTime.After(last7dStart) {
|
||||
report.Last7D.Commission += netAmount
|
||||
report.Last7D.Report++
|
||||
}
|
||||
|
||||
if createTime.After(last30dStart) {
|
||||
report.Last30D.Commission += netAmount
|
||||
report.Last30D.Report++
|
||||
}
|
||||
}
|
||||
|
||||
return report, nil
|
||||
}
|
||||
66
app/main/api/internal/logic/agent/getagentwithdrawallogic.go
Normal file
66
app/main/api/internal/logic/agent/getagentwithdrawallogic.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"bd-server/common/ctxdata"
|
||||
"bd-server/common/xerr"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"bd-server/app/main/api/internal/svc"
|
||||
"bd-server/app/main/api/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type GetAgentWithdrawalLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewGetAgentWithdrawalLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentWithdrawalLogic {
|
||||
return &GetAgentWithdrawalLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GetAgentWithdrawalLogic) GetAgentWithdrawal(req *types.GetWithdrawalReq) (resp *types.GetWithdrawalResp, err error) {
|
||||
userID, err := ctxdata.GetUidFromCtx(l.ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理提现列表, %v", err)
|
||||
}
|
||||
agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理提现列表, %v", err)
|
||||
}
|
||||
builder := l.svcCtx.AgentWithdrawalModel.SelectBuilder().Where(squirrel.Eq{
|
||||
"agent_id": agentModel.Id,
|
||||
})
|
||||
agentWithdrawalModelList, total, err := l.svcCtx.AgentWithdrawalModel.FindPageListByPageWithTotal(l.ctx, builder, req.Page, req.PageSize, "create_time DESC")
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理提现列表, 查找列表错误, %v", err)
|
||||
}
|
||||
|
||||
var list = make([]types.Withdrawal, 0)
|
||||
|
||||
if len(agentWithdrawalModelList) > 0 {
|
||||
for _, agentWithdrawalModel := range agentWithdrawalModelList {
|
||||
var withdrawal types.Withdrawal
|
||||
copyErr := copier.Copy(&withdrawal, agentWithdrawalModel)
|
||||
if copyErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理提现列表, %v", err)
|
||||
}
|
||||
withdrawal.CreateTime = agentWithdrawalModel.CreateTime.Format("2006-01-02 15:04:05")
|
||||
list = append(list, withdrawal)
|
||||
}
|
||||
}
|
||||
return &types.GetWithdrawalResp{
|
||||
Total: total,
|
||||
List: list,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"bd-server/app/main/api/internal/svc"
|
||||
"bd-server/app/main/api/internal/types"
|
||||
"bd-server/common/ctxdata"
|
||||
"bd-server/common/xerr"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type GetAgentWithdrawalTaxExemptionLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewGetAgentWithdrawalTaxExemptionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAgentWithdrawalTaxExemptionLogic {
|
||||
return &GetAgentWithdrawalTaxExemptionLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GetAgentWithdrawalTaxExemptionLogic) GetAgentWithdrawalTaxExemption(req *types.GetWithdrawalTaxExemptionReq) (resp *types.GetWithdrawalTaxExemptionResp, err error) {
|
||||
_, err = ctxdata.GetUidFromCtx(l.ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户ID失败: %+v", err)
|
||||
}
|
||||
|
||||
// 统一按6%收取税收,不再有免税额度
|
||||
return &types.GetWithdrawalTaxExemptionResp{
|
||||
TotalExemptionAmount: 0.00, // 免税总额度为0
|
||||
UsedExemptionAmount: 0.00, // 已使用免税额度为0
|
||||
RemainingExemptionAmount: 0.00, // 剩余免税额度为0
|
||||
TaxRate: l.svcCtx.Config.TaxConfig.TaxRate, // 6%税收
|
||||
}, nil
|
||||
}
|
||||
81
app/main/api/internal/logic/agent/getbankcardinfologic.go
Normal file
81
app/main/api/internal/logic/agent/getbankcardinfologic.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"bd-server/app/main/model"
|
||||
"bd-server/common/ctxdata"
|
||||
"bd-server/common/xerr"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"bd-server/app/main/api/internal/svc"
|
||||
"bd-server/app/main/api/internal/types"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
type GetBankCardInfoLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewGetBankCardInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetBankCardInfoLogic {
|
||||
return &GetBankCardInfoLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GetBankCardInfoLogic) GetBankCardInfo(req *types.GetBankCardInfoReq) (resp *types.GetBankCardInfoResp, err error) {
|
||||
userID, err := ctxdata.GetUidFromCtx(l.ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户ID失败: %v", err)
|
||||
}
|
||||
|
||||
// 查询代理信息
|
||||
agentModel, err := l.svcCtx.AgentModel.FindOneByUserId(l.ctx, userID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询代理信息失败: %v", err)
|
||||
}
|
||||
|
||||
// 查询实名认证信息
|
||||
agentRealName, err := l.svcCtx.AgentRealNameModel.FindOneByAgentId(l.ctx, agentModel.Id)
|
||||
if err != nil {
|
||||
if errors.Is(err, model.ErrNotFound) {
|
||||
return nil, errors.Wrapf(xerr.NewErrMsg("您未进行实名认证"), "您未进行实名认证")
|
||||
}
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "查询代理实名信息失败: %v", err)
|
||||
}
|
||||
|
||||
// 初始化响应,包含实名认证信息
|
||||
resp = &types.GetBankCardInfoResp{
|
||||
PayeeName: agentRealName.Name,
|
||||
IdCard: agentRealName.IdCard,
|
||||
BankCardNo: "",
|
||||
BankName: "",
|
||||
}
|
||||
|
||||
// 查询最近一次成功的银行卡提现记录,用于自动填充
|
||||
builder := l.svcCtx.AgentWithdrawalModel.SelectBuilder()
|
||||
builder = builder.Where(squirrel.Eq{"agent_id": agentModel.Id})
|
||||
builder = builder.Where(squirrel.Eq{"withdraw_type": 2}) // 银行卡提现
|
||||
builder = builder.Where(squirrel.Eq{"status": 2}) // 成功状态
|
||||
builder = builder.OrderBy("create_time DESC")
|
||||
builder = builder.Limit(1)
|
||||
|
||||
list, err := l.svcCtx.AgentWithdrawalModel.FindAll(l.ctx, builder, "create_time DESC")
|
||||
if err == nil && len(list) > 0 {
|
||||
lastRecord := list[0]
|
||||
if lastRecord.BankCardNo.Valid {
|
||||
resp.BankCardNo = lastRecord.BankCardNo.String
|
||||
}
|
||||
if lastRecord.BankName.Valid {
|
||||
resp.BankName = lastRecord.BankName.String
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
86
app/main/api/internal/logic/agent/getlinkdatalogic.go
Normal file
86
app/main/api/internal/logic/agent/getlinkdatalogic.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"bd-server/common/xerr"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"bd-server/app/main/api/internal/svc"
|
||||
"bd-server/app/main/api/internal/types"
|
||||
"bd-server/app/main/model"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/mr"
|
||||
)
|
||||
|
||||
type GetLinkDataLogic struct {
|
||||
logx.Logger
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
}
|
||||
|
||||
func NewGetLinkDataLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetLinkDataLogic {
|
||||
return &GetLinkDataLogic{
|
||||
Logger: logx.WithContext(ctx),
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GetLinkDataLogic) GetLinkData(req *types.GetLinkDataReq) (resp *types.GetLinkDataResp, err error) {
|
||||
agentLinkModel, err := l.svcCtx.AgentLinkModel.FindOneByLinkIdentifier(l.ctx, req.LinkIdentifier)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理链接数据, %v", err)
|
||||
}
|
||||
|
||||
productModel, err := l.svcCtx.ProductModel.FindOne(l.ctx, agentLinkModel.ProductId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理链接数据, %v", err)
|
||||
}
|
||||
|
||||
// 查询产品关联的 feature
|
||||
build := l.svcCtx.ProductFeatureModel.SelectBuilder().Where(squirrel.Eq{
|
||||
"product_id": productModel.Id,
|
||||
})
|
||||
productFeatureAll, err := l.svcCtx.ProductFeatureModel.FindAll(l.ctx, build, "")
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取代理链接数据, 查找产品关联错误: %v", err)
|
||||
}
|
||||
|
||||
var product types.Product
|
||||
err = copier.Copy(&product, productModel)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取代理链接数据, 产品信息结构体复制失败, %v", err)
|
||||
}
|
||||
product.SellPrice = agentLinkModel.Price
|
||||
|
||||
// 并发查询所有 feature 详情
|
||||
mr.MapReduceVoid(func(source chan<- interface{}) {
|
||||
for _, productFeature := range productFeatureAll {
|
||||
source <- productFeature.FeatureId
|
||||
}
|
||||
}, func(item interface{}, writer mr.Writer[*model.Feature], cancel func(error)) {
|
||||
id := item.(int64)
|
||||
|
||||
feature, findFeatureErr := l.svcCtx.FeatureModel.FindOne(l.ctx, id)
|
||||
if findFeatureErr != nil {
|
||||
logx.WithContext(l.ctx).Errorf("获取代理链接数据, 查找关联feature错误: %d, err:%v", id, findFeatureErr)
|
||||
return
|
||||
}
|
||||
if feature != nil && feature.Id > 0 {
|
||||
writer.Write(feature)
|
||||
}
|
||||
}, func(pipe <-chan *model.Feature, cancel func(error)) {
|
||||
for item := range pipe {
|
||||
var feature types.Feature
|
||||
_ = copier.Copy(&feature, item)
|
||||
product.Features = append(product.Features, feature)
|
||||
}
|
||||
})
|
||||
return &types.GetLinkDataResp{
|
||||
Product: product,
|
||||
}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user