This commit is contained in:
2025-10-24 15:39:20 +08:00
parent c72d0a7aa6
commit a258c360c2
28 changed files with 1335 additions and 351 deletions

View File

@@ -0,0 +1,29 @@
package admin_order
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"tydata-server/app/main/api/internal/logic/admin_order"
"tydata-server/app/main/api/internal/svc"
"tydata-server/app/main/api/internal/types"
"tydata-server/common/result"
"tydata-server/pkg/lzkit/validator"
)
func AdminRetryAgentProcessHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.AdminRetryAgentProcessReq
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_order.NewAdminRetryAgentProcessLogic(r.Context(), svcCtx)
resp, err := l.AdminRetryAgentProcess(&req)
result.HttpResult(r, w, resp, err)
}
}

View File

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

View File

@@ -310,6 +310,12 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
Path: "/refund/:id",
Handler: admin_order.AdminRefundOrderHandler(serverCtx),
},
{
// 重新执行代理处理
Method: http.MethodPost,
Path: "/retry-agent-process/:id",
Handler: admin_order.AdminRetryAgentProcessHandler(serverCtx),
},
{
// 更新订单
Method: http.MethodPut,
@@ -639,6 +645,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
server.AddRoutes(
[]rest.Route{
{
Method: http.MethodGet,
Path: "/membership/info",
Handler: agent.GetMembershipInfoHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/promotion/qrcode",

View File

@@ -47,6 +47,36 @@ func (l *AdminGetOrderDetailLogic) AdminGetOrderDetail(req *types.AdminGetOrderD
isPromotion = 1
}
// 判断是否为代理订单并获取代理处理状态
var isAgentOrder bool
var agentProcessStatus string
agentOrder, err := l.svcCtx.AgentOrderModel.FindOneByOrderId(l.ctx, order.Id)
if err == nil && agentOrder != nil {
isAgentOrder = true
// 查询代理佣金记录
commissions, err := l.svcCtx.AgentCommissionModel.FindAll(l.ctx,
l.svcCtx.AgentCommissionModel.SelectBuilder().Where("order_id = ?", order.Id), "")
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderDetail, 查询代理佣金失败 err: %v", err)
}
if len(commissions) > 0 {
agentProcessStatus = "success"
} else {
// 检查订单状态,如果是已支付但无佣金记录,则为待处理或失败
if order.Status == "paid" {
agentProcessStatus = "pending"
} else {
agentProcessStatus = "failed"
}
}
} else {
isAgentOrder = false
agentProcessStatus = "not_agent"
}
// 获取查询状态
var queryState string
builder := l.svcCtx.QueryModel.SelectBuilder().Where("order_id = ?", order.Id).Columns("query_state")
@@ -78,18 +108,20 @@ func (l *AdminGetOrderDetailLogic) AdminGetOrderDetail(req *types.AdminGetOrderD
// 构建响应
resp = &types.AdminGetOrderDetailResp{
Id: order.Id,
OrderNo: order.OrderNo,
PlatformOrderId: order.PlatformOrderId.String,
ProductName: product.ProductName,
PaymentPlatform: order.PaymentPlatform,
PaymentScene: order.PaymentScene,
Amount: order.Amount,
Status: order.Status,
CreateTime: order.CreateTime.Format("2006-01-02 15:04:05"),
UpdateTime: order.UpdateTime.Format("2006-01-02 15:04:05"),
IsPromotion: isPromotion,
QueryState: queryState,
Id: order.Id,
OrderNo: order.OrderNo,
PlatformOrderId: order.PlatformOrderId.String,
ProductName: product.ProductName,
PaymentPlatform: order.PaymentPlatform,
PaymentScene: order.PaymentScene,
Amount: order.Amount,
Status: order.Status,
CreateTime: order.CreateTime.Format("2006-01-02 15:04:05"),
UpdateTime: order.UpdateTime.Format("2006-01-02 15:04:05"),
IsPromotion: isPromotion,
QueryState: queryState,
IsAgentOrder: isAgentOrder,
AgentProcessStatus: agentProcessStatus,
}
// 处理可选字段

View File

@@ -102,6 +102,9 @@ func (l *AdminGetOrderListLogic) AdminGetOrderList(req *types.AdminGetOrderListR
// 并发获取产品信息和查询状态
productMap := make(map[int64]string)
queryStateMap := make(map[int64]string)
agentOrderMap := make(map[int64]bool) // 代理订单映射
agentProcessStatusMap := make(map[int64]string) // 代理处理状态映射
var mu sync.Mutex
// 批量获取查询状态
@@ -160,6 +163,60 @@ func (l *AdminGetOrderListLogic) AdminGetOrderList(req *types.AdminGetOrderListR
}
}
}
// 批量获取代理订单状态
agentOrders, err := l.svcCtx.AgentOrderModel.FindAll(l.ctx,
l.svcCtx.AgentOrderModel.SelectBuilder().Where(squirrel.Eq{"order_id": orderIds}), "")
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderList, 批量查询代理订单失败 err: %v", err)
}
// 记录代理订单
for _, agentOrder := range agentOrders {
agentOrderMap[agentOrder.OrderId] = true
}
// 对于代理订单,查询代理处理状态
if len(agentOrders) > 0 {
agentOrderIds := make([]int64, 0, len(agentOrders))
for _, agentOrder := range agentOrders {
agentOrderIds = append(agentOrderIds, agentOrder.OrderId)
}
// 查询代理佣金记录
commissions, err := l.svcCtx.AgentCommissionModel.FindAll(l.ctx,
l.svcCtx.AgentCommissionModel.SelectBuilder().Where(squirrel.Eq{"order_id": agentOrderIds}), "")
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminGetOrderList, 批量查询代理佣金失败 err: %v", err)
}
// 记录有佣金记录的订单为处理成功
processedOrderIds := make(map[int64]bool)
for _, commission := range commissions {
processedOrderIds[commission.OrderId] = true
}
// 创建订单状态映射,避免重复查找
orderStatusMap := make(map[int64]string)
for _, order := range orders {
orderStatusMap[order.Id] = order.Status
}
// 设置代理处理状态
for _, agentOrder := range agentOrders {
orderId := agentOrder.OrderId
if processedOrderIds[orderId] {
agentProcessStatusMap[orderId] = "success"
} else {
// 检查订单状态,如果是已支付但无佣金记录,则为待处理或失败
if orderStatusMap[orderId] == "paid" {
agentProcessStatusMap[orderId] = "pending"
} else {
agentProcessStatusMap[orderId] = "failed"
}
}
}
}
}
// 并发获取产品信息
@@ -222,6 +279,15 @@ func (l *AdminGetOrderListLogic) AdminGetOrderList(req *types.AdminGetOrderListR
if err == nil && promotionOrder != nil {
item.IsPromotion = 1
}
// 设置代理订单相关字段
if agentOrderMap[order.Id] {
item.IsAgentOrder = true
item.AgentProcessStatus = agentProcessStatusMap[order.Id]
} else {
item.IsAgentOrder = false
item.AgentProcessStatus = "not_agent"
}
resp.Items = append(resp.Items, item)
}

View File

