Compare commits

...

2 Commits

Author SHA1 Message Date
f545aee45e f 2026-04-22 17:49:20 +08:00
a2fad4a095 f 2026-04-20 16:58:33 +08:00
18 changed files with 318 additions and 151 deletions

View File

@@ -19,6 +19,12 @@ service main {
@handler getAppVersion
get /app/version returns (getAppVersionResp)
@doc(
summary: "获取APP全局配置"
)
@handler getAppConfig
get /app/config returns (GetAppConfigResp)
}
type (
@@ -35,3 +41,13 @@ type (
WgtUrl string `json:"wgtUrl"`
}
)
type (
AppQueryConfig {
RetentionDays int64 `json:"retention_days"` // 查询结果保留天数
}
GetAppConfigResp {
Query AppQueryConfig `json:"query"` // 查询相关配置
}
)

View File

@@ -50,7 +50,7 @@ Wxpay:
MchPublicKeyPath: "etc/merchant/wxpay/XXXXXXXXXXXX_XXXXXXXXXXXX_cert/pub_key.pem"
MchPlatformRAS: "xxxx"
NotifyUrl: "https://www.XXXXXXXXXXXXXX.com/api/v1/pay/wechat/callback"
RefundNotifyUrl: "https://www.XXXXXXXXXXXXXX.com/api/v1/wechat/refund_callback"
RefundNotifyUrl: "https://www.XXXXXXXXXXXXXX.com/api/v1/pay/wechat/refund_callback"
Applepay:
ProductionVerifyURL: "https://api.storekit.itunes.apple.com/inApps/v1/transactions/receipt"
SandboxVerifyURL: "https://api.storekit-sandbox.itunes.apple.com/inApps/v1/transactions/receipt"

View File

@@ -0,0 +1,17 @@
package app
import (
"net/http"
"bdrp-server/app/main/api/internal/logic/app"
"bdrp-server/app/main/api/internal/svc"
"bdrp-server/common/result"
)
func GetAppConfigHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := app.NewGetAppConfigLogic(r.Context(), svcCtx)
resp, err := l.GetAppConfig()
result.HttpResult(r, w, resp, err)
}
}

View File

