t
This commit is contained in:
198
app/main/api/internal/logic/agent/bankcardwithdrawallogic.go
Normal file
198
app/main/api/internal/logic/agent/bankcardwithdrawallogic.go
Normal file
@@ -0,0 +1,198 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"regexp"
|
||||
"time"
|
||||
"tydata-server/app/main/model"
|
||||
"tydata-server/common/ctxdata"
|
||||
"tydata-server/common/xerr"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zeromicro/go-zero/core/stores/sqlx"
|
||||
|
||||
"tydata-server/app/main/api/internal/svc"
|
||||
"tydata-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{}
|
||||
)
|
||||
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("开户支行不能为空"), "开户支行验证失败")
|
||||
}
|
||||
|
||||
// 使用事务处理核心操作
|
||||
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)
|
||||
}
|
||||
|
||||
// 查询实名认证信息
|
||||
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 req.Amount > agentWallet.Balance {
|
||||
return errors.Wrapf(xerr.NewErrMsg("您可提现的余额不足"), "余额不足")
|
||||
}
|
||||
|
||||
// 最低提现金额验证
|
||||
if req.Amount < 50 {
|
||||
return errors.Wrapf(xerr.NewErrMsg("提现金额不能低于50元"), "金额验证失败")
|
||||
}
|
||||
|
||||
// 生成交易号
|
||||
outBizNo = "BC_" + l.svcCtx.AlipayService.GenerateOutTradeNo()
|
||||
|
||||
// 冻结资金(事务内操作)
|
||||
if err = l.freezeFunds(session, agentWallet, req.Amount); 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 = req.Amount // 应税金额 = 提现金额
|
||||
taxAmount = taxDeductionPart * taxRate // 应缴税费 = 应税金额 * 税率
|
||||
finalWithdrawAmount = req.Amount - taxAmount // 实际到账金额 = 提现金额 - 应缴税费
|
||||
|
||||
// 创建提现记录(初始状态为申请中,提现类型为银行卡)
|
||||
withdrawalID, err := l.createBankCardWithdrawalRecord(session, agentModel.Id, req.BankCardNo, req.BankName, agentRealName.Name, req.Amount, 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: req.Amount,
|
||||
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, 0, req.Amount)
|
||||
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,
|
||||
WithdrawNo: outBizNo,
|
||||
PayeeAccount: bankCardNo, // 银行卡号存储在PayeeAccount字段
|
||||
Amount: amount,
|
||||
ActualAmount: finalWithdrawAmount,
|
||||
TaxAmount: taxAmount,
|
||||
Status: StatusProcessing, // 申请中状态
|
||||
// 注意:以下字段需要在数据库迁移后添加,如果模型还没有这些字段,需要先更新模型
|
||||
// WithdrawType: WithdrawTypeBankCard,
|
||||
// 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 {
|
||||
wallet.Balance -= amount
|
||||
wallet.FrozenBalance += amount
|
||||
err := l.svcCtx.AgentWalletModel.UpdateWithVersion(l.ctx, session, wallet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
87
app/main/api/internal/logic/agent/getbankcardinfologic.go
Normal file
87
app/main/api/internal/logic/agent/getbankcardinfologic.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tydata-server/app/main/model"
|
||||
"tydata-server/common/ctxdata"
|
||||
"tydata-server/common/xerr"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"tydata-server/app/main/api/internal/svc"
|
||||
"tydata-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)
|
||||
}
|
||||
|
||||
// 查询最近一次银行卡提现记录,用于自动填充
|
||||
// 注意:这里需要查询 withdraw_type = 2 的记录
|
||||
// 如果数据库还没有迁移,可以先返回空值
|
||||
resp = &types.GetBankCardInfoResp{
|
||||
PayeeName: agentRealName.Name,
|
||||
IdCard: agentRealName.IdCard,
|
||||
BankCardNo: "",
|
||||
BankName: "",
|
||||
}
|
||||
|
||||
// 查询最近一次成功的银行卡提现记录
|
||||
// 这里使用SelectBuilder查询,但由于模型可能还没有withdraw_type字段,先注释
|
||||
// builder := l.svcCtx.AgentWithdrawalModel.SelectBuilder()
|
||||
// builder = builder.Where(squirrel.Eq{"agent_id": agentModel.Id})
|
||||
// builder = builder.Where(squirrel.Eq{"withdraw_type": WithdrawTypeBankCard})
|
||||
// builder = builder.Where(squirrel.Eq{"status": StatusSuccess})
|
||||
// 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
|
||||
// }
|
||||
// }
|
||||
|
||||
// 临时方案:从PayeeAccount字段读取(如果之前有银行卡提现记录)
|
||||
// 注意:这个方案不够准确,因为PayeeAccount也可能存储支付宝账号
|
||||
// 建议数据库迁移后使用上面的方案
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
Reference in New Issue
Block a user