@@ -16,6 +16,13 @@ import (
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
const (
PaymentPlatformAlipay = "alipay"
PaymentPlatformWechat = "wechat"
OrderStatusPaid = "paid"
RefundNoPrefix = "refund-"
)
type AdminRefundOrderLogic struct {
logx.Logger
ctx context.Context
@@ -29,64 +36,158 @@ func NewAdminRefundOrderLogic(ctx context.Context, svcCtx *svc.ServiceContext) *
svcCtx: svcCtx,
}
}
func (l *AdminRefundOrderLogic) AdminRefundOrder(req *types.AdminRefundOrderReq) (resp *types.AdminRefundOrderResp, err error) {
// 获取订单信息
order, err := l.svcCtx.OrderModel.FindOne(l.ctx, req.Id)
// 获取并验证订单
order, err := l.getAndValidateOrder(req.Id, req.RefundAmount)
if err != nil {
return nil, err
}
// 根据支付平台处理退款
switch order.PaymentPlatform {
case PaymentPlatformAlipay:
return l.handleAlipayRefund(order, req)
case PaymentPlatformWechat:
return l.handleWechatRefund(order, req)
default:
return nil, errors.Wrapf(xerr.NewErrMsg("不支持的支付平台"), "AdminRefundOrder, 不支持的支付平台: %s", order.PaymentPlatform)
}
}
// getAndValidateOrder 获取并验证订单信息
func (l *AdminRefundOrderLogic) getAndValidateOrder(orderId int64, refundAmount float64) (*model.Order, error) {
order, err := l.svcCtx.OrderModel.FindOne(l.ctx, orderId)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "AdminRefundOrder, 查询订单失败 err: %v", err)
}
// 检查订单状态
if order.Status != "paid" {
return nil, errors.Wrapf(xerr.NewErrMsg("订单状态不正确,无法退款"), "AdminRefundOrder, 订单状态不正确,无法退款 err: %v", err)
if order.Status != OrderStatusPaid {
return nil, errors.Wrapf(xerr.NewErrMsg("订单状态不正确,无法退款"), "AdminRefundOrder, 订单状态: %s", order.Status)
}
// 检查退款金额
if req.RefundAmount > order.Amount {
return nil, errors.Wrapf(xerr.NewErrMsg("退款金额不能大于订单金额"), "AdminRefundOrder, 退款金额不能大于订单金额 err: %v", err)
if refundAmount > order.Amount {
return nil, errors.Wrapf(xerr.NewErrMsg("退款金额不能大于订单金额"), "AdminRefundOrder, 退款金额: %f, 订单金额: %f", refundAmount, order.Amount)
}
return order, nil
}
// handleAlipayRefund 处理支付宝退款
func (l *AdminRefundOrderLogic) handleAlipayRefund(order *model.Order, req *types.AdminRefundOrderReq) (*types.AdminRefundOrderResp, error) {
// 调用支付宝退款接口
refundResp, err := l.svcCtx.AlipayService.AliRefund(l.ctx, order.OrderNo, req.RefundAmount)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "AdminRefundOrder, 退款失败 err: %v", err)
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "AdminRefundOrder, 支付宝退款失败 err: %v", err)
}
refundNo := l.generateRefundNo(order.OrderNo)
if refundResp.IsSuccess() {
err = l.svcCtx.OrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
// 创建退款记录
refund := &model.OrderRefund{
RefundNo: fmt.Sprintf("refund-%s", order.OrderNo),
PlatformRefundId: sql.NullString{String: refundResp.TradeNo, Valid: true},
OrderId: order.Id,
UserId: order.UserId,
ProductId: order.ProductId,
RefundAmount: req.RefundAmount,
RefundReason: sql.NullString{String: req.RefundReason, Valid: true},
Status: model.OrderRefundStatusPending,
RefundTime: sql.NullTime{Time: time.Now(), Valid: true},
}
if _, err := l.svcCtx.OrderRefundModel.Insert(ctx, session, refund); err != nil {
return fmt.Errorf("创建退款记录失败: %v", err)
}
// 更新订单状态
order.Status = model.OrderStatusRefunded
order.RefundTime = sql.NullTime{Time: time.Now(), Valid: true}
if _, err := l.svcCtx.OrderModel.Update(ctx, session, order); err != nil {
return fmt.Errorf("更新订单状态失败: %v", err)
}
return nil
})
// 支付宝退款成功,创建成功记录
err = l.createRefundRecordAndUpdateOrder(order, req, refundNo, refundResp.TradeNo, model.OrderStatusRefunded, model.OrderRefundStatusSuccess)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "AdminRefundOrder, 退款失败 err: %v", err)
return nil, err
}
return &types.AdminRefundOrderResp{
Status: model.OrderStatusRefunded,
RefundNo: fmt.Sprintf("refund-%s", order.OrderNo),
RefundNo: refundNo,
Amount: req.RefundAmount,
}, nil
} else {
return nil, errors.Wrapf(xerr.NewErrMsg(fmt.Sprintf("退款失败, : %v", refundResp.Msg)), "AdminRefundOrder, 退款失败 err: %v", err)
// 支付宝退款失败,创建失败记录但不更新订单状态
err = l.createRefundRecordOnly(order, req, refundNo, refundResp.TradeNo, model.OrderRefundStatusFailed)
if err != nil {
logx.Errorf("创建退款失败记录时出错: %v", err)
}
return nil, errors.Wrapf(xerr.NewErrMsg(fmt.Sprintf("退款失败: %v", refundResp.Msg)), "AdminRefundOrder, 支付宝退款失败")
}
}
// handleWechatRefund 处理微信退款
func (l *AdminRefundOrderLogic) handleWechatRefund(order *model.Order, req *types.AdminRefundOrderReq) (*types.AdminRefundOrderResp, error) {
// 调用微信退款接口
err := l.svcCtx.WechatPayService.WeChatRefund(l.ctx, order.OrderNo, req.RefundAmount, order.Amount)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "AdminRefundOrder, 微信退款失败 err: %v", err)
}
// 微信退款是异步的创建pending状态的退款记录
refundNo := l.generateRefundNo(order.OrderNo)
err = l.createRefundRecordAndUpdateOrder(order, req, refundNo, "", model.OrderStatusRefunding, model.OrderRefundStatusPending)
if err != nil {
return nil, err
}
return &types.AdminRefundOrderResp{
Status: model.OrderRefundStatusPending,
RefundNo: refundNo,
Amount: req.RefundAmount,
}, nil
}
// createRefundRecordAndUpdateOrder 创建退款记录并更新订单状态
func (l *AdminRefundOrderLogic) createRefundRecordAndUpdateOrder(order *model.Order, req *types.AdminRefundOrderReq, refundNo, platformRefundId, orderStatus, refundStatus string) error {
return l.svcCtx.OrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
// 创建退款记录
refund := &model.OrderRefund{
RefundNo: refundNo,
PlatformRefundId: l.createNullString(platformRefundId),
OrderId: order.Id,
UserId: order.UserId,
ProductId: order.ProductId,
RefundAmount: req.RefundAmount,
RefundReason: l.createNullString(req.RefundReason),
Status: refundStatus, // 使用传入的状态,不再硬编码
RefundTime: sql.NullTime{Time: time.Now(), Valid: true},
}
if _, err := l.svcCtx.OrderRefundModel.Insert(ctx, session, refund); err != nil {
return fmt.Errorf("创建退款记录失败: %v", err)
}
// 更新订单状态
order.Status = orderStatus
if _, err := l.svcCtx.OrderModel.Update(ctx, session, order); err != nil {
return fmt.Errorf("更新订单状态失败: %v", err)
}
return nil
})
}
// createRefundRecordOnly 仅创建退款记录,不更新订单状态(用于退款失败的情况)
func (l *AdminRefundOrderLogic) createRefundRecordOnly(order *model.Order, req *types.AdminRefundOrderReq, refundNo, platformRefundId, refundStatus string) error {
refund := &model.OrderRefund{
RefundNo: refundNo,
PlatformRefundId: l.createNullString(platformRefundId),
OrderId: order.Id,
UserId: order.UserId,
ProductId: order.ProductId,
RefundAmount: req.RefundAmount,
RefundReason: l.createNullString(req.RefundReason),
Status: refundStatus,
RefundTime: sql.NullTime{Time: time.Now(), Valid: true},
}
_, err := l.svcCtx.OrderRefundModel.Insert(l.ctx, nil, refund)
if err != nil {
return fmt.Errorf("创建退款记录失败: %v", err)
}
return nil
}
// generateRefundNo 生成退款单号
func (l *AdminRefundOrderLogic) generateRefundNo(orderNo string) string {
return fmt.Sprintf("%s%s", RefundNoPrefix, orderNo)
}
// createNullString 创建 sql.NullString
func (l *AdminRefundOrderLogic) createNullString(value string) sql.NullString {
return sql.NullString{
String: value,
Valid: value != "",
}
}

