This commit is contained in:
2025-12-26 14:43:27 +08:00
18 changed files with 1180 additions and 35 deletions

View File

@@ -61,6 +61,10 @@ service main {
// 代理会员配置编辑
@handler AdminUpdateAgentMembershipConfig
post /agent-membership-config/update (AdminUpdateAgentMembershipConfigReq) returns (AdminUpdateAgentMembershipConfigResp)
// 银行卡提现审核(确认/拒绝)
@handler AdminReviewBankCardWithdrawal
post /agent-withdrawal/bank-card/review (AdminReviewBankCardWithdrawalReq) returns (AdminReviewBankCardWithdrawalResp)
}
type (
@@ -189,10 +193,16 @@ type (
AgentId int64 `json:"agent_id"` // 代理ID
WithdrawNo string `json:"withdraw_no"` // 提现单号
Amount float64 `json:"amount"` // 金额
ActualAmount float64 `json:"actual_amount"` // 实际到账金额(扣税后)
TaxAmount float64 `json:"tax_amount"` // 扣税金额
Status int64 `json:"status"` // 状态
PayeeAccount string `json:"payee_account"` // 收款账户
Remark string `json:"remark"` // 备注
CreateTime string `json:"create_time"` // 创建时间
WithdrawType int64 `json:"withdraw_type"` // 提现类型:1-支付宝,2-银行卡
BankCardNo string `json:"bank_card_no"` // 银行卡号
BankName string `json:"bank_name"` // 开户支行
PayeeName string `json:"payee_name"` // 收款人姓名
}
// 代理提现分页查询响应
@@ -383,4 +393,16 @@ type (
AdminUpdateAgentMembershipConfigResp {
Success bool `json:"success"` // 是否成功
}
// 银行卡提现审核请求
AdminReviewBankCardWithdrawalReq {
WithdrawalId int64 `json:"withdrawal_id"` // 提现记录ID
Action int64 `json:"action"` // 操作:1-确认,2-拒绝
Remark string `json:"remark"` // 备注(拒绝时必填)
}
// 银行卡提现审核响应
AdminReviewBankCardWithdrawalResp {
Success bool `json:"success"` // 是否成功
}
)

View File

@@ -278,6 +278,14 @@ service main {
@handler GetAgentWithdrawalTaxExemption
get /withdrawal/tax/exemption (GetWithdrawalTaxExemptionReq) returns (GetWithdrawalTaxExemptionResp)
// 银行卡提现申请
@handler BankCardWithdrawal
post /withdrawal/bank-card (BankCardWithdrawalReq) returns (WithdrawalResp)
// 获取历史银行卡信息
@handler GetBankCardInfo
get /withdrawal/bank-card/info (GetBankCardInfoReq) returns (GetBankCardInfoResp)
}
type (
@@ -318,9 +326,10 @@ type (
SubWithdrawReward float64 `json:"sub_withdraw_reward"`
}
Commission {
ProductName string `json:"product_name"`
Amount float64 `json:"amount"`
CreateTime string `json:"create_time"`
OrderId string `json:"order_id"` // 订单号
ProductName string `json:"product_name"`
Amount float64 `json:"amount"`
CreateTime string `json:"create_time"`
QueryParams map[string]interface{} `json:"query_params,omitempty"`
}
GetCommissionReq {
@@ -384,6 +393,21 @@ type (
RemainingExemptionAmount float64 `json:"remaining_exemption_amount"`
TaxRate float64 `json:"tax_rate"`
}
// 银行卡提现申请请求
BankCardWithdrawalReq {
BankCardNo string `json:"bank_card_no"` // 银行卡号
BankName string `json:"bank_name"` // 开户支行
Amount float64 `json:"amount"` // 提现金额
}
// 获取历史银行卡信息请求
GetBankCardInfoReq {}
// 获取历史银行卡信息响应
GetBankCardInfoResp {
BankCardNo string `json:"bank_card_no"` // 银行卡号
BankName string `json:"bank_name"` // 开户支行
PayeeName string `json:"payee_name"` // 收款人姓名
IdCard string `json:"id_card"` // 身份证号
}
)
@server (

View File

@@ -0,0 +1,29 @@
package admin_agent
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"tydata-server/app/main/api/internal/logic/admin_agent"
"tydata-server/app/main/api/internal/svc"
"tydata-server/app/main/api/internal/types"
"tydata-server/common/result"
"tydata-server/pkg/lzkit/validator"
)
func AdminReviewBankCardWithdrawalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.AdminReviewBankCardWithdrawalReq
if err := httpx.Parse(r, &req); err != nil {
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := admin_agent.NewAdminReviewBankCardWithdrawalLogic(r.Context(), svcCtx)
resp, err := l.AdminReviewBankCardWithdrawal(&req)
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -0,0 +1,29 @@
package agent
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"tydata-server/app/main/api/internal/logic/agent"
"tydata-server/app/main/api/internal/svc"
"tydata-server/app/main/api/internal/types"
"tydata-server/common/result"
"tydata-server/pkg/lzkit/validator"
)
func BankCardWithdrawalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.BankCardWithdrawalReq
if err := httpx.Parse(r, &req); err != nil {
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := agent.NewBankCardWithdrawalLogic(r.Context(), svcCtx)
resp, err := l.BankCardWithdrawal(&req)
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -0,0 +1,29 @@
package agent
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"tydata-server/app/main/api/internal/logic/agent"
"tydata-server/app/main/api/internal/svc"
"tydata-server/app/main/api/internal/types"
"tydata-server/common/result"
"tydata-server/pkg/lzkit/validator"
)
func GetBankCardInfoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.GetBankCardInfoReq
if err := httpx.Parse(r, &req); err != nil {
result.ParamErrorResult(r, w, err)
return
}
if err := validator.Validate(req); err != nil {
result.ParamValidateErrorResult(r, w, err)
return
}
l := agent.NewGetBankCardInfoLogic(r.Context(), svcCtx)
resp, err := l.GetBankCardInfo(&req)
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -87,6 +87,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
Path: "/agent-reward/list",
Handler: admin_agent.AdminGetAgentRewardListHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/agent-withdrawal/bank-card/review",
Handler: admin_agent.AdminReviewBankCardWithdrawalHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/agent-withdrawal/list",
@@ -765,6 +770,16 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
Path: "/withdrawal",
Handler: agent.AgentWithdrawalHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/withdrawal/bank-card",
Handler: agent.BankCardWithdrawalHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/withdrawal/bank-card/info",
Handler: agent.GetBankCardInfoHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/withdrawal/tax/exemption",

View File

@@ -49,6 +49,19 @@ func (l *AdminGetAgentWithdrawalListLogic) AdminGetAgentWithdrawalList(req *type
item.Remark = v.Remark.String
}
item.CreateTime = v.CreateTime.Format("2006-01-02 15:04:05")
// 手动设置银行卡信息copier不会自动处理sql.NullString
item.WithdrawType = v.WithdrawType
if v.BankCardNo.Valid {
item.BankCardNo = v.BankCardNo.String
}
if v.BankName.Valid {
item.BankName = v.BankName.String
}
if v.PayeeName.Valid {
item.PayeeName = v.PayeeName.String
}
items = append(items, item)
}
resp = &types.AdminGetAgentWithdrawalListResp{

View File

@@ -0,0 +1,187 @@
package admin_agent
import (
"context"
"database/sql"
"time"
"tydata-server/app/main/model"
"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 (
ReviewActionApprove = 1 // 确认
ReviewActionReject = 2 // 拒绝
)
// 状态常量
const (
StatusPending = 1 // 申请中/处理中
StatusSuccess = 2 // 成功
StatusFailed = 3 // 失败
)
// 提现类型常量
const (
WithdrawTypeAlipay = 1 // 支付宝提现
WithdrawTypeBankCard = 2 // 银行卡提现
)
type AdminReviewBankCardWithdrawalLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminReviewBankCardWithdrawalLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminReviewBankCardWithdrawalLogic {
return &AdminReviewBankCardWithdrawalLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminReviewBankCardWithdrawalLogic) AdminReviewBankCardWithdrawal(req *types.AdminReviewBankCardWithdrawalReq) (resp *types.AdminReviewBankCardWithdrawalResp, err error) {
// 验证操作类型
if req.Action != ReviewActionApprove && req.Action != ReviewActionReject {
return nil, errors.Wrapf(xerr.NewErrMsg("操作类型不正确"), "操作类型验证失败")
}
// 拒绝操作必须填写备注
if req.Action == ReviewActionReject && req.Remark == "" {
return nil, errors.Wrapf(xerr.NewErrMsg("拒绝提现必须填写拒绝原因"), "拒绝原因验证失败")
}
resp = &types.AdminReviewBankCardWithdrawalResp{
Success: false,
}
// 使用事务处理审核操作
err = l.svcCtx.AgentModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
// 获取提现记录
record, err := l.svcCtx.AgentWithdrawalModel.FindOne(ctx, req.WithdrawalId)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return errors.Wrapf(xerr.NewErrMsg("提现记录不存在"), "提现记录不存在")
}
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询提现记录失败: %v", err)
}
// 验证提现记录状态必须是申请中
if record.Status != StatusPending {
return errors.Wrapf(xerr.NewErrMsg("该提现记录已处理,无法重复操作"), "状态验证失败")
}
// 验证提现类型
if record.WithdrawType != WithdrawTypeBankCard {
return errors.Wrapf(xerr.NewErrMsg("该记录不是银行卡提现,无法审核"), "提现类型验证失败")
}
if req.Action == ReviewActionApprove {
// 确认提现
return l.approveWithdrawal(ctx, session, record)
} else {
// 拒绝提现
return l.rejectWithdrawal(ctx, session, record, req.Remark)
}
})
if err != nil {
return nil, err
}
resp.Success = true
return resp, nil
}
// 确认提现
func (l *AdminReviewBankCardWithdrawalLogic) approveWithdrawal(ctx context.Context, session sqlx.Session, record *model.AgentWithdrawal) error {
// 更新提现记录状态为成功
record.Status = StatusSuccess
record.Remark = sql.NullString{String: "管理员确认提现", Valid: true}
if _, err := l.svcCtx.AgentWithdrawalModel.Update(ctx, session, record); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新提现记录失败: %v", err)
}
// 解冻资金并扣除FrozenBalance -= amount, Balance不变
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(ctx, record.AgentId)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询钱包失败: %v", err)
}
wallet.FrozenBalance -= record.Amount
if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(ctx, session, wallet); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新钱包失败: %v", err)
}
// 更新扣税记录状态为成功
taxModel, err := l.svcCtx.AgentWithdrawalTaxModel.FindOneByWithdrawalId(ctx, record.Id)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询扣税记录失败: %v", 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)
}
}
// 提现成功后,给上级代理发放提现奖励
withdrawRewardErr := l.svcCtx.AgentService.GiveWithdrawReward(ctx, record.AgentId, record.Amount, session)
if withdrawRewardErr != nil {
l.Logger.Errorf("发放提现奖励失败代理ID%d提现金额%f错误%+v", record.AgentId, record.Amount, withdrawRewardErr)
// 提现奖励失败不影响主流程,只记录日志
} else {
l.Logger.Infof("发放提现奖励成功代理ID%d提现金额%f", record.AgentId, record.Amount)
}
l.Logger.Infof("银行卡提现确认成功 withdrawalId:%d amount:%f", record.Id, record.Amount)
return nil
}
// 拒绝提现
func (l *AdminReviewBankCardWithdrawalLogic) rejectWithdrawal(ctx context.Context, session sqlx.Session, record *model.AgentWithdrawal, remark string) error {
// 更新提现记录状态为失败
record.Status = StatusFailed
record.Remark = sql.NullString{String: "管理员拒绝:" + remark, Valid: true}
if _, err := l.svcCtx.AgentWithdrawalModel.Update(ctx, session, record); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新提现记录失败: %v", err)
}
// 解冻资金FrozenBalance -= amount, Balance += amount
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(ctx, record.AgentId)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询钱包失败: %v", err)
}
wallet.Balance += record.Amount
wallet.FrozenBalance -= record.Amount
if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(ctx, session, wallet); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新钱包失败: %v", err)
}
// 更新扣税记录状态为失败
taxModel, err := l.svcCtx.AgentWithdrawalTaxModel.FindOneByWithdrawalId(ctx, record.Id)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询扣税记录失败: %v", 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)
}
}
l.Logger.Infof("银行卡提现拒绝 withdrawalId:%d amount:%f reason:%s", record.Id, record.Amount, remark)
return nil
}

