Files
2026-08-17 00:44:49 +08:00

322 lines
11 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)