@@ -899,6 +899,12 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
server.AddRoutes(
[]rest.Route{
{
// 获取APP全局配置
Method: http.MethodGet,
Path: "/app/config",
Handler: app.GetAppConfigHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/app/version",

View File

@@ -57,7 +57,7 @@ func (l *AdminBatchUnfreezeAgentCommissionLogic) AdminBatchUnfreezeAgentCommissi
// 计算总金额
var totalAmount float64
for _, commission := range commissions {
totalAmount += commission.Amount
totalAmount = roundMoney(totalAmount + commission.Amount)
}
// 开始事务
@@ -96,16 +96,16 @@ func (l *AdminBatchUnfreezeAgentCommissionLogic) AdminBatchUnfreezeAgentCommissi
// 累加到对应代理商的钱包数据
if wallet, exists := agentWalletMap[commission.AgentId]; exists {
wallet.Balance += commission.Amount
wallet.FrozenBalance -= commission.Amount
wallet.Balance = roundMoney(wallet.Balance + commission.Amount)
wallet.FrozenBalance = roundMoney(wallet.FrozenBalance - commission.Amount)
} else {
// 查询该代理商的钱包
wallet, err := l.svcCtx.AgentWalletModel.FindOneByAgentId(ctx, commission.AgentId)
if err != nil {
return err
}
wallet.Balance += commission.Amount
wallet.FrozenBalance -= commission.Amount
wallet.Balance = roundMoney(wallet.Balance + commission.Amount)
wallet.FrozenBalance = roundMoney(wallet.FrozenBalance - commission.Amount)
agentWalletMap[commission.AgentId] = wallet
}
}

View File

@@ -1,11 +1,11 @@
package admin_agent
import (
"bdrp-server/app/main/model"
"bdrp-server/common/xerr"
"context"
"database/sql"
"time"
"bdrp-server/app/main/model"
"bdrp-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/stores/sqlx"
@@ -164,7 +164,7 @@ func (l *AdminReviewBankCardWithdrawalLogic) approveBankCardWithdrawal(ctx conte
frozenBalanceBefore := wallet.FrozenBalance
// 更新钱包(减少冻结余额)
wallet.FrozenBalance -= record.Amount
wallet.FrozenBalance = roundMoney(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)
}
@@ -175,14 +175,14 @@ func (l *AdminReviewBankCardWithdrawalLogic) approveBankCardWithdrawal(ctx conte
session,
wallet.AgentId,
model.WalletTransactionTypeWithdraw,
-record.Amount, // 变动金额(负数表示减少)
wallet.Balance, // 变动前余额(不变)
wallet.Balance, // 变动后余额(不变)
frozenBalanceBefore, // 变动前冻结余额
wallet.FrozenBalance, // 变动后冻结余额
record.WithdrawNo, // 关联交易ID
0, // 关联用户ID
"提现审核通过", // 备注
roundMoney(-record.Amount), // 变动金额(负数表示减少)
roundMoney(wallet.Balance), // 变动前余额(不变)
roundMoney(wallet.Balance), // 变动后余额(不变)
roundMoney(frozenBalanceBefore), // 变动前冻结余额
roundMoney(wallet.FrozenBalance), // 变动后冻结余额
record.WithdrawNo, // 关联交易ID
0, // 关联用户ID
"提现审核通过", // 备注
)
if err != nil {
return err
@@ -234,8 +234,8 @@ func (l *AdminReviewBankCardWithdrawalLogic) rejectWithdrawal(ctx context.Contex
frozenBalanceBefore := wallet.FrozenBalance
// 更新钱包(余额增加,冻结余额减少)
wallet.Balance += record.Amount
wallet.FrozenBalance -= record.Amount
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 errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新钱包失败: %v", err)
}
@@ -246,14 +246,14 @@ func (l *AdminReviewBankCardWithdrawalLogic) rejectWithdrawal(ctx context.Contex
session,
wallet.AgentId,
model.WalletTransactionTypeUnfreeze,
record.Amount, // 变动金额(正数表示增加)
balanceBefore, // 变动前余额
wallet.Balance, // 变动后余额
frozenBalanceBefore, // 变动前冻结余额
wallet.FrozenBalance, // 变动后冻结余额
record.WithdrawNo, // 关联交易ID
0, // 关联用户ID
"提现拒绝,解冻资金", // 备注
roundMoney(record.Amount), // 变动金额(正数表示增加)
roundMoney(balanceBefore), // 变动前余额
roundMoney(wallet.Balance), // 变动后余额
roundMoney(frozenBalanceBefore), // 变动前冻结余额
roundMoney(wallet.FrozenBalance), // 变动后冻结余额
record.WithdrawNo, // 关联交易ID
0, // 关联用户ID
"提现拒绝,解冻资金", // 备注
)
if err != nil {
return err
@@ -295,7 +295,7 @@ func (l *AdminReviewBankCardWithdrawalLogic) completeWithdrawalSuccess(ctx conte
frozenBalanceBefore := wallet.FrozenBalance
// 更新钱包(减少冻结余额)
wallet.FrozenBalance -= record.Amount
wallet.FrozenBalance = roundMoney(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)
}
@@ -306,14 +306,14 @@ func (l *AdminReviewBankCardWithdrawalLogic) completeWithdrawalSuccess(ctx conte
session,
wallet.AgentId,
model.WalletTransactionTypeWithdraw,
-record.Amount, // 变动金额(负数表示减少)
wallet.Balance, // 变动前余额(不变)
wallet.Balance, // 变动后余额(不变)
frozenBalanceBefore, // 变动前冻结余额
wallet.FrozenBalance, // 变动后冻结余额
record.WithdrawNo, // 关联交易ID
0, // 关联用户ID
"提现成功", // 备注
roundMoney(-record.Amount), // 变动金额(负数表示减少)
roundMoney(wallet.Balance), // 变动前余额(不变)
roundMoney(wallet.Balance), // 变动后余额(不变)
roundMoney(frozenBalanceBefore), // 变动前冻结余额
roundMoney(wallet.FrozenBalance), // 变动后冻结余额
record.WithdrawNo, // 关联交易ID
0, // 关联用户ID
"提现成功", // 备注
)
if err != nil {
return err
@@ -365,8 +365,8 @@ func (l *AdminReviewBankCardWithdrawalLogic) completeWithdrawalFailure(ctx conte
frozenBalanceBefore := wallet.FrozenBalance
// 更新钱包(余额增加,冻结余额减少)
wallet.Balance += record.Amount
wallet.FrozenBalance -= record.Amount
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 errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新钱包失败: %v", err)
}
@@ -377,14 +377,14 @@ func (l *AdminReviewBankCardWithdrawalLogic) completeWithdrawalFailure(ctx conte
session,
wallet.AgentId,
model.WalletTransactionTypeUnfreeze,
record.Amount, // 变动金额(正数表示增加)
balanceBefore, // 变动前余额
wallet.Balance, // 变动后余额
frozenBalanceBefore, // 变动前冻结余额
wallet.FrozenBalance, // 变动后冻结余额
record.WithdrawNo, // 关联交易ID
0, // 关联用户ID
"提现失败,解冻资金", // 备注
roundMoney(record.Amount), // 变动金额(正数表示增加)
roundMoney(balanceBefore), // 变动前余额
roundMoney(wallet.Balance), // 变动后余额
roundMoney(frozenBalanceBefore), // 变动前冻结余额
roundMoney(wallet.FrozenBalance), // 变动后冻结余额
record.WithdrawNo, // 关联交易ID
0, // 关联用户ID
"提现失败,解冻资金", // 备注
)
if err != nil {
return err

View File

@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"math"
"bdrp-server/app/main/api/internal/svc"
"bdrp-server/app/main/api/internal/types"
@@ -14,6 +15,10 @@ import (
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
func roundMoney(v float64) float64 {
return math.Round(v*100) / 100
}
type AdminUpdateAgentWalletBalanceLogic struct {
logx.Logger
ctx context.Context
@@ -47,7 +52,8 @@ func (l *AdminUpdateAgentWalletBalanceLogic) AdminUpdateAgentWalletBalance(req *
}
// 计算新余额
newBalance := wallet.Balance + req.Amount
adjustAmount := roundMoney(req.Amount)
newBalance := roundMoney(wallet.Balance + adjustAmount)
// 校验余额不能为负数
if newBalance < 0 {
@@ -68,20 +74,20 @@ func (l *AdminUpdateAgentWalletBalanceLogic) AdminUpdateAgentWalletBalance(req *
}
// 创建钱包交易流水记录(手动调整)
remark := fmt.Sprintf("管理员手动调整余额,金额: %.2f", req.Amount)
remark := fmt.Sprintf("管理员手动调整余额,金额: %.2f", adjustAmount)
transErr := l.svcCtx.AgentService.CreateWalletTransaction(
transCtx,
session,
req.AgentId,
model.WalletTransactionTypeAdjust,
req.Amount, // 变动金额(正数表示增加,负数表示减少)
balanceBefore, // 变动前余额
wallet.Balance, // 变动后余额
frozenBalanceBefore, // 变动前冻结余额
wallet.FrozenBalance, // 变动后冻结余额(保持不变)
"", // 关联交易ID无关联
0, // 关联用户ID无关联
remark, // 备注
adjustAmount, // 变动金额(正数表示增加,负数表示减少)
roundMoney(balanceBefore), // 变动前余额
roundMoney(wallet.Balance), // 变动后余额
roundMoney(frozenBalanceBefore), // 变动前冻结余额
roundMoney(wallet.FrozenBalance), // 变动后冻结余额(保持不变)
"", // 关联交易ID无关联
0, // 关联用户ID无关联
remark, // 备注
)
if transErr != nil {
l.Logger.Errorf("创建代理钱包流水记录失败: %+v", transErr)
@@ -89,7 +95,7 @@ func (l *AdminUpdateAgentWalletBalanceLogic) AdminUpdateAgentWalletBalance(req *
}
l.Logger.Infof("代理钱包余额变更 - AgentId: %d, 原余额: %.2f, 变更金额: %.2f, 新余额: %.2f",
req.AgentId, balanceBefore, req.Amount, wallet.Balance)
req.AgentId, roundMoney(balanceBefore), adjustAmount, roundMoney(wallet.Balance))
return nil
})
@@ -101,7 +107,7 @@ func (l *AdminUpdateAgentWalletBalanceLogic) AdminUpdateAgentWalletBalance(req *
resp = &types.AdminUpdateAgentWalletBalanceResp{
Success: true,
Balance: newBalance,
Balance: roundMoney(newBalance),
}
return
}

View File

@@ -1,15 +1,17 @@
package admin_product
import (
"context"
"bdrp-server/app/main/api/internal/svc"
"bdrp-server/app/main/api/internal/types"
"bdrp-server/app/main/model"
"bdrp-server/common/xerr"
"context"
"database/sql"
"math"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
type AdminCreateProductLogic struct {
@@ -27,7 +29,7 @@ func NewAdminCreateProductLogic(ctx context.Context, svcCtx *svc.ServiceContext)
}
func (l *AdminCreateProductLogic) AdminCreateProduct(req *types.AdminCreateProductReq) (resp *types.AdminCreateProductResp, err error) {
// 1. 数据转换
// 1. 构建产品数据
data := &model.Product{
ProductName: req.ProductName,
ProductEn: req.ProductEn,
@@ -37,14 +39,37 @@ func (l *AdminCreateProductLogic) AdminCreateProduct(req *types.AdminCreateProdu
SellPrice: req.SellPrice,
}
// 2. 数据库操作
result, err := l.svcCtx.ProductModel.Insert(l.ctx, nil, data)
// 2. 开启事务,确保产品与代理产品配置同步创建
var id int64
err = l.svcCtx.ProductModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
result, txErr := l.svcCtx.ProductModel.Insert(ctx, session, data)
if txErr != nil {
return txErr
}
productID, txErr := result.LastInsertId()
if txErr != nil {
return txErr
}
id = productID
priceRangeMax := math.Max(req.CostPrice, req.SellPrice)
agentConfig := &model.AgentProductConfig{
ProductId: productID,
CostPrice: req.CostPrice,
PriceRangeMin: req.CostPrice,
PriceRangeMax: priceRangeMax,
PricingStandard: priceRangeMax,
OverpricingRatio: 0.10,
}
_, txErr = l.svcCtx.AgentProductConfigModel.Insert(ctx, session, agentConfig)
return txErr
})
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"创建产品失败, err: %v, req: %+v", err, req)
"创建产品及代理产品配置失败, err: %v, req: %+v", err, req)
}
// 3. 返回结果
id, _ := result.LastInsertId()
return &types.AdminCreateProductResp{Id: id}, nil
}

View File

@@ -1,13 +1,15 @@
package admin_product
import (
"context"
"bdrp-server/app/main/api/internal/svc"
"bdrp-server/app/main/api/internal/types"
"bdrp-server/app/main/model"
"bdrp-server/common/xerr"
"context"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
type AdminDeleteProductLogic struct {
@@ -25,15 +27,27 @@ func NewAdminDeleteProductLogic(ctx context.Context, svcCtx *svc.ServiceContext)
}
func (l *AdminDeleteProductLogic) AdminDeleteProduct(req *types.AdminDeleteProductReq) (resp *types.AdminDeleteProductResp, err error) {
// 1. 查询记录是否存在
// 1. 查询产品是否存在
record, err := l.svcCtx.ProductModel.FindOne(l.ctx, req.Id)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"查找产品失败, err: %v, id: %d", err, req.Id)
}
// 2. 执行软删除
err = l.svcCtx.ProductModel.DeleteSoft(l.ctx, nil, record)
// 2. 事务内执行物理删除,保持 product 与 agent_product_config 同步
err = l.svcCtx.ProductModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
agentCfg, findErr := l.svcCtx.AgentProductConfigModel.FindOneByProductId(ctx, req.Id)
if findErr != nil && !errors.Is(findErr, model.ErrNotFound) {
return findErr
}
if findErr == nil {
if deleteCfgErr := l.svcCtx.AgentProductConfigModel.Delete(ctx, session, agentCfg.Id); deleteCfgErr != nil {
return deleteCfgErr
}
}
return l.svcCtx.ProductModel.Delete(ctx, session, record.Id)
})
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR),
"删除产品失败, err: %v, id: %d", err, req.Id)

View File

@@ -1,14 +1,14 @@
package agent
import (
"context"
"database/sql"
"fmt"
"time"
"bdrp-server/app/main/model"
"bdrp-server/common/ctxdata"
"bdrp-server/common/xerr"
"bdrp-server/pkg/lzkit/lzUtils"
"context"
"database/sql"
"fmt"
"time"
"github.com/cenkalti/backoff/v4"
"github.com/pkg/errors"
@@ -56,6 +56,8 @@ func (l *AgentWithdrawalLogic) AgentWithdrawal(req *types.WithdrawalReq) (*types
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)
@@ -89,7 +91,7 @@ func (l *AgentWithdrawalLogic) AgentWithdrawal(req *types.WithdrawalReq) (*types
}
// 校验可提现金额
if req.Amount > agentWallet.Balance {
if withdrawAmount > roundMoney(agentWallet.Balance) {
return errors.Wrapf(xerr.NewErrMsg("您可提现的余额不足"), "获取用户ID失败")
}
@@ -97,7 +99,7 @@ func (l *AgentWithdrawalLogic) AgentWithdrawal(req *types.WithdrawalReq) (*types
outBizNo = "W_" + l.svcCtx.AlipayService.GenerateOutTradeNo()
// 冻结资金(事务内操作)
if err = l.freezeFunds(session, agentWallet, req.Amount); err != nil {
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()))
@@ -111,14 +113,14 @@ func (l *AgentWithdrawalLogic) AgentWithdrawal(req *types.WithdrawalReq) (*types
)
// 统一扣税逻辑所有提现都按6%收取税收
exemptionAmount = 0 // 免税金额 = 0
TaxStatus = model.TaxStatusPending // 扣税状态 = 待扣税
taxDeductionPart = req.Amount // 应税金额 = 提现金额
taxAmount = taxDeductionPart * taxRate // 应缴税费 = 应税金额 * 税率
finalWithdrawAmount = req.Amount - taxAmount // 实际到账金额 = 提现金额 - 应缴税费
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, req.Amount, finalWithdrawAmount, taxAmount, outBizNo)
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)
}
@@ -127,7 +129,7 @@ func (l *AgentWithdrawalLogic) AgentWithdrawal(req *types.WithdrawalReq) (*types
AgentId: agentModel.Id,
YearMonth: yearMonth,
WithdrawalId: withdrawalID,
WithdrawalAmount: req.Amount,
WithdrawalAmount: withdrawAmount,
ExemptionAmount: exemptionAmount,
TaxableAmount: taxDeductionPart,
TaxRate: taxRate,
@@ -153,7 +155,7 @@ func (l *AgentWithdrawalLogic) AgentWithdrawal(req *types.WithdrawalReq) (*types
withdrawRes.Status = WithdrawStatusProcessing
withdrawRes.FailMsg = ""
l.Logger.Infof("支付宝提现申请成功 outBizNo:%s agentId:%d amount:%f", outBizNo, agentID, req.Amount)
l.Logger.Infof("支付宝提现申请成功 outBizNo:%s agentId:%d amount:%f", outBizNo, agentID, withdrawAmount)
return withdrawRes, nil
}
@@ -193,6 +195,7 @@ func (l *AgentWithdrawalLogic) mapAlipayError(code string) string {
if msg, ok := errorMapping[code]; ok {
return msg
}
l.Logger.Infof("未匹配到支付宝错误码 code:%s", code)
return "系统错误,请联系客服"
}
@@ -204,9 +207,9 @@ func (l *AgentWithdrawalLogic) createWithdrawalRecord(session sqlx.Session, agen
WithdrawNo: outBizNo,
PayeeAccount: payeeAccount,
PayeeName: sql.NullString{String: payeeName, Valid: true}, // 设置收款人姓名
Amount: amount,
ActualAmount: finalWithdrawAmount,
TaxAmount: taxAmount,
Amount: roundMoney(amount),
ActualAmount: roundMoney(finalWithdrawAmount),
TaxAmount: roundMoney(taxAmount),
Status: StatusProcessing,
}
@@ -219,8 +222,8 @@ func (l *AgentWithdrawalLogic) createWithdrawalRecord(session sqlx.Session, agen
// 冻结资金(事务内操作)
func (l *AgentWithdrawalLogic) freezeFunds(session sqlx.Session, wallet *model.AgentWallet, amount float64) error {
wallet.Balance -= amount
wallet.FrozenBalance += amount
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
@@ -296,8 +299,8 @@ func (l *AgentWithdrawalLogic) updateWithdrawalStatus(outBizNo string, status in
return err
}
wallet.Balance += record.Amount
wallet.FrozenBalance -= record.Amount
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
}
@@ -320,7 +323,7 @@ func (l *AgentWithdrawalLogic) updateWithdrawalStatus(outBizNo string, status in
return err
}
wallet.FrozenBalance -= record.Amount
wallet.FrozenBalance = roundMoney(wallet.FrozenBalance - record.Amount)
if err := l.svcCtx.AgentWalletModel.UpdateWithVersion(ctx, session, wallet); err != nil {
return err
}

View File

@@ -1,13 +1,13 @@
package agent
import (
"bdrp-server/app/main/model"
"bdrp-server/common/ctxdata"
"bdrp-server/common/xerr"
"context"
"database/sql"
"regexp"
"time"
"bdrp-server/app/main/model"
"bdrp-server/common/ctxdata"
"bdrp-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/stores/sqlx"
@@ -57,6 +57,8 @@ func (l *BankCardWithdrawalLogic) BankCardWithdrawal(req *types.BankCardWithdraw
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)
@@ -90,12 +92,12 @@ func (l *BankCardWithdrawalLogic) BankCardWithdrawal(req *types.BankCardWithdraw
}
// 校验可提现金额
if req.Amount > agentWallet.Balance {
if withdrawAmount > roundMoney(agentWallet.Balance) {
return errors.Wrapf(xerr.NewErrMsg("您可提现的余额不足"), "余额不足")
}
// 最低提现金额验证
if req.Amount < 50 {
if withdrawAmount < 50 {
return errors.Wrapf(xerr.NewErrMsg("提现金额不能低于50元"), "金额验证失败")
}
@@ -103,7 +105,7 @@ func (l *BankCardWithdrawalLogic) BankCardWithdrawal(req *types.BankCardWithdraw
outBizNo = "BC_" + l.svcCtx.AlipayService.GenerateOutTradeNo()
// 冻结资金(事务内操作)
if err = l.freezeFunds(session, agentWallet, req.Amount); err != nil {
if err = l.freezeFunds(session, agentWallet, withdrawAmount); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "资金冻结失败: %v", err)
}
@@ -118,14 +120,14 @@ func (l *BankCardWithdrawalLogic) BankCardWithdrawal(req *types.BankCardWithdraw
)
// 统一扣税逻辑所有提现都按6%收取税收
exemptionAmount = 0 // 免税金额 = 0
TaxStatus = model.TaxStatusPending // 扣税状态 = 待扣税
taxDeductionPart = req.Amount // 应税金额 = 提现金额
taxAmount = taxDeductionPart * taxRate // 应缴税费 = 应税金额 * 税率
finalWithdrawAmount = req.Amount - taxAmount // 实际到账金额 = 提现金额 - 应缴税费
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, req.Amount, finalWithdrawAmount, taxAmount, outBizNo)
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)
}
@@ -135,7 +137,7 @@ func (l *BankCardWithdrawalLogic) BankCardWithdrawal(req *types.BankCardWithdraw
AgentId: agentModel.Id,
YearMonth: yearMonth,
WithdrawalId: withdrawalID,
WithdrawalAmount: req.Amount,
WithdrawalAmount: withdrawAmount,
ExemptionAmount: exemptionAmount,
TaxableAmount: taxDeductionPart,
TaxRate: taxRate,
@@ -160,7 +162,7 @@ func (l *BankCardWithdrawalLogic) BankCardWithdrawal(req *types.BankCardWithdraw
withdrawRes.Status = WithdrawStatusProcessing
withdrawRes.FailMsg = ""
l.Logger.Infof("银行卡提现申请成功 outBizNo:%s agentId:%d amount:%f", outBizNo, agentID, req.Amount)
l.Logger.Infof("银行卡提现申请成功 outBizNo:%s agentId:%d amount:%f", outBizNo, agentID, withdrawAmount)
return withdrawRes, nil
}
@@ -171,9 +173,9 @@ func (l *BankCardWithdrawalLogic) createBankCardWithdrawalRecord(session sqlx.Se
WithdrawType: WithdrawTypeBankCard, // 银行卡提现
WithdrawNo: outBizNo,
PayeeAccount: bankCardNo, // 银行卡号存储在PayeeAccount字段
Amount: amount,
ActualAmount: finalWithdrawAmount,
TaxAmount: taxAmount,
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},
@@ -194,8 +196,8 @@ func (l *BankCardWithdrawalLogic) freezeFunds(session sqlx.Session, wallet *mode
frozenBalanceBefore := wallet.FrozenBalance
// 更新钱包余额
wallet.Balance -= amount
wallet.FrozenBalance += amount
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
@@ -207,7 +209,7 @@ func (l *BankCardWithdrawalLogic) freezeFunds(session sqlx.Session, wallet *mode
session,
wallet.AgentId,
model.WalletTransactionTypeFreeze,
amount, // 变动金额
roundMoney(amount), // 变动金额
balanceBefore, // 变动前余额
wallet.Balance, // 变动后余额
frozenBalanceBefore, // 变动前冻结余额

View File

@@ -5,6 +5,7 @@ import (
"bdrp-server/app/main/model"
"bdrp-server/common/ctxdata"
"bdrp-server/common/xerr"
"math"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/mr"
@@ -32,6 +33,10 @@ func NewGetAgentProductConfigLogic(ctx context.Context, svcCtx *svc.ServiceConte
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 {
@@ -81,10 +86,10 @@ func (l *GetAgentProductConfigLogic) GetAgentProductConfig() (resp *types.AgentP
return
}
agentProductConfig.ProductID = config.ProductId
agentProductConfig.CostPrice = agentProductConfigModel.CostPrice
agentProductConfig.PriceRangeMin = agentProductConfigModel.PriceRangeMin
agentProductConfig.PriceRangeMax = agentProductConfigModel.PriceRangeMax
agentProductConfig.PPricingStandard = agentProductConfigModel.PricingStandard
agentProductConfig.CostPrice = roundMoney(agentProductConfigModel.CostPrice)
agentProductConfig.PriceRangeMin = roundMoney(agentProductConfigModel.PriceRangeMin)
agentProductConfig.PriceRangeMax = roundMoney(agentProductConfigModel.PriceRangeMax)
agentProductConfig.PPricingStandard = roundMoney(agentProductConfigModel.PricingStandard)
agentProductConfig.POverpricingRatio = agentProductConfigModel.OverpricingRatio
// 看推广人是否有上级,上级是否有这个配置权限,上级是否有相关配置
@@ -119,10 +124,10 @@ func (l *GetAgentProductConfigLogic) GetAgentProductConfig() (resp *types.AgentP
cancel(membershipConfigErr)
return
}
agentProductConfig.CostPrice += membershipUserConfigModel.PriceIncreaseAmount
agentProductConfig.PriceRangeMin += membershipUserConfigModel.PriceIncreaseAmount
agentProductConfig.APricingStandard = membershipUserConfigModel.PriceRangeFrom
agentProductConfig.APricingEnd = membershipUserConfigModel.PriceRangeTo
agentProductConfig.CostPrice = roundMoney(agentProductConfig.CostPrice + membershipUserConfigModel.PriceIncreaseAmount)
agentProductConfig.PriceRangeMin = roundMoney(agentProductConfig.PriceRangeMin + membershipUserConfigModel.PriceIncreaseAmount)
agentProductConfig.APricingStandard = roundMoney(membershipUserConfigModel.PriceRangeFrom)
agentProductConfig.APricingEnd = roundMoney(membershipUserConfigModel.PriceRangeTo)
agentProductConfig.AOverpricingRatio = membershipUserConfigModel.PriceRatio
}
}

View File

@@ -0,0 +1,51 @@
package app
import (
"context"
"strconv"
"bdrp-server/app/main/api/internal/svc"
"bdrp-server/app/main/api/internal/types"
"bdrp-server/app/main/model"
"github.com/zeromicro/go-zero/core/logx"
)
type GetAppConfigLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetAppConfigLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetAppConfigLogic {
return &GetAppConfigLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetAppConfigLogic) GetAppConfig() (resp *types.GetAppConfigResp, err error) {
const defaultRetentionDays int64 = 30
retentionDays := defaultRetentionDays
retentionConfig, findErr := l.svcCtx.QueryCleanupConfigModel.FindOneByConfigKey(l.ctx, "retention_days")
if findErr != nil {
if findErr != model.ErrNotFound {
l.Errorf("获取 APP 配置 retention_days 失败: %+v", findErr)
}
} else if retentionConfig.Status == 1 {
parsedDays, parseErr := strconv.ParseInt(retentionConfig.ConfigValue, 10, 64)
if parseErr != nil || parsedDays <= 0 {
l.Errorf("解析 APP 配置 retention_days 失败, value=%s, err=%+v", retentionConfig.ConfigValue, parseErr)
} else {
retentionDays = parsedDays
}
}
return &types.GetAppConfigResp{
Query: types.AppQueryConfig{
RetentionDays: retentionDays,
},
}, nil
}

View File

@@ -1,14 +1,15 @@
package pay
import (
"context"
"database/sql"
"net/http"
"strings"
"time"
"bdrp-server/app/main/api/internal/svc"
"bdrp-server/app/main/model"
"bdrp-server/common/globalkey"
"context"
"database/sql"
"math"
"net/http"
"strings"
"time"
"github.com/Masterminds/squirrel"
"github.com/pkg/errors"
@@ -17,10 +18,15 @@ import (
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
func roundMoney(v float64) float64 {
return math.Round(v*100) / 100
}
// HandleCommissionAndWalletDeduction 处理退款后的佣金状态更新和钱包金额扣除
// refundAmount 为本次实际退款金额(单位:元),从代理侧总共需要承担的金额
// 该函数会优先冲减当前订单相关的佣金(基于 RefundedAmount不足部分再从钱包余额/冻结余额中扣除
func HandleCommissionAndWalletDeduction(ctx context.Context, svcCtx *svc.ServiceContext, session sqlx.Session, order *model.Order, refundAmount float64) error {
refundAmount = roundMoney(refundAmount)
if refundAmount <= 0 {
return nil
}
@@ -52,7 +58,7 @@ func HandleCommissionAndWalletDeduction(ctx context.Context, svcCtx *svc.Service
// 1. 先在佣金记录上做冲减:增加 RefundedAmount必要时将状态置为已退款
for _, commission := range commissions {
available := commission.Amount - commission.RefundedAmount
available := roundMoney(commission.Amount - commission.RefundedAmount)
if available <= 0 {
continue
}
@@ -66,11 +72,12 @@ func HandleCommissionAndWalletDeduction(ctx context.Context, svcCtx *svc.Service
if currentRefund > remainRefundAmount {
currentRefund = remainRefundAmount
}
currentRefund = roundMoney(currentRefund)
// 更新佣金的已退款金额
commission.RefundedAmount += currentRefund
commission.RefundedAmount = roundMoney(commission.RefundedAmount + currentRefund)
// 如果这条佣金已经被完全冲减,则标记为已退款
if commission.RefundedAmount >= commission.Amount {
if commission.RefundedAmount >= roundMoney(commission.Amount) {
commission.Status = 2
}
@@ -92,9 +99,9 @@ func HandleCommissionAndWalletDeduction(ctx context.Context, svcCtx *svc.Service
wa = &walletAdjust{agentId: commission.AgentId}
walletAdjustMap[commission.AgentId] = wa
}
wa.amount += currentRefund
wa.amount = roundMoney(wa.amount + currentRefund)
remainRefundAmount -= currentRefund
remainRefundAmount = roundMoney(remainRefundAmount - currentRefund)
}
// 2. 再按代理维度,从钱包(冻结余额/可用余额)中扣除对应金额
@@ -115,19 +122,19 @@ func HandleCommissionAndWalletDeduction(ctx context.Context, svcCtx *svc.Service
frozenBalanceBefore := wallet.FrozenBalance
// 优先从冻结余额中扣除(与原先“冻结佣金优先使用冻结余额”的设计一致)
deduct := wa.amount
deduct := roundMoney(wa.amount)
if wallet.FrozenBalance >= deduct {
wallet.FrozenBalance -= deduct
wallet.FrozenBalance = roundMoney(wallet.FrozenBalance - deduct)
} else {
remaining := deduct - wallet.FrozenBalance
remaining := roundMoney(deduct - wallet.FrozenBalance)
wallet.FrozenBalance = 0
// 可用余额可以为负数,由业务承担风险
wallet.Balance -= remaining
wallet.Balance = roundMoney(wallet.Balance - remaining)
}
// 变动后余额和冻结余额
balanceAfter := wallet.Balance
frozenBalanceAfter := wallet.FrozenBalance
balanceAfter := roundMoney(wallet.Balance)
frozenBalanceAfter := roundMoney(wallet.FrozenBalance)
// 更新钱包
var updateWalletErr error
if session != nil {
@@ -145,7 +152,7 @@ func HandleCommissionAndWalletDeduction(ctx context.Context, svcCtx *svc.Service
session,
wa.agentId,
model.WalletTransactionTypeRefund,
-wa.amount*-1, // 钱包流水金额为负数
roundMoney(-wa.amount*-1), // 钱包流水金额为负数
balanceBefore,
balanceAfter,
frozenBalanceBefore,
@@ -348,7 +355,7 @@ func (l *WechatPayRefundCallbackLogic) WechatPayRefundCallback(w http.ResponseWr
var refundAmountYuan float64
if notification.Amount != nil && notification.Amount.Refund != nil {
// 微信退款金额单位为分,这里转换为元
refundAmountYuan = float64(*notification.Amount.Refund) / 100.0
refundAmountYuan = roundMoney(float64(*notification.Amount.Refund) / 100.0)
}
// 4. 根据订单号前缀处理不同类型的订单

View File

@@ -1,11 +1,11 @@
package service
import (
"context"
"crypto/rand"
"bdrp-server/app/main/api/internal/config"
"bdrp-server/app/main/model"
"bdrp-server/pkg/lzkit/lzUtils"
"context"
"crypto/rand"
"encoding/hex"
"fmt"
"net/http"
@@ -217,12 +217,19 @@ func (a *AliPayService) AliTransfer(
// 构造转账请求
req := alipay.FundTransUniTransfer{
OutBizNo: outBizNo,
TransAmount: lzUtils.ToAlipayAmount(amount), // 金额格式转换
ProductCode: "TRANS_ACCOUNT_NO_PWD", // 单笔无密转账到支付宝账户
BizScene: "DIRECT_TRANSFER", // 单笔转账
OrderTitle: "账户提现", // 转账标题
Remark: remark,
OutBizNo: outBizNo,
TransAmount: lzUtils.ToAlipayAmount(amount), // 金额格式转换
ProductCode: "TRANS_ACCOUNT_NO_PWD", // 单笔无密转账到支付宝账户
BizScene: "DIRECT_TRANSFER", // 单笔转账
OrderTitle: "账户提现", // 转账标题
Remark: remark,
TransferSceneName: "业务结算",
TransferSceneReportInfo: []*alipay.TransferSceneReportInfo{
{
InfoType: "结算款项名称",
InfoContent: "向代理商转账支付代理费用",
},
},
PayeeInfo: &alipay.PayeeInfo{
Identity: payeeAccount,
IdentityType: "ALIPAY_LOGON_ID", // 根据账户类型选择:

View File

@@ -1317,6 +1317,10 @@ type AgentWithdrawalListItem struct {
PayeeName string `json:"payee_name"` // 收款人姓名
}
type AppQueryConfig struct {
RetentionDays int64 `json:"retention_days"` // 查询结果保留天数
}
type AuthorizationDocumentInfo struct {
DocumentId int64 `json:"documentId"` // 授权书ID
UserId int64 `json:"userId"` // 用户ID
@@ -1504,6 +1508,10 @@ type GetAgentSubordinateListResp struct {
List []AgentSubordinateList `json:"list"` // 查询列表
}
type GetAppConfigResp struct {
Query AppQueryConfig `json:"query"` // 查询相关配置
}
type GetAuthorizationDocumentByOrderReq struct {
OrderId int64 `json:"orderId" validate:"required"` // 订单ID
}

4
go.mod
View File

@@ -25,7 +25,7 @@ require (
github.com/samber/lo v1.50.0
github.com/shopspring/decimal v1.4.0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/smartwalle/alipay/v3 v3.2.23
github.com/smartwalle/alipay/v3 v3.2.29-0.20251230233706-e15a4853bc1c
github.com/sony/sonyflake v1.2.0
github.com/stretchr/testify v1.11.1
github.com/tidwall/gjson v1.18.0
@@ -81,7 +81,7 @@ require (
github.com/prometheus/procfs v0.15.1 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/smartwalle/ncrypto v1.0.4 // indirect
github.com/smartwalle/ngx v1.0.9 // indirect
github.com/smartwalle/ngx v1.0.12 // indirect
github.com/smartwalle/nsign v1.0.9 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/spf13/cast v1.7.0 // indirect

8
go.sum
View File

@@ -237,12 +237,12 @@ github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/smartwalle/alipay/v3 v3.2.23 h1:i1VwJeu70EmwpsXXz6GZZnMAtRx5MTfn2dPoql/L3zE=
github.com/smartwalle/alipay/v3 v3.2.23/go.mod h1:lVqFiupPf8YsAXaq5JXcwqnOUC2MCF+2/5vub+RlagE=
github.com/smartwalle/alipay/v3 v3.2.29-0.20251230233706-e15a4853bc1c h1:o48e92wOA4wuoWHtugNBU2wizp9ui4skQNcpqxcvgsM=
github.com/smartwalle/alipay/v3 v3.2.29-0.20251230233706-e15a4853bc1c/go.mod h1:dwPyjY5y17qUsDrsVYCQqmDBtQ/qpvY8JmQMvuJYGLQ=
github.com/smartwalle/ncrypto v1.0.4 h1:P2rqQxDepJwgeO5ShoC+wGcK2wNJDmcdBOWAksuIgx8=
github.com/smartwalle/ncrypto v1.0.4/go.mod h1:Dwlp6sfeNaPMnOxMNayMTacvC5JGEVln3CVdiVDgbBk=
github.com/smartwalle/ngx v1.0.9 h1:pUXDvWRZJIHVrCKA1uZ15YwNti+5P4GuJGbpJ4WvpMw=
github.com/smartwalle/ngx v1.0.9/go.mod h1:mx/nz2Pk5j+RBs7t6u6k22MPiBG/8CtOMpCnALIG8Y0=
github.com/smartwalle/ngx v1.0.12 h1:jcoCyu/0HtQ1y/gbiSLzqOUZcHnVLlKOmm0awRF7Mcg=
github.com/smartwalle/ngx v1.0.12/go.mod h1:mx/nz2Pk5j+RBs7t6u6k22MPiBG/8CtOMpCnALIG8Y0=
github.com/smartwalle/nsign v1.0.9 h1:8poAgG7zBd8HkZy9RQDwasC6XZvJpDGQWSjzL2FZL6E=
github.com/smartwalle/nsign v1.0.9/go.mod h1:eY6I4CJlyNdVMP+t6z1H6Jpd4m5/V+8xi44ufSTxXgc=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=