View File

@@ -0,0 +1,55 @@
package admin_order
import (
"context"
"time"
"tydata-server/app/main/api/internal/svc"
"tydata-server/app/main/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type AdminRetryAgentProcessLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewAdminRetryAgentProcessLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AdminRetryAgentProcessLogic {
return &AdminRetryAgentProcessLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *AdminRetryAgentProcessLogic) AdminRetryAgentProcess(req *types.AdminRetryAgentProcessReq) (resp *types.AdminRetryAgentProcessResp, err error) {
// 调用AgentService的重新执行代理处理方法
err = l.svcCtx.AgentService.RetryAgentProcess(l.ctx, req.Id)
if err != nil {
// 检查是否是"已经处理"的错误
if err.Error() == "代理处理已经成功,无需重新执行" {
return &types.AdminRetryAgentProcessResp{
Status: "already_processed",
Message: "代理处理已经成功,无需重新执行",
ProcessedAt: time.Now().Format("2006-01-02 15:04:05"),
}, nil
}
// 其他错误
logx.Errorf("重新执行代理处理失败订单ID: %d, 错误: %v", req.Id, err)
return &types.AdminRetryAgentProcessResp{
Status: "failed",
Message: err.Error(),
ProcessedAt: time.Now().Format("2006-01-02 15:04:05"),
}, nil
}
// 执行成功
return &types.AdminRetryAgentProcessResp{
Status: "success",
Message: "代理处理重新执行成功",
ProcessedAt: time.Now().Format("2006-01-02 15:04:05"),
}, nil
}

View File

@@ -353,6 +353,15 @@ func (l *AgentWithdrawalLogic) updateWithdrawalStatus(outBizNo string, status in
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)
}
}
return nil
})

View File

@@ -4,13 +4,16 @@ import (
"context"
"tydata-server/common/xerr"
"github.com/Masterminds/squirrel"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
"tydata-server/app/main/api/internal/svc"
"tydata-server/app/main/api/internal/types"
"tydata-server/app/main/model"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/mr"
)
type GetLinkDataLogic struct {
@@ -37,9 +40,46 @@ func (l *GetLinkDataLogic) GetLinkData(req *types.GetLinkDataReq) (resp *types.G
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
copier.Copy(&product, productModel)
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

View File

@@ -0,0 +1,100 @@
package agent
import (
"context"
"tydata-server/app/main/api/internal/svc"
"tydata-server/app/main/api/internal/types"
"tydata-server/app/main/model"
"tydata-server/common/xerr"
"tydata-server/pkg/lzkit/lzUtils"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type GetMembershipInfoLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetMembershipInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetMembershipInfoLogic {
return &GetMembershipInfoLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetMembershipInfoLogic) GetMembershipInfo() (resp *types.GetMembershipInfoResp, err error) {
// 获取普通代理配置
normalConfig, err := l.svcCtx.AgentMembershipConfigModel.FindOneByLevelName(l.ctx, model.AgentLeveNameNormal)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取普通代理配置失败: %v", err)
}
// 获取VIP会员配置
vipConfig, err := l.svcCtx.AgentMembershipConfigModel.FindOneByLevelName(l.ctx, model.AgentLeveNameVIP)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取VIP会员配置失败: %v", err)
}
// 获取SVIP会员配置
svipConfig, err := l.svcCtx.AgentMembershipConfigModel.FindOneByLevelName(l.ctx, model.AgentLeveNameSVIP)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取SVIP会员配置失败: %v", err)
}
// 转换配置的辅助函数
convertConfig := func(config *model.AgentMembershipConfig) (types.MembershipConfigInfo, error) {
var configInfo types.MembershipConfigInfo
err := copier.Copy(&configInfo, config)
if err != nil {
return configInfo, err
}
// 转换Null类型字段
configInfo.Price = lzUtils.NullFloat64ToFloat64(config.Price)
configInfo.ReportCommission = lzUtils.NullFloat64ToFloat64(config.ReportCommission)
configInfo.LowerActivityReward = lzUtils.NullFloat64ToFloat64(config.LowerActivityReward)
configInfo.NewActivityReward = lzUtils.NullFloat64ToFloat64(config.NewActivityReward)
configInfo.LowerStandardCount = lzUtils.NullInt64ToInt64(config.LowerStandardCount)
configInfo.NewLowerStandardCount = lzUtils.NullInt64ToInt64(config.NewLowerStandardCount)
configInfo.LowerWithdrawRewardRatio = lzUtils.NullFloat64ToFloat64(config.LowerWithdrawRewardRatio)
configInfo.LowerConvertVipReward = lzUtils.NullFloat64ToFloat64(config.LowerConvertVipReward)
configInfo.LowerConvertSvipReward = lzUtils.NullFloat64ToFloat64(config.LowerConvertSvipReward)
configInfo.ExemptionAmount = lzUtils.NullFloat64ToFloat64(config.ExemptionAmount)
configInfo.PriceIncreaseMax = lzUtils.NullFloat64ToFloat64(config.PriceIncreaseMax)
configInfo.PriceRatio = lzUtils.NullFloat64ToFloat64(config.PriceRatio)
configInfo.PriceIncreaseAmount = lzUtils.NullFloat64ToFloat64(config.PriceIncreaseAmount)
return configInfo, nil
}
// 转换普通代理配置
normalConfigInfo, err := convertConfig(normalConfig)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "转换普通代理配置失败: %v", err)
}
// 转换VIP配置
vipConfigInfo, err := convertConfig(vipConfig)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "转换VIP配置失败: %v", err)
}
// 转换SVIP配置
svipConfigInfo, err := convertConfig(svipConfig)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "转换SVIP配置失败: %v", err)
}
// 构建响应数据
resp = &types.GetMembershipInfoResp{
NormalConfig: normalConfigInfo,
VipConfig: vipConfigInfo,
SvipConfig: svipConfigInfo,
}
return resp, nil
}

View File

