package payment import ( "encoding/json" "strconv" "strings" "git.apinb.com/bsm-sdk/core/errcode" "git.apinb.com/bsm-sdk/core/infra" "git.apinb.com/heqiapp/platforms/backend/api/internal/impl" "git.apinb.com/heqiapp/platforms/backend/api/internal/logic/common" paylogic "git.apinb.com/heqiapp/platforms/backend/api/internal/logic/payment" "git.apinb.com/heqiapp/platforms/backend/api/internal/models" "github.com/gin-gonic/gin" ) // paymentRefundDisplay 在退款事实之外附加财务审核所需的可读关系名称。 type paymentRefundDisplay struct { models.PaymentRefund BusinessDisplayName string `json:"business_display_name"` UserDisplayName string `json:"user_display_name"` ReviewerDisplayName string `json:"reviewer_display_name"` RefundProductSummary string `json:"refund_product_summary"` } // refundEcProductSnapshot 是商城退款读取成交商品名称所需的最小快照结构。 type refundEcProductSnapshot struct { Name string `json:"name"` } // ListRefund 返回退款列表,并批量解析原订单号和申请用户姓名。 func ListRefund(ctx *gin.Context) { page, size := common.PageSize(ctx) var refunds []models.PaymentRefund var total int64 query := common.ApplyKeywordFilter( ctx, common.ActiveRecords(impl.DBService.Model(&models.PaymentRefund{})), &models.PaymentRefund{}, ) if err := query.Count(&total).Error; err != nil { infra.Response.Error(ctx, err) return } if err := query.Order("created_at desc").Offset((page - 1) * size).Limit(size).Find(&refunds).Error; err != nil { infra.Response.Error(ctx, err) return } displays, err := buildPaymentRefundDisplays(refunds) if err != nil { infra.Response.Error(ctx, err) return } response, err := common.PublicResourceResponse(displays) if err != nil { infra.Response.Error(ctx, err) return } infra.Response.Success(ctx, gin.H{"total": total, "list": response}) } // GetRefund 返回退款详情,并附加与列表一致的可读关系名称。 func GetRefund(ctx *gin.Context) { var refund models.PaymentRefund if err := common.ActiveRecords(impl.DBService). Where("identity = ?", ctx.Param("identity")).First(&refund).Error; err != nil { common.RespondRecordError(ctx, err) return } displays, err := buildPaymentRefundDisplays([]models.PaymentRefund{refund}) if err != nil { infra.Response.Error(ctx, err) return } response, err := common.PublicResourceResponse(displays[0]) if err != nil { infra.Response.Error(ctx, err) return } infra.Response.Success(ctx, response) } // buildPaymentRefundDisplays 批量读取当前页引用,避免退款列表逐行查询。 func buildPaymentRefundDisplays(refunds []models.PaymentRefund) ([]paymentRefundDisplay, error) { gasorderIdentities := make([]string, 0, len(refunds)) ecOrderIdentities := make([]string, 0, len(refunds)) userIdentities := make([]string, 0, len(refunds)) for _, refund := range refunds { switch refund.BusinessType { case "gasorder": gasorderIdentities = append(gasorderIdentities, refund.BusinessIdentity) case "ec_order": ecOrderIdentities = append(ecOrderIdentities, refund.BusinessIdentity) } userIdentities = append(userIdentities, refund.UserIdentity) } gasorderNames := map[string]string{} if len(gasorderIdentities) > 0 { var orders []models.GasorderBasic if err := common.ActiveRecords(impl.DBService).Select("identity", "order_no"). Where("identity IN ?", gasorderIdentities).Find(&orders).Error; err != nil { return nil, err } for _, order := range orders { gasorderNames[order.Identity] = order.OrderNo } } ecOrderNames := map[string]string{} if len(ecOrderIdentities) > 0 { var orders []models.EcOrder if err := common.ActiveRecords(impl.DBService).Select("identity", "order_no"). Where("identity IN ?", ecOrderIdentities).Find(&orders).Error; err != nil { return nil, err } for _, order := range orders { ecOrderNames[order.Identity] = order.OrderNo } } userNames := map[string]string{} if len(userIdentities) > 0 { var users []models.UserAccount if err := common.ActiveRecords(impl.DBService).Select("identity", "name"). Where("identity IN ?", userIdentities).Find(&users).Error; err != nil { return nil, err } for _, user := range users { userNames[user.Identity] = user.Name } } reviewerNames := map[string]string{} reviewerIdentities := make([]string, 0, len(refunds)) for _, refund := range refunds { if refund.ReviewerIdentity != "" { reviewerIdentities = append(reviewerIdentities, refund.ReviewerIdentity) } } if len(reviewerIdentities) > 0 { var reviewers []models.PlatformAccount // 历史审核人即使已经归档也需要保留可读名称,因此不套用活动记录过滤。 if err := impl.DBService.Model(&models.PlatformAccount{}). Select("identity", "display_name", "username"). Where("identity IN ?", reviewerIdentities).Find(&reviewers).Error; err != nil { return nil, err } for _, reviewer := range reviewers { reviewerNames[reviewer.Identity] = readableRefundName( strings.TrimSpace(reviewer.DisplayName), strings.TrimSpace(reviewer.Username), ) } } productSummaries, err := loadPaymentRefundProductSummaries(refunds) if err != nil { return nil, err } displays := make([]paymentRefundDisplay, 0, len(refunds)) for _, refund := range refunds { displays = append(displays, paymentRefundDisplay{ PaymentRefund: refund, BusinessDisplayName: refundBusinessDisplayName(refund, gasorderNames, ecOrderNames), UserDisplayName: readableRefundName(userNames[refund.UserIdentity], "用户记录已失效"), ReviewerDisplayName: refundReviewerDisplayName(refund.ReviewerIdentity, reviewerNames), RefundProductSummary: productSummaries[refund.ID], }) } return displays, nil } // refundReviewerDisplayName 区分尚未审核、历史账号失效和正常审核人三种状态。 func refundReviewerDisplayName(identity string, names map[string]string) string { if strings.TrimSpace(identity) == "" { return "尚未审核" } return readableRefundName(names[identity], "审核人记录已失效") } // loadPaymentRefundProductSummaries 批量读取退款明细及原订单项,生成退款商品名称与数量。 func loadPaymentRefundProductSummaries(refunds []models.PaymentRefund) (map[uint64]string, error) { refundIDs := make([]uint64, 0, len(refunds)) refundTypes := make(map[uint64]string, len(refunds)) for _, refund := range refunds { refundIDs = append(refundIDs, refund.ID) refundTypes[refund.ID] = refund.BusinessType } var refundItems []models.PaymentRefundItem if len(refundIDs) > 0 { if err := impl.DBService.Where("payment_refund_id IN ?", refundIDs). Order("payment_refund_id asc, id asc").Find(&refundItems).Error; err != nil { return nil, err } } gasorderItemIdentities := make([]string, 0, len(refundItems)) ecOrderItemIdentities := make([]string, 0, len(refundItems)) for _, item := range refundItems { if refundTypes[item.PaymentRefundID] == "gasorder" { gasorderItemIdentities = append(gasorderItemIdentities, item.OrderItemIdentity) } else if refundTypes[item.PaymentRefundID] == "ec_order" { ecOrderItemIdentities = append(ecOrderItemIdentities, item.OrderItemIdentity) } } gasorderItems := map[string]models.GasorderItem{} productInfoIDs := make([]uint64, 0, len(gasorderItemIdentities)) if len(gasorderItemIdentities) > 0 { var items []models.GasorderItem if err := common.ActiveRecords(impl.DBService). Where("identity IN ?", gasorderItemIdentities).Find(&items).Error; err != nil { return nil, err } for _, item := range items { gasorderItems[item.Identity] = item productInfoIDs = append(productInfoIDs, item.ProductInfoID) } } productNames := map[uint64]string{} if len(productInfoIDs) > 0 { var products []models.ProductInfo if err := common.ActiveRecords(impl.DBService).Select("id", "name"). Where("id IN ?", productInfoIDs).Find(&products).Error; err != nil { return nil, err } for _, product := range products { productNames[product.ID] = product.Name } } ecOrderItems := map[string]models.EcOrderItem{} if len(ecOrderItemIdentities) > 0 { var items []models.EcOrderItem if err := common.ActiveRecords(impl.DBService). Where("identity IN ?", ecOrderItemIdentities).Find(&items).Error; err != nil { return nil, err } for _, item := range items { ecOrderItems[item.Identity] = item } } labelsByRefund := make(map[uint64][]string, len(refunds)) for _, refundItem := range refundItems { name := "" switch refundTypes[refundItem.PaymentRefundID] { case "gasorder": item := gasorderItems[refundItem.OrderItemIdentity] name = strings.TrimSpace(productNames[item.ProductInfoID]) if name == "" { name = readableRefundName(item.ProductTypeName, item.ProductCode) } case "ec_order": item := ecOrderItems[refundItem.OrderItemIdentity] var snapshot refundEcProductSnapshot if json.Unmarshal([]byte(item.ProductSnapshot), &snapshot) == nil { name = strings.TrimSpace(snapshot.Name) } } labelsByRefund[refundItem.PaymentRefundID] = append( labelsByRefund[refundItem.PaymentRefundID], refundProductLabel(name, refundItem.Quantity), ) } summaries := make(map[uint64]string, len(refunds)) for _, refund := range refunds { labels := labelsByRefund[refund.ID] if len(labels) == 0 { summaries[refund.ID] = "退款商品信息缺失" continue } summaries[refund.ID] = strings.Join(labels, "、") } return summaries, nil } // refundProductLabel 统一生成退款商品名称与数量文案。 func refundProductLabel(name string, quantity int) string { name = strings.TrimSpace(name) if name == "" { name = "商品信息缺失" } if quantity > 1 { return name + " × " + strconv.Itoa(quantity) } return name } // refundBusinessDisplayName 按稳定业务类型选择订单号,未知或失效关系给出明确文案。 func refundBusinessDisplayName(refund models.PaymentRefund, gasorderNames, ecOrderNames map[string]string) string { switch refund.BusinessType { case "gasorder": return readableRefundName(gasorderNames[refund.BusinessIdentity], "订单记录已失效") case "ec_order": return readableRefundName(ecOrderNames[refund.BusinessIdentity], "订单记录已失效") default: return "未知业务对象" } } // readableRefundName 统一处理退款关联名称缺失场景。 func readableRefundName(value, fallback string) string { if value == "" { return fallback } return value } func review(approve bool) gin.HandlerFunc { return func(ctx *gin.Context) { var request struct { Remark string `json:"remark"` } if ctx.ShouldBindJSON(&request) != nil { infra.Response.Error(ctx, errcode.ErrInvalidArgument) return } operator, _ := common.PlatformOperator(ctx) if err := paylogic.ReviewRefund(ctx.Param("identity"), operator, request.Remark, approve); err != nil { infra.Response.Error(ctx, err) return } infra.Response.Success(ctx, gin.H{"reviewed": true}) } } var ApproveRefund = review(true) var RejectRefund = review(false)