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

@@ -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,