View File

@@ -220,6 +220,7 @@ func (l *AgentWithdrawalLogic) mapAlipayError(code string) string {
func (l *AgentWithdrawalLogic) createWithdrawalRecord(session sqlx.Session, agentID int64, payeeAccount string, amount float64, finalWithdrawAmount float64, taxAmount float64, outBizNo string) (int64, error) {
record := &model.AgentWithdrawal{
AgentId: agentID,
WithdrawType: 1, // 支付宝提现
WithdrawNo: outBizNo,
PayeeAccount: payeeAccount,
Amount: amount,

View File

@@ -0,0 +1,199 @@
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{}
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("开户支行不能为空"), "开户支行验证失败")
}
// 使用事务处理核心操作
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 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, agentID, 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,
WithdrawType: WithdrawTypeBankCard, // 银行卡提现
WithdrawNo: outBizNo,
PayeeAccount: bankCardNo, // 银行卡号存储在PayeeAccount字段
Amount: amount,
ActualAmount: finalWithdrawAmount,
TaxAmount: 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 {
wallet.Balance -= amount
wallet.FrozenBalance += amount
err := l.svcCtx.AgentWalletModel.UpdateWithVersion(l.ctx, session, wallet)
if err != nil {
return err
}
return nil
}

View File

@@ -64,6 +64,14 @@ func (l *GetAgentCommissionLogic) GetAgentCommission(req *types.GetCommissionReq
}
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 {

View File

@@ -0,0 +1,81 @@
package agent
import (
"context"
"tydata-server/app/main/model"
"tydata-server/common/ctxdata"
"tydata-server/common/xerr"
"github.com/Masterminds/squirrel"
"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)
}
// 初始化响应,包含实名认证信息
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
}

View File

@@ -269,7 +269,7 @@ func (s *AuthorizationService) generatePDFContent(userInfo map[string]interface{
}
// 构建授权书内容(去掉标题部分)
content := fmt.Sprintf(`海南天远大数据科技有限公司:
content := fmt.Sprintf(`海南海宇大数据有限公司:
本人%s拟向贵司申请大数据分析报告查询业务贵司需要了解本人相关状况用于查询大数据分析报告因此本人同意向贵司提供本人的姓名和手机号等个人信息并同意贵司向第三方传送上述信息。第三方将使用上述信息核实信息真实情况查询信用记录并生成报告。
授权内容如下:
@@ -292,8 +292,8 @@ func (s *AuthorizationService) generatePDFContent(userInfo map[string]interface{
附加说明:
本人在授权的相关数据将依据法律法规及贵司内部数据管理规范妥善存储,存储期限为法律要求的最短必要时间。超过存储期限或在数据使用目的达成后,贵司将对相关数据进行销毁或匿名化处理。
本人有权随时撤回本授权书中的授权,但撤回前的授权行为及其法律后果仍具有法律效力。若需撤回授权,本人可通过贵司官方渠道提交书面申请,贵司将在收到申请后依法停止对本人数据的使用。
你通过"天远数据",自愿支付相应费用,用于购买海南天远大数据科技有限公司的大数据报告产品。如若对产品内容存在异议可通过邮箱admin@iieeii.com或APP"联系客服"按钮进行反馈贵司将在收到异议之日起20日内进行核查和处理并将结果答复。
你向海南天远大数据科技有限公司的支付方式为:海南天远大数据科技有限公司及其经官方授权的相关企业的支付宝账户。
你通过"天远数据",自愿支付相应费用,用于购买海南海宇大数据有限公司的大数据报告产品。如若对产品内容存在异议可通过邮箱admin@iieeii.com或APP"联系客服"按钮进行反馈贵司将在收到异议之日起20日内进行核查和处理并将结果答复。
你向海南海宇大数据有限公司的支付方式为:海南海宇大数据有限公司及其经官方授权的相关企业的支付宝账户。
争议解决机制:
若因本授权书引发争议,双方应友好协商解决;协商不成的,双方同意将争议提交至授权书签署地(海南省)有管辖权的人民法院解决。

View File

@@ -695,6 +695,16 @@ type AdminRetryAgentProcessResp struct {
ProcessedAt string `json:"processed_at"` // 处理时间
}
type AdminReviewBankCardWithdrawalReq struct {
WithdrawalId int64 `json:"withdrawal_id"` // 提现记录ID
Action int64 `json:"action"` // 操作:1-确认,2-拒绝
Remark string `json:"remark"` // 备注(拒绝时必填)
}
type AdminReviewBankCardWithdrawalResp struct {
Success bool `json:"success"` // 是否成功
}
type AdminRoleApiInfo struct {
Id int64 `json:"id"`
RoleId int64 `json:"role_id"`
@@ -1129,10 +1139,16 @@ type AgentWithdrawalListItem struct {
AgentId int64 `json:"agent_id"` // 代理ID
WithdrawNo string `json:"withdraw_no"` // 提现单号
Amount float64 `json:"amount"` // 金额
ActualAmount float64 `json:"actual_amount"` // 实际到账金额(扣税后)
TaxAmount float64 `json:"tax_amount"` // 扣税金额
Status int64 `json:"status"` // 状态
PayeeAccount string `json:"payee_account"` // 收款账户
Remark string `json:"remark"` // 备注
CreateTime string `json:"create_time"` // 创建时间
WithdrawType int64 `json:"withdraw_type"` // 提现类型:1-支付宝,2-银行卡
BankCardNo string `json:"bank_card_no"` // 银行卡号
BankName string `json:"bank_name"` // 开户支行
PayeeName string `json:"payee_name"` // 收款人姓名
}
type AuthorizationDocumentInfo struct {
@@ -1148,6 +1164,12 @@ type AuthorizationDocumentInfo struct {
CreateTime string `json:"createTime"` // 创建时间
}
type BankCardWithdrawalReq struct {
BankCardNo string `json:"bank_card_no"` // 银行卡号
BankName string `json:"bank_name"` // 开户支行
Amount float64 `json:"amount"` // 提现金额
}
type BindMobileReq struct {
Mobile string `json:"mobile" validate:"required,mobile"`
Code string `json:"code" validate:"required"`
@@ -1160,6 +1182,7 @@ type BindMobileResp struct {
}
type Commission struct {
OrderId string `json:"order_id"` // 订单号
ProductName string `json:"product_name"`
Amount float64 `json:"amount"`
CreateTime string `json:"create_time"`
@@ -1333,6 +1356,16 @@ type GetAuthorizationDocumentResp struct {
CreateTime string `json:"createTime"` // 创建时间
}
type GetBankCardInfoReq struct {
}
type GetBankCardInfoResp struct {
BankCardNo string `json:"bank_card_no"` // 银行卡号
BankName string `json:"bank_name"` // 开户支行
PayeeName string `json:"payee_name"` // 收款人姓名
IdCard string `json:"id_card"` // 身份证号
}
type GetCommissionReq struct {
Page int64 `form:"page"` // 页码
PageSize int64 `form:"page_size"` // 每页数据量

View File

@@ -10,8 +10,6 @@ import (
"time"
"tydata-server/common/globalkey"
"github.com/Masterminds/squirrel"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/stores/builder"
@@ -19,6 +17,7 @@ import (
"github.com/zeromicro/go-zero/core/stores/sqlc"
"github.com/zeromicro/go-zero/core/stores/sqlx"
"github.com/zeromicro/go-zero/core/stringx"
"tydata-server/common/globalkey"
)
var (
@@ -27,8 +26,8 @@ var (
agentWithdrawalRowsExpectAutoSet = strings.Join(stringx.Remove(agentWithdrawalFieldNames, "`id`", "`create_time`", "`update_time`"), ",")
agentWithdrawalRowsWithPlaceHolder = strings.Join(stringx.Remove(agentWithdrawalFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?"
cacheHmAgentWithdrawalIdPrefix = "cache:tydata:agentWithdrawal:id:"
cacheHmAgentWithdrawalWithdrawNoPrefix = "cache:tydata:agentWithdrawal:withdrawNo:"
cacheTydataAgentWithdrawalIdPrefix = "cache:tydata:agentWithdrawal:id:"
cacheTydataAgentWithdrawalWithdrawNoPrefix = "cache:tydata:agentWithdrawal:withdrawNo:"
)
type (
@@ -59,12 +58,16 @@ type (
AgentWithdrawal struct {
Id int64 `db:"id"`
AgentId int64 `db:"agent_id"` // 代理ID
WithdrawType int64 `db:"withdraw_type"` // 提现类型:1-支付宝,2-银行卡
WithdrawNo string `db:"withdraw_no"` // 提现单号
Amount float64 `db:"amount"` // 提现金额
ActualAmount float64 `db:"actual_amount"` // 实际到账金额(扣税后)
TaxAmount float64 `db:"tax_amount"` // 扣税金额
Status int64 `db:"status"` // 状态:1-申请中,2-成功,3-失败
PayeeAccount string `db:"payeeAccount"` // 收款人账号
BankCardNo sql.NullString `db:"bank_card_no"` // 银行卡号
BankName sql.NullString `db:"bank_name"` // 开户支行
PayeeName sql.NullString `db:"payee_name"` // 收款人姓名
Remark sql.NullString `db:"remark"`
CreateTime time.Time `db:"create_time"` // 创建时间
UpdateTime time.Time `db:"update_time"` // 更新时间
@@ -83,21 +86,21 @@ func newAgentWithdrawalModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultAgent
func (m *defaultAgentWithdrawalModel) Insert(ctx context.Context, session sqlx.Session, data *AgentWithdrawal) (sql.Result, error) {
data.DelState = globalkey.DelStateNo
hmAgentWithdrawalIdKey := fmt.Sprintf("%s%v", cacheHmAgentWithdrawalIdPrefix, data.Id)
hmAgentWithdrawalWithdrawNoKey := fmt.Sprintf("%s%v", cacheHmAgentWithdrawalWithdrawNoPrefix, data.WithdrawNo)
tydataAgentWithdrawalIdKey := fmt.Sprintf("%s%v", cacheTydataAgentWithdrawalIdPrefix, data.Id)
tydataAgentWithdrawalWithdrawNoKey := fmt.Sprintf("%s%v", cacheTydataAgentWithdrawalWithdrawNoPrefix, data.WithdrawNo)
return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, agentWithdrawalRowsExpectAutoSet)
query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, agentWithdrawalRowsExpectAutoSet)
if session != nil {
return session.ExecCtx(ctx, query, data.AgentId, data.WithdrawNo, data.Amount, data.ActualAmount, data.TaxAmount, data.Status, data.PayeeAccount, data.Remark, data.DeleteTime, data.DelState, data.Version)
return session.ExecCtx(ctx, query, data.AgentId, data.WithdrawType, data.WithdrawNo, data.Amount, data.ActualAmount, data.TaxAmount, data.Status, data.PayeeAccount, data.BankCardNo, data.BankName, data.PayeeName, data.Remark, data.DeleteTime, data.DelState, data.Version)
}
return conn.ExecCtx(ctx, query, data.AgentId, data.WithdrawNo, data.Amount, data.ActualAmount, data.TaxAmount, data.Status, data.PayeeAccount, data.Remark, data.DeleteTime, data.DelState, data.Version)
}, hmAgentWithdrawalIdKey, hmAgentWithdrawalWithdrawNoKey)
return conn.ExecCtx(ctx, query, data.AgentId, data.WithdrawType, data.WithdrawNo, data.Amount, data.ActualAmount, data.TaxAmount, data.Status, data.PayeeAccount, data.BankCardNo, data.BankName, data.PayeeName, data.Remark, data.DeleteTime, data.DelState, data.Version)
}, tydataAgentWithdrawalIdKey, tydataAgentWithdrawalWithdrawNoKey)
}
func (m *defaultAgentWithdrawalModel) FindOne(ctx context.Context, id int64) (*AgentWithdrawal, error) {
hmAgentWithdrawalIdKey := fmt.Sprintf("%s%v", cacheHmAgentWithdrawalIdPrefix, id)
tydataAgentWithdrawalIdKey := fmt.Sprintf("%s%v", cacheTydataAgentWithdrawalIdPrefix, id)
var resp AgentWithdrawal
err := m.QueryRowCtx(ctx, &resp, hmAgentWithdrawalIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error {
err := m.QueryRowCtx(ctx, &resp, tydataAgentWithdrawalIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error {
query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", agentWithdrawalRows, m.table)
return conn.QueryRowCtx(ctx, v, query, id, globalkey.DelStateNo)
})
@@ -112,9 +115,9 @@ func (m *defaultAgentWithdrawalModel) FindOne(ctx context.Context, id int64) (*A
}
func (m *defaultAgentWithdrawalModel) FindOneByWithdrawNo(ctx context.Context, withdrawNo string) (*AgentWithdrawal, error) {
hmAgentWithdrawalWithdrawNoKey := fmt.Sprintf("%s%v", cacheHmAgentWithdrawalWithdrawNoPrefix, withdrawNo)
tydataAgentWithdrawalWithdrawNoKey := fmt.Sprintf("%s%v", cacheTydataAgentWithdrawalWithdrawNoPrefix, withdrawNo)
var resp AgentWithdrawal
err := m.QueryRowIndexCtx(ctx, &resp, hmAgentWithdrawalWithdrawNoKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
err := m.QueryRowIndexCtx(ctx, &resp, tydataAgentWithdrawalWithdrawNoKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
query := fmt.Sprintf("select %s from %s where `withdraw_no` = ? and del_state = ? limit 1", agentWithdrawalRows, m.table)
if err := conn.QueryRowCtx(ctx, &resp, query, withdrawNo, globalkey.DelStateNo); err != nil {
return nil, err
@@ -136,15 +139,15 @@ func (m *defaultAgentWithdrawalModel) Update(ctx context.Context, session sqlx.S
if err != nil {
return nil, err
}
hmAgentWithdrawalIdKey := fmt.Sprintf("%s%v", cacheHmAgentWithdrawalIdPrefix, data.Id)
hmAgentWithdrawalWithdrawNoKey := fmt.Sprintf("%s%v", cacheHmAgentWithdrawalWithdrawNoPrefix, data.WithdrawNo)
tydataAgentWithdrawalIdKey := fmt.Sprintf("%s%v", cacheTydataAgentWithdrawalIdPrefix, data.Id)
tydataAgentWithdrawalWithdrawNoKey := fmt.Sprintf("%s%v", cacheTydataAgentWithdrawalWithdrawNoPrefix, data.WithdrawNo)
return m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, agentWithdrawalRowsWithPlaceHolder)
if session != nil {
return session.ExecCtx(ctx, query, newData.AgentId, newData.WithdrawNo, newData.Amount, newData.ActualAmount, newData.TaxAmount, newData.Status, newData.PayeeAccount, newData.Remark, newData.DeleteTime, newData.DelState, newData.Version, newData.Id)
return session.ExecCtx(ctx, query, newData.AgentId, newData.WithdrawType, newData.WithdrawNo, newData.Amount, newData.ActualAmount, newData.TaxAmount, newData.Status, newData.PayeeAccount, newData.BankCardNo, newData.BankName, newData.PayeeName, newData.Remark, newData.DeleteTime, newData.DelState, newData.Version, newData.Id)
}
return conn.ExecCtx(ctx, query, newData.AgentId, newData.WithdrawNo, newData.Amount, newData.ActualAmount, newData.TaxAmount, newData.Status, newData.PayeeAccount, newData.Remark, newData.DeleteTime, newData.DelState, newData.Version, newData.Id)
}, hmAgentWithdrawalIdKey, hmAgentWithdrawalWithdrawNoKey)
return conn.ExecCtx(ctx, query, newData.AgentId, newData.WithdrawType, newData.WithdrawNo, newData.Amount, newData.ActualAmount, newData.TaxAmount, newData.Status, newData.PayeeAccount, newData.BankCardNo, newData.BankName, newData.PayeeName, newData.Remark, newData.DeleteTime, newData.DelState, newData.Version, newData.Id)
}, tydataAgentWithdrawalIdKey, tydataAgentWithdrawalWithdrawNoKey)
}
func (m *defaultAgentWithdrawalModel) UpdateWithVersion(ctx context.Context, session sqlx.Session, newData *AgentWithdrawal) error {
@@ -159,15 +162,15 @@ func (m *defaultAgentWithdrawalModel) UpdateWithVersion(ctx context.Context, ses
if err != nil {
return err
}
hmAgentWithdrawalIdKey := fmt.Sprintf("%s%v", cacheHmAgentWithdrawalIdPrefix, data.Id)
hmAgentWithdrawalWithdrawNoKey := fmt.Sprintf("%s%v", cacheHmAgentWithdrawalWithdrawNoPrefix, data.WithdrawNo)
tydataAgentWithdrawalIdKey := fmt.Sprintf("%s%v", cacheTydataAgentWithdrawalIdPrefix, data.Id)
tydataAgentWithdrawalWithdrawNoKey := fmt.Sprintf("%s%v", cacheTydataAgentWithdrawalWithdrawNoPrefix, data.WithdrawNo)
sqlResult, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("update %s set %s where `id` = ? and version = ? ", m.table, agentWithdrawalRowsWithPlaceHolder)
if session != nil {
return session.ExecCtx(ctx, query, newData.AgentId, newData.WithdrawNo, newData.Amount, newData.ActualAmount, newData.TaxAmount, newData.Status, newData.PayeeAccount, newData.Remark, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion)
return session.ExecCtx(ctx, query, newData.AgentId, newData.WithdrawType, newData.WithdrawNo, newData.Amount, newData.ActualAmount, newData.TaxAmount, newData.Status, newData.PayeeAccount, newData.BankCardNo, newData.BankName, newData.PayeeName, newData.Remark, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion)
}
return conn.ExecCtx(ctx, query, newData.AgentId, newData.WithdrawNo, newData.Amount, newData.ActualAmount, newData.TaxAmount, newData.Status, newData.PayeeAccount, newData.Remark, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion)
}, hmAgentWithdrawalIdKey, hmAgentWithdrawalWithdrawNoKey)
return conn.ExecCtx(ctx, query, newData.AgentId, newData.WithdrawType, newData.WithdrawNo, newData.Amount, newData.ActualAmount, newData.TaxAmount, newData.Status, newData.PayeeAccount, newData.BankCardNo, newData.BankName, newData.PayeeName, newData.Remark, newData.DeleteTime, newData.DelState, newData.Version, newData.Id, oldVersion)
}, tydataAgentWithdrawalIdKey, tydataAgentWithdrawalWithdrawNoKey)
if err != nil {
return err
}
@@ -390,19 +393,19 @@ func (m *defaultAgentWithdrawalModel) Delete(ctx context.Context, session sqlx.S
return err
}
hmAgentWithdrawalIdKey := fmt.Sprintf("%s%v", cacheHmAgentWithdrawalIdPrefix, id)
hmAgentWithdrawalWithdrawNoKey := fmt.Sprintf("%s%v", cacheHmAgentWithdrawalWithdrawNoPrefix, data.WithdrawNo)
tydataAgentWithdrawalIdKey := fmt.Sprintf("%s%v", cacheTydataAgentWithdrawalIdPrefix, id)
tydataAgentWithdrawalWithdrawNoKey := fmt.Sprintf("%s%v", cacheTydataAgentWithdrawalWithdrawNoPrefix, data.WithdrawNo)
_, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("delete from %s where `id` = ?", m.table)
if session != nil {
return session.ExecCtx(ctx, query, id)
}
return conn.ExecCtx(ctx, query, id)
}, hmAgentWithdrawalIdKey, hmAgentWithdrawalWithdrawNoKey)
}, tydataAgentWithdrawalIdKey, tydataAgentWithdrawalWithdrawNoKey)
return err
}
func (m *defaultAgentWithdrawalModel) formatPrimary(primary interface{}) string {
return fmt.Sprintf("%s%v", cacheHmAgentWithdrawalIdPrefix, primary)
return fmt.Sprintf("%s%v", cacheTydataAgentWithdrawalIdPrefix, primary)
}
func (m *defaultAgentWithdrawalModel) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary interface{}) error {
query := fmt.Sprintf("select %s from %s where `id` = ? and del_state = ? limit 1", agentWithdrawalRows, m.table)