@@ -2,11 +2,11 @@ package pay
import (
"context"
"tydata-server/pkg/lzkit/lzUtils"
"fmt"
"net/http"
"strings"
"time"
"tydata-server/pkg/lzkit/lzUtils"
"github.com/smartwalle/alipay/v3"
@@ -151,22 +151,40 @@ func (l *AlipayCallbackLogic) handleAgentVipOrderPayment(w http.ResponseWriter,
return fmt.Errorf("修改代理会员订单信息失败: %+v", updateErr)
}
// 记录旧等级,用于判断是否为升级
oldLevel := agentModel.LevelName
// 设置会员等级
agentModel.LevelName = agentOrder.LevelName
// 延长会员时间
// 检查是否是同级续费并记录到日志
isRenewal := agentModel.LevelName == agentOrder.LevelName && agentModel.MembershipExpiryTime.Valid
if isRenewal {
logx.Infof("代理会员续费成功会员ID%d等级%s", agentModel.Id, agentModel.LevelName)
// 检查是否是有效期内续费(不发放奖励)还是重新激活(发放奖励)
isValidRenewal := oldLevel == agentOrder.LevelName && agentModel.MembershipExpiryTime.Valid && agentModel.MembershipExpiryTime.Time.After(time.Now())
if isValidRenewal {
logx.Infof("代理会员有效期内续费成功会员ID%d等级%s", agentModel.Id, agentModel.LevelName)
} else {
logx.Infof("代理会员新购升级成功会员ID%d等级%s", agentModel.Id, agentModel.LevelName)
logx.Infof("代理会员新购升级或重新激活成功会员ID%d等级%s", agentModel.Id, agentModel.LevelName)
}
agentModel.MembershipExpiryTime = lzUtils.RenewMembership(agentModel.MembershipExpiryTime)
if updateErr := l.svcCtx.AgentModel.UpdateWithVersion(l.ctx, nil, agentModel); updateErr != nil {
return fmt.Errorf("修改代理信息失败: %+v", updateErr)
}
// 如果不是有效期内续费,给上级代理发放升级奖励
if !isValidRenewal && (agentOrder.LevelName == model.AgentLeveNameVIP || agentOrder.LevelName == model.AgentLeveNameSVIP) {
// 验证升级路径的有效性
if oldLevel != agentOrder.LevelName {
upgradeRewardErr := l.svcCtx.AgentService.GiveUpgradeReward(transCtx, agentModel.Id, oldLevel, agentOrder.LevelName, session)
if upgradeRewardErr != nil {
logx.Errorf("发放升级奖励失败代理ID%d旧等级%s新等级%s错误%+v", agentModel.Id, oldLevel, agentOrder.LevelName, upgradeRewardErr)
// 升级奖励失败不影响主流程,只记录日志
} else {
logx.Infof("发放升级奖励成功代理ID%d旧等级%s新等级%s", agentModel.Id, oldLevel, agentOrder.LevelName)
}
}
}
return nil
})
if err != nil {

View File

@@ -188,7 +188,18 @@ func (l *PaymentLogic) AgentVipOrderPayment(req *types.PaymentReq, session sqlx.
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 获取代理会员配置失败, %+v", err)
}
// 验证会员配置价格是否有效
if !agentMembershipConfig.Price.Valid {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 会员等级%s的价格配置无效", agentVipCache.Type)
}
amount := agentMembershipConfig.Price.Float64
// 验证价格是否合理
if amount <= 0 {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成订单, 会员等级%s的价格配置无效: %f", agentVipCache.Type, amount)
}
// 内部用户测试金额
if user.Inside == 1 {
amount = 0.01
}

View File

@@ -2,13 +2,13 @@ package pay
import (
"context"
"tydata-server/app/main/api/internal/service"
"tydata-server/app/main/model"
"tydata-server/pkg/lzkit/lzUtils"
"fmt"
"net/http"
"strings"
"time"
"tydata-server/app/main/api/internal/service"
"tydata-server/app/main/model"
"tydata-server/pkg/lzkit/lzUtils"
"tydata-server/app/main/api/internal/svc"
@@ -150,21 +150,40 @@ func (l *WechatPayCallbackLogic) handleAgentVipOrderPayment(w http.ResponseWrite
return fmt.Errorf("修改代理会员订单信息失败: %+v", updateErr)
}
// 记录旧等级,用于判断是否为升级
oldLevel := agentModel.LevelName
// 设置会员等级
agentModel.LevelName = agentOrder.LevelName
// 延长会员时间
isRenewal := agentModel.LevelName == agentOrder.LevelName && agentModel.MembershipExpiryTime.Valid
if isRenewal {
logx.Infof("代理会员续费成功会员ID%d等级%s", agentModel.Id, agentModel.LevelName)
// 检查是否是有效期内续费(不发放奖励)还是重新激活(发放奖励)
isValidRenewal := oldLevel == agentOrder.LevelName && agentModel.MembershipExpiryTime.Valid && agentModel.MembershipExpiryTime.Time.After(time.Now())
if isValidRenewal {
logx.Infof("代理会员有效期内续费成功会员ID%d等级%s", agentModel.Id, agentModel.LevelName)
} else {
logx.Infof("代理会员新购升级成功会员ID%d等级%s", agentModel.Id, agentModel.LevelName)
logx.Infof("代理会员新购升级或重新激活成功会员ID%d等级%s", agentModel.Id, agentModel.LevelName)
}
agentModel.MembershipExpiryTime = lzUtils.RenewMembership(agentModel.MembershipExpiryTime)
if updateErr := l.svcCtx.AgentModel.UpdateWithVersion(l.ctx, nil, agentModel); updateErr != nil {
return fmt.Errorf("修改代理信息失败: %+v", updateErr)
}
// 如果不是有效期内续费,给上级代理发放升级奖励
if !isValidRenewal && (agentOrder.LevelName == model.AgentLeveNameVIP || agentOrder.LevelName == model.AgentLeveNameSVIP) {
// 验证升级路径的有效性
if oldLevel != agentOrder.LevelName {
upgradeRewardErr := l.svcCtx.AgentService.GiveUpgradeReward(transCtx, agentModel.Id, oldLevel, agentOrder.LevelName, session)
if upgradeRewardErr != nil {
logx.Errorf("发放升级奖励失败代理ID%d旧等级%s新等级%s错误%+v", agentModel.Id, oldLevel, agentOrder.LevelName, upgradeRewardErr)
// 升级奖励失败不影响主流程,只记录日志
} else {
logx.Infof("发放升级奖励成功代理ID%d旧等级%s新等级%s", agentModel.Id, oldLevel, agentOrder.LevelName)
}
}
}
return nil
})

View File

@@ -2,11 +2,18 @@ package pay
import (
"context"
"tydata-server/app/main/api/internal/svc"
"database/sql"
"net/http"
"strings"
"time"
"tydata-server/app/main/api/internal/svc"
"tydata-server/app/main/model"
"tydata-server/common/globalkey"
"github.com/pkg/errors"
"github.com/wechatpay-apiv3/wechatpay-go/services/refunddomestic"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
type WechatPayRefundCallbackLogic struct {
@@ -23,33 +30,207 @@ func NewWechatPayRefundCallbackLogic(ctx context.Context, svcCtx *svc.ServiceCon
}
}
func (l *WechatPayRefundCallbackLogic) WechatPayRefundCallback(w http.ResponseWriter, r *http.Request) error {
notification, err := l.svcCtx.WechatPayService.HandleRefundNotification(l.ctx, r)
// handleQueryOrderRefund 处理查询订单退款
func (l *WechatPayRefundCallbackLogic) handleQueryOrderRefund(orderNo string, status refunddomestic.Status) error {
order, err := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, orderNo)
if err != nil {
logx.Errorf("微信退款回调,%v", err)
return nil
return errors.Wrapf(err, "查找查询订单信息失败: %s", orderNo)
}
order, findOrderErr := l.svcCtx.OrderModel.FindOneByOrderNo(l.ctx, *notification.OutTradeNo)
if findOrderErr != nil {
logx.Errorf("微信退款回调,查找订单信息失败: %+v", findOrderErr)
// 检查订单是否已经处理过退款
if order.Status == model.OrderStatusRefunded {
logx.Infof("订单已经是退款状态,无需重复处理: orderNo=%s", orderNo)
return nil
}
switch *notification.Status {
// 只处理成功和失败状态
var orderStatus, refundStatus string
switch status {
case refunddomestic.STATUS_SUCCESS:
order.Status = "refunded"
orderStatus = model.OrderStatusRefunded
refundStatus = model.OrderRefundStatusSuccess
case refunddomestic.STATUS_CLOSED:
// 退款关闭,保持订单原状态,更新退款记录为失败
refundStatus = model.OrderRefundStatusFailed
case refunddomestic.STATUS_ABNORMAL:
// 异常
return nil
// 退款异常,保持订单原状态,更新退款记录为失败
refundStatus = model.OrderRefundStatusFailed
default:
// 其他状态暂不处理
return nil
}
if updateErr := l.svcCtx.OrderModel.UpdateWithVersion(l.ctx, nil, order); updateErr != nil {
logx.Errorf("微信退款回调,更新订单失败%+v", updateErr)
// 使用事务同时更新订单和退款记录
err = l.svcCtx.OrderModel.Trans(l.ctx, func(ctx context.Context, session sqlx.Session) error {
// 更新订单状态(仅在退款成功时更新)
if status == refunddomestic.STATUS_SUCCESS {
order.Status = orderStatus
order.RefundTime = sql.NullTime{
Time: time.Now(),
Valid: true,
}
if err := l.svcCtx.OrderModel.UpdateWithVersion(ctx, session, order); err != nil {
return errors.Wrapf(err, "更新查询订单状态失败: %s", orderNo)
}
}
// 查找最新的pending状态的退款记录
refund, err := l.findLatestPendingRefund(ctx, order.Id)
if err != nil {
if err == model.ErrNotFound {
logx.Errorf("未找到订单对应的待处理退款记录: orderNo=%s, orderId=%d", orderNo, order.Id)
return nil // 没有退款记录时不报错,只记录警告
}
return errors.Wrapf(err, "查找退款记录失败: orderNo=%s", orderNo)
}
// 检查退款记录是否已经处理过
if refund.Status == model.OrderRefundStatusSuccess {
logx.Infof("退款记录已经是成功状态,无需重复处理: orderNo=%s, refundId=%d", orderNo, refund.Id)
return nil
}
refund.Status = refundStatus
if status == refunddomestic.STATUS_SUCCESS {
refund.RefundTime = sql.NullTime{
Time: time.Now(),
Valid: true,
}
} else if status == refunddomestic.STATUS_CLOSED {
refund.CloseTime = sql.NullTime{
Time: time.Now(),
Valid: true,
}
}
if _, err := l.svcCtx.OrderRefundModel.Update(ctx, session, refund); err != nil {
return errors.Wrapf(err, "更新退款记录状态失败: orderNo=%s", orderNo)
}
return nil
})
if err != nil {
return errors.Wrapf(err, "更新订单和退款记录失败: %s", orderNo)
}
// 响应微信回调成功
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("success")) // 确保只写入一次响应
return nil
}
// handleAgentOrderRefund 处理代理会员订单退款
func (l *WechatPayRefundCallbackLogic) handleAgentOrderRefund(orderNo string, status refunddomestic.Status) error {
order, err := l.svcCtx.AgentMembershipRechargeOrderModel.FindOneByOrderNo(l.ctx, orderNo)
if err != nil {
return errors.Wrapf(err, "查找代理会员订单信息失败: %s", orderNo)
}
// 检查订单是否已经处理过退款
if order.Status == "refunded" {
logx.Infof("代理会员订单已经是退款状态,无需重复处理: orderNo=%s", orderNo)
return nil
}
if status == refunddomestic.STATUS_SUCCESS {
order.Status = "refunded"
} else if status == refunddomestic.STATUS_ABNORMAL {
return nil // 异常状态直接返回
} else {
return nil // 其他状态直接返回
}
if err := l.svcCtx.AgentMembershipRechargeOrderModel.UpdateWithVersion(l.ctx, nil, order); err != nil {
return errors.Wrapf(err, "更新代理会员订单状态失败: %s", orderNo)
}
return nil
}
// sendSuccessResponse 发送成功响应
func (l *WechatPayRefundCallbackLogic) sendSuccessResponse(w http.ResponseWriter) {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("success"))
}
func (l *WechatPayRefundCallbackLogic) WechatPayRefundCallback(w http.ResponseWriter, r *http.Request) error {
// 1. 处理微信退款通知
notification, err := l.svcCtx.WechatPayService.HandleRefundNotification(l.ctx, r)
if err != nil {
logx.Errorf("微信退款回调处理失败: %v", err)
l.sendSuccessResponse(w)
return nil
}
// 2. 检查关键字段是否为空
if notification.OutTradeNo == nil {
logx.Errorf("微信退款回调OutTradeNo字段为空")
l.sendSuccessResponse(w)
return nil
}
orderNo := *notification.OutTradeNo
// 3. 判断退款状态优先使用Status如果Status为nil则使用SuccessTime判断
var status refunddomestic.Status
var statusDetermined bool = false
if notification.Status != nil {
status = *notification.Status
statusDetermined = true
} else if notification.SuccessTime != nil && !notification.SuccessTime.IsZero() {
// 如果Status为空但SuccessTime有值说明退款成功
status = refunddomestic.STATUS_SUCCESS
statusDetermined = true
} else {
logx.Errorf("微信退款回调Status和SuccessTime都为空无法确定退款状态: orderNo=%s", orderNo)
l.sendSuccessResponse(w)
return nil
}
if !statusDetermined {
logx.Errorf("微信退款回调无法确定退款状态: orderNo=%s", orderNo)
l.sendSuccessResponse(w)
return nil
}
var processErr error
// 4. 根据订单号前缀处理不同类型的订单
switch {
case strings.HasPrefix(orderNo, "Q_"):
processErr = l.handleQueryOrderRefund(orderNo, status)
case strings.HasPrefix(orderNo, "A_"):
processErr = l.handleAgentOrderRefund(orderNo, status)
default:
// 兼容旧订单,假设没有前缀的是查询订单
processErr = l.handleQueryOrderRefund(orderNo, status)
}
// 5. 处理错误并响应
if processErr != nil {
logx.Errorf("处理退款订单失败: orderNo=%s, err=%v", orderNo, processErr)
}
// 无论处理是否成功,都返回成功响应给微信
l.sendSuccessResponse(w)
return nil
}
// findLatestPendingRefund 查找订单最新的pending状态退款记录
func (l *WechatPayRefundCallbackLogic) findLatestPendingRefund(ctx context.Context, orderId int64) (*model.OrderRefund, error) {
// 使用SelectBuilder查询最新的pending状态退款记录
builder := l.svcCtx.OrderRefundModel.SelectBuilder().
Where("order_id = ? AND status = ? AND del_state = ?", orderId, model.OrderRefundStatusPending, globalkey.DelStateNo).
OrderBy("id DESC").
Limit(1)
refunds, err := l.svcCtx.OrderRefundModel.FindAll(ctx, builder, "")
if err != nil {
return nil, err
}
if len(refunds) == 0 {
return nil, model.ErrNotFound
}
return refunds[0], nil
}

View File

@@ -2,14 +2,14 @@ package query
import (
"context"
"tydata-server/common/ctxdata"
"tydata-server/common/xerr"
"tydata-server/pkg/lzkit/crypto"
"tydata-server/pkg/lzkit/lzUtils"
"database/sql"
"encoding/hex"
"encoding/json"
"fmt"
"tydata-server/common/ctxdata"
"tydata-server/common/xerr"
"tydata-server/pkg/lzkit/crypto"
"tydata-server/pkg/lzkit/lzUtils"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
@@ -104,6 +104,7 @@ func (l *QueryDetailByOrderIdLogic) QueryDetailByOrderId(req *types.QueryDetailB
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 获取商品信息失败, %v", err)
}
query.ProductName = product.ProductName
query.Product = product.ProductEn
return &types.QueryDetailByOrderIdResp{
Query: query,
}, nil

View File

@@ -2,10 +2,10 @@ package query
import (
"context"
"tydata-server/common/ctxdata"
"tydata-server/common/xerr"
"encoding/hex"
"fmt"
"tydata-server/common/ctxdata"
"tydata-server/common/xerr"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
@@ -94,6 +94,7 @@ func (l *QueryDetailByOrderNoLogic) QueryDetailByOrderNo(req *types.QueryDetailB
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 获取商品信息失败, %v", err)
}
query.Product = product.ProductEn
query.ProductName = product.ProductName
return &types.QueryDetailByOrderNoResp{
Query: query,

View File

@@ -37,6 +37,7 @@ func (l *QueryExampleLogic) QueryExample(req *types.QueryExampleReq) (resp *type
// 创建一个空的Query结构体来存储结果
query := types.Query{
Product: product.ProductEn,
ProductName: product.ProductName,
QueryData: make([]types.QueryItem, 0),
QueryParams: make(map[string]interface{}),

View File

@@ -4,6 +4,7 @@ import (
"context"
"tydata-server/app/main/api/internal/svc"
"tydata-server/app/main/api/internal/types"
"tydata-server/app/main/model"
"tydata-server/common/ctxdata"
"tydata-server/common/xerr"
@@ -58,7 +59,14 @@ func (l *QueryListLogic) QueryList(req *types.QueryListReq) (resp *types.QueryLi
if findProductErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告列表查询, 获取商品信息失败, %+v", err)
}
// 检查订单状态,如果订单已退款,则设置查询状态为已退款
order, findOrderErr := l.svcCtx.OrderModel.FindOne(l.ctx, queryModel.OrderId)
if findOrderErr == nil && order.Status == model.OrderStatusRefunded {
query.QueryState = model.QueryStateRefunded
}
query.ProductName = product.ProductName
query.Product = product.ProductEn
list = append(list, query)
}
}

View File

@@ -103,6 +103,7 @@ func (l *QueryShareDetailLogic) QueryShareDetail(req *types.QueryShareDetailReq)
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "报告查询, 获取商品信息失败, %v", err)
}
query.ProductName = product.ProductName
query.Product = product.ProductEn
return &types.QueryShareDetailResp{
Status: "success",
Query: query,

View File

@@ -12,6 +12,7 @@ import (
type AgentService struct {
config config.Config
OrderModel model.OrderModel
AgentModel model.AgentModel
AgentAuditModel model.AgentAuditModel
AgentClosureModel model.AgentClosureModel
@@ -30,7 +31,7 @@ type AgentService struct {
AgentWithdrawalModel model.AgentWithdrawalModel
}
func NewAgentService(c config.Config, agentModel model.AgentModel, agentAuditModel model.AgentAuditModel,
func NewAgentService(c config.Config, orderModel model.OrderModel, agentModel model.AgentModel, agentAuditModel model.AgentAuditModel,
agentClosureModel model.AgentClosureModel, agentCommissionModel model.AgentCommissionModel,
agentCommissionDeductionModel model.AgentCommissionDeductionModel, agentWalletModel model.AgentWalletModel, agentLinkModel model.AgentLinkModel, agentOrderModel model.AgentOrderModel, agentRewardsModel model.AgentRewardsModel,
agentMembershipConfigModel model.AgentMembershipConfigModel,
@@ -41,6 +42,7 @@ func NewAgentService(c config.Config, agentModel model.AgentModel, agentAuditMod
return &AgentService{
config: c,
OrderModel: orderModel,
AgentModel: agentModel,
AgentAuditModel: agentAuditModel,
AgentClosureModel: agentClosureModel,
@@ -99,10 +101,10 @@ func (l *AgentService) AgentProcess(ctx context.Context, order *model.Order) err
if AgentClosureModel != nil {
AncestorId := AgentClosureModel.AncestorId
AncestorModel, findAgentModelErr := l.AgentModel.FindOne(transCtx, AncestorId)
if findAgentModelErr != nil != errors.Is(findAgentModelErr, model.ErrNotFound) {
if findAgentModelErr != nil && !errors.Is(findAgentModelErr, model.ErrNotFound) {
return findAgentModelErr
}
if AgentClosureModel != nil {
if AncestorModel != nil {
if AncestorModel.LevelName == "" {
AncestorModel.LevelName = model.AgentLeveNameNormal
}
@@ -271,6 +273,10 @@ func (l *AgentService) CommissionCost(ctx context.Context, descendantId int64, A
// 拥有则查看该上级设定的成本
agentMembershipUserConfigModel, findAgentMembershipUserConfigModelErr := l.AgentMembershipUserConfigModel.FindOneByAgentIdProductId(ctx, AncestorId, productID)
if findAgentMembershipUserConfigModelErr != nil {
// 如果上级没有配置该产品的定价规则,则跳过成本计算
if errors.Is(findAgentMembershipUserConfigModelErr, model.ErrNotFound) {
return 0, nil
}
return 0, findAgentMembershipUserConfigModelErr
}
@@ -301,6 +307,10 @@ func (l *AgentService) CommissionPricing(ctx context.Context, descendantId int64
// 拥有则查看该上级设定的成本
agentMembershipUserConfigModel, findAgentMembershipUserConfigModelErr := l.AgentMembershipUserConfigModel.FindOneByAgentIdProductId(ctx, AncestorId, productID)
if findAgentMembershipUserConfigModelErr != nil {
// 如果上级没有配置该产品的定价规则,则跳过成本计算
if errors.Is(findAgentMembershipUserConfigModelErr, model.ErrNotFound) {
return 0, nil
}
return 0, findAgentMembershipUserConfigModelErr
}
@@ -332,13 +342,247 @@ func (l *AgentService) CommissionPricing(ctx context.Context, descendantId int64
return 0, nil
}
//func (l *AgentService) UpgradeVip(ctx context.Context, agentID int64, leve string, session sqlx.Session) error {
// agentModel, err := l.AgentModel.FindOne(ctx, agentID)
// if err != nil {
// return err
// }
// if agentModel.LevelName != model.AgentLeveNameNormal {
// return fmt.Errorf("已经是会员")
// }
// return nil
//}
// GiveUpgradeReward 给上级代理发放下级升级奖励
func (l *AgentService) GiveUpgradeReward(ctx context.Context, agentID int64, oldLevel, newLevel string, session sqlx.Session) error {
// 查找上级代理
agentClosureModel, err := l.AgentClosureModel.FindOneByDescendantIdDepth(ctx, agentID, 1)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
// 没有上级代理,直接返回
return nil
}
return err
}
ancestorID := agentClosureModel.AncestorId
ancestorModel, err := l.AgentModel.FindOne(ctx, ancestorID)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
// 上级代理不存在,直接返回
return nil
}
return err
}
if ancestorModel == nil {
// 上级代理不存在,直接返回
return nil
}
// 获取上级代理的等级配置
if ancestorModel.LevelName == "" {
ancestorModel.LevelName = model.AgentLeveNameNormal
}
agentMembershipConfigModel, err := l.AgentMembershipConfigModel.FindOneByLevelName(ctx, ancestorModel.LevelName)
if err != nil {
return err
}
// 根据升级路径计算奖励金额差额
var rewardAmount float64
var rewardType string
// 获取各等级的奖励金额
var vipRewardAmount float64
var svipRewardAmount float64
if agentMembershipConfigModel.LowerConvertVipReward.Valid {
vipRewardAmount = agentMembershipConfigModel.LowerConvertVipReward.Float64
}
if agentMembershipConfigModel.LowerConvertSvipReward.Valid {
svipRewardAmount = agentMembershipConfigModel.LowerConvertSvipReward.Float64
}
// 根据升级路径计算实际奖励金额
switch {
case oldLevel == "" || oldLevel == model.AgentLeveNameNormal:
// 普通代理升级
switch newLevel {
case model.AgentLeveNameVIP:
rewardAmount = vipRewardAmount
rewardType = model.AgentRewardsTypeDescendantUpgradeVip
case model.AgentLeveNameSVIP:
rewardAmount = svipRewardAmount
rewardType = model.AgentRewardsTypeDescendantUpgradeSvip
default:
// 无效的升级路径,直接返回
return nil
}
case oldLevel == model.AgentLeveNameVIP && newLevel == model.AgentLeveNameSVIP:
// VIP升级到SVIP发放差额奖励
rewardAmount = svipRewardAmount - vipRewardAmount
rewardType = model.AgentRewardsTypeDescendantUpgradeSvip
// 如果差额为负数或零,不发放奖励
if rewardAmount <= 0 {
return nil
}
default:
// 其他无效的升级路径如SVIP降级等直接返回
return nil
}
// 如果有奖励金额,则发放奖励
if rewardAmount > 0 {
// 创建奖励记录
agentRewards := model.AgentRewards{
AgentId: ancestorID,
Amount: rewardAmount,
RelationAgentId: lzUtils.Int64ToNullInt64(agentID),
Type: rewardType,
}
_, err = l.AgentRewardsModel.Insert(ctx, session, &agentRewards)
if err != nil {
return err
}
// 更新上级代理钱包
ancestorWallet, err := l.AgentWalletModel.FindOneByAgentId(ctx, ancestorID)
if err != nil {
return err
}
ancestorWallet.Balance += rewardAmount
ancestorWallet.TotalEarnings += rewardAmount
err = l.AgentWalletModel.UpdateWithVersion(ctx, session, ancestorWallet)
if err != nil {
return err
}
}
return nil
}
// GiveWithdrawReward 给上级代理发放下级提现奖励
func (l *AgentService) GiveWithdrawReward(ctx context.Context, agentID int64, withdrawAmount float64, session sqlx.Session) error {
// 验证提现金额
if withdrawAmount <= 0 {
return nil
}
// 查找上级代理
agentClosureModel, err := l.AgentClosureModel.FindOneByDescendantIdDepth(ctx, agentID, 1)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
// 没有上级代理,直接返回
return nil
}
return err
}
ancestorID := agentClosureModel.AncestorId
ancestorModel, err := l.AgentModel.FindOne(ctx, ancestorID)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
// 上级代理不存在,直接返回
return nil
}
return err
}
if ancestorModel == nil {
// 上级代理不存在,直接返回
return nil
}
// 获取上级代理的等级配置
if ancestorModel.LevelName == "" {
ancestorModel.LevelName = model.AgentLeveNameNormal
}
agentMembershipConfigModel, err := l.AgentMembershipConfigModel.FindOneByLevelName(ctx, ancestorModel.LevelName)
if err != nil {
return err
}
// 计算提现奖励金额
if agentMembershipConfigModel.LowerWithdrawRewardRatio.Valid {
rewardRatio := agentMembershipConfigModel.LowerWithdrawRewardRatio.Float64
// 验证奖励比例的有效性0-1之间
if rewardRatio < 0 || rewardRatio > 1 {
// 无效的奖励比例,直接返回
return nil
}
rewardAmount := withdrawAmount * rewardRatio
if rewardAmount > 0 {
// 创建奖励记录
agentRewards := model.AgentRewards{
AgentId: ancestorID,
Amount: rewardAmount,
RelationAgentId: lzUtils.Int64ToNullInt64(agentID),
Type: model.AgentRewardsTypeDescendantWithdraw,
}
_, err = l.AgentRewardsModel.Insert(ctx, session, &agentRewards)
if err != nil {
return err
}
// 更新上级代理钱包
ancestorWallet, err := l.AgentWalletModel.FindOneByAgentId(ctx, ancestorID)
if err != nil {
return err
}
ancestorWallet.Balance += rewardAmount
ancestorWallet.TotalEarnings += rewardAmount
err = l.AgentWalletModel.UpdateWithVersion(ctx, session, ancestorWallet)
if err != nil {
return err
}
}
}
return nil
}
// CheckAgentProcessStatus 检查代理处理事务是否已成功
func (l *AgentService) CheckAgentProcessStatus(ctx context.Context, orderID int64) (bool, error) {
// 检查是否存在代理订单记录
_, err := l.AgentOrderModel.FindOneByOrderId(ctx, orderID)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
// 没有代理订单记录,说明不是代理推广订单
return true, nil
}
return false, err
}
// 检查是否存在代理佣金记录
// 使用SelectBuilder查询该订单的佣金记录
selectBuilder := l.AgentCommissionModel.SelectBuilder()
selectBuilder = selectBuilder.Where("order_id = ?", orderID)
selectBuilder = selectBuilder.Where("del_state = ?", 0) // 未删除
commissions, err := l.AgentCommissionModel.FindAll(ctx, selectBuilder, "")
if err != nil {
return false, err
}
// 如果存在佣金记录,说明代理处理已成功
return len(commissions) > 0, nil
}
// RetryAgentProcess 重新执行代理处理事务
func (l *AgentService) RetryAgentProcess(ctx context.Context, orderID int64) error {
// 首先检查订单是否存在
order, err := l.OrderModel.FindOne(ctx, orderID)
if err != nil {
return err
}
// 检查订单状态是否为已支付
if order.Status != "paid" {
return errors.New("订单状态不是已支付,无法执行代理处理")
}
// 检查代理处理是否已经成功
alreadyProcessed, err := l.CheckAgentProcessStatus(ctx, orderID)
if err != nil {
return err
}
if alreadyProcessed {
return errors.New("代理处理已经成功,无需重新执行")
}
// 执行代理处理
return l.AgentProcess(ctx, order)
}

View File

@@ -192,7 +192,7 @@ func NewServiceContext(c config.Config) *ServiceContext {
apiRequestService := service.NewApiRequestService(c, featureModel, productFeatureModel, tianyuanapi)
verificationService := service.NewVerificationService(c, tianyuanapi, apiRequestService)
asynqService := service.NewAsynqService(c)
agentService := service.NewAgentService(c, agentModel, agentAuditModel, agentClosureModel,
agentService := service.NewAgentService(c, orderModel, agentModel, agentAuditModel, agentClosureModel,
agentCommissionModel, agentCommissionDeductionModel, agentWalletModel, agentLinkModel,
agentOrderModel, agentRewardsModel, agentMembershipConfigModel, agentMembershipRechargeOrderModel,
agentMembershipUserConfigModel, agentProductConfigModel, agentPlatformDeductionModel,

View File

@@ -433,20 +433,22 @@ type AdminGetOrderDetailReq struct {
}
type AdminGetOrderDetailResp struct {
Id int64 `json:"id"` // 订单ID
OrderNo string `json:"order_no"` // 商户订单号
PlatformOrderId string `json:"platform_order_id"` // 支付订单号
ProductName string `json:"product_name"` // 产品名称
PaymentPlatform string `json:"payment_platform"` // 支付方式
PaymentScene string `json:"payment_scene"` // 支付平台
Amount float64 `json:"amount"` // 金额
Status string `json:"status"` // 支付状态pending-待支付paid-已支付refunded-已退款closed-已关闭failed-支付失败
QueryState string `json:"query_state"` // 查询状态pending-待查询success-查询成功failed-查询失败 processing-查询中
CreateTime string `json:"create_time"` // 创建时间
PayTime string `json:"pay_time"` // 支付时间
RefundTime string `json:"refund_time"` // 退款时间
IsPromotion int64 `json:"is_promotion"` // 是否推广订单0-否1-是
UpdateTime string `json:"update_time"` // 更新时间
Id int64 `json:"id"` // 订单ID
OrderNo string `json:"order_no"` // 商户订单号
PlatformOrderId string `json:"platform_order_id"` // 支付订单号
ProductName string `json:"product_name"` // 产品名称
PaymentPlatform string `json:"payment_platform"` // 支付方式
PaymentScene string `json:"payment_scene"` // 支付平台
Amount float64 `json:"amount"` // 金额
Status string `json:"status"` // 支付状态pending-待支付paid-已支付refunded-已退款closed-已关闭failed-支付失败
QueryState string `json:"query_state"` // 查询状态pending-待查询success-查询成功failed-查询失败 processing-查询中
CreateTime string `json:"create_time"` // 创建时间
PayTime string `json:"pay_time"` // 支付时间
RefundTime string `json:"refund_time"` // 退款时间
IsPromotion int64 `json:"is_promotion"` // 是否推广订单0-否1-是
UpdateTime string `json:"update_time"` // 更新时间
IsAgentOrder bool `json:"is_agent_order"` // 是否是代理订单
AgentProcessStatus string `json:"agent_process_status"` // 代理事务处理状态not_agent-非代理订单success-处理成功failed-处理失败pending-待处理
}
type AdminGetOrderListReq struct {
@@ -680,6 +682,16 @@ type AdminResetPasswordResp struct {
Success bool `json:"success"` // 是否成功
}
type AdminRetryAgentProcessReq struct {
Id int64 `path:"id"` // 订单ID
}
type AdminRetryAgentProcessResp struct {
Status string `json:"status"` // 执行状态success-成功already_processed-已处理failed-失败
Message string `json:"message"` // 执行结果消息
ProcessedAt string `json:"processed_at"` // 处理时间
}
type AdminRoleApiInfo struct {
Id int64 `json:"id"`
RoleId int64 `json:"role_id"`
@@ -1329,6 +1341,12 @@ type GetLinkDataResp struct {
Product
}
type GetMembershipInfoResp struct {
NormalConfig MembershipConfigInfo `json:"normal_config"` // 普通代理配置
VipConfig MembershipConfigInfo `json:"vip_config"` // VIP会员配置
SvipConfig MembershipConfigInfo `json:"svip_config"` // SVIP会员配置
}
type GetMenuAllReq struct {
}
@@ -1495,6 +1513,24 @@ type IapCallbackReq struct {
TransactionReceipt string `json:"transaction_receipt" validate:"required"`
}
type MembershipConfigInfo struct {
Id int64 `json:"id"` // 主键
LevelName string `json:"level_name"` // 会员级别名称
Price float64 `json:"price"` // 会员年费
ReportCommission float64 `json:"report_commission"` // 直推报告收益
LowerActivityReward float64 `json:"lower_activity_reward"` // 下级活跃奖励金额
NewActivityReward float64 `json:"new_activity_reward"` // 新增活跃奖励金额
LowerStandardCount int64 `json:"lower_standard_count"` // 活跃下级达标个数
NewLowerStandardCount int64 `json:"new_lower_standard_count"` // 新增活跃下级达标个数
LowerWithdrawRewardRatio float64 `json:"lower_withdraw_reward_ratio"` // 下级提现奖励比例
LowerConvertVipReward float64 `json:"lower_convert_vip_reward"` // 下级转化VIP奖励
LowerConvertSvipReward float64 `json:"lower_convert_svip_reward"` // 下级转化SVIP奖励
ExemptionAmount float64 `json:"exemption_amount"` // 免审核金额
PriceIncreaseMax float64 `json:"price_increase_max"` // 提价最高金额
PriceRatio float64 `json:"price_ratio"` // 提价区间收取比例
PriceIncreaseAmount float64 `json:"price_increase_amount"` // 在原本成本上加价的金额
}
type MenuListItem struct {
Id int64 `json:"id"` // 菜单ID
Pid int64 `json:"pid"` // 父菜单ID
@@ -1546,19 +1582,21 @@ type NotificationListItem struct {
}
type OrderListItem struct {
Id int64 `json:"id"` // 订单ID
OrderNo string `json:"order_no"` // 商户订单号
PlatformOrderId string `json:"platform_order_id"` // 支付订单号
ProductName string `json:"product_name"` // 产品名称
PaymentPlatform string `json:"payment_platform"` // 支付方式
PaymentScene string `json:"payment_scene"` // 支付平台
Amount float64 `json:"amount"` // 金额
Status string `json:"status"` // 支付状态pending-待支付paid-已支付refunded-已退款closed-已关闭failed-支付失败
QueryState string `json:"query_state"` // 查询状态pending-待查询success-查询成功failed-查询失败 processing-查询中
CreateTime string `json:"create_time"` // 创建时间
PayTime string `json:"pay_time"` // 支付时间
RefundTime string `json:"refund_time"` // 退款时间
IsPromotion int64 `json:"is_promotion"` // 是否推广订单0-否1-是
Id int64 `json:"id"` // 订单ID
OrderNo string `json:"order_no"` // 商户订单号
PlatformOrderId string `json:"platform_order_id"` // 支付订单号
ProductName string `json:"product_name"` // 产品名称
PaymentPlatform string `json:"payment_platform"` // 支付方式
PaymentScene string `json:"payment_scene"` // 支付平台
Amount float64 `json:"amount"` // 金额
Status string `json:"status"` // 支付状态pending-待支付paid-已支付refunded-已退款closed-已关闭failed-支付失败
QueryState string `json:"query_state"` // 查询状态pending-待查询success-查询成功failed-查询失败 processing-查询中
CreateTime string `json:"create_time"` // 创建时间
PayTime string `json:"pay_time"` // 支付时间
RefundTime string `json:"refund_time"` // 退款时间
IsPromotion int64 `json:"is_promotion"` // 是否推广订单0-否1-是
IsAgentOrder bool `json:"is_agent_order"` // 是否是代理订单
AgentProcessStatus string `json:"agent_process_status"` // 代理事务处理状态not_agent-非代理订单success-处理成功failed-处理失败pending-待处理
}
type PaymentCheckReq struct {
@@ -1656,6 +1694,7 @@ type Query struct {
Id int64 `json:"id"` // 主键ID
OrderId int64 `json:"order_id"` // 订单ID
UserId int64 `json:"user_id"` // 用户ID
Product string `json:"product"` // 产品ID
ProductName string `json:"product_name"` // 产品ID
QueryParams map[string]interface{} `json:"query_params"`
QueryData []QueryItem `json:"query_data"`