Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Failing after 8h37m36s
- M2 批量换绑表头改为“平台登录账号”,只认手机号,细分格式、不存在、多匹配、非平台、已禁用失败原因 - M3 后台订单列表与详情返回 C 端在线购买的交易流水号、支付渠道、商户名称,详情新增全部支付单 payments - M16 预警规则不再返回 real_data_mb,预警列表/详情新增 threshold_mb,导出末列追加“阈值流量(MB)” - M23 设备导入 IMEI 必填,空 IMEI 行记为失败 - OpenSpec Change fix-august-iteration-follow-up-gaps:13/18,导入任务、导出任务的 Worker 端到端验证与前端/运营通知待部署后完成
140 lines
5.4 KiB
Go
140 lines
5.4 KiB
Go
package order
|
||
|
||
import (
|
||
"context"
|
||
|
||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||
"github.com/break/junhong_cmp_fiber/internal/model/dto"
|
||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||
)
|
||
|
||
// orderHasOnlinePaymentInfo 判断订单是否为 C 端在线购买且已支付或已退款,只有这类订单返回原始支付信息。
|
||
func orderHasOnlinePaymentInfo(order *model.Order) bool {
|
||
if order == nil || order.BuyerType != model.BuyerTypePersonal {
|
||
return false
|
||
}
|
||
if order.PaymentMethod != model.PaymentMethodWechat && order.PaymentMethod != model.PaymentMethodAlipay {
|
||
return false
|
||
}
|
||
return order.PaymentStatus == model.PaymentStatusPaid || order.PaymentStatus == model.PaymentStatusRefunded
|
||
}
|
||
|
||
// orderIsOnlinePurchase 判断订单是否为个人客户微信或支付宝订单,详情支付单列表按此范围返回。
|
||
func orderIsOnlinePurchase(order *model.Order) bool {
|
||
if order == nil || order.BuyerType != model.BuyerTypePersonal {
|
||
return false
|
||
}
|
||
return order.PaymentMethod == model.PaymentMethodWechat || order.PaymentMethod == model.PaymentMethodAlipay
|
||
}
|
||
|
||
// fillOrderPaymentInfo 批量为订单响应填充交易流水号、支付渠道与商户名称;一次查询支付单、一次查询支付配置。
|
||
func (s *Service) fillOrderPaymentInfo(ctx context.Context, orders []*model.Order, responses []*dto.OrderResponse) error {
|
||
orderIDs := make([]uint, 0, len(orders))
|
||
for _, order := range orders {
|
||
if orderHasOnlinePaymentInfo(order) {
|
||
orderIDs = append(orderIDs, order.ID)
|
||
}
|
||
}
|
||
if len(orderIDs) == 0 {
|
||
return nil
|
||
}
|
||
var payments []*model.Payment
|
||
// 带 order_type 过滤:tb_payment 同时存充值类支付,业务单 ID 空间与套餐订单不同。
|
||
if err := s.db.WithContext(ctx).
|
||
Where("order_id IN ? AND order_type = ? AND status = ?", orderIDs, model.PaymentOrderTypePackage, constants.PaymentRecordStatusPaid).
|
||
Order("order_id ASC, paid_at DESC NULLS LAST, id DESC").
|
||
Find(&payments).Error; err != nil {
|
||
return errors.Wrap(errors.CodeDatabaseError, err, "查询订单支付记录失败")
|
||
}
|
||
channels, err := s.resolvePaymentChannels(ctx, payments)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
paidByOrder := make(map[uint]*model.Payment, len(payments))
|
||
for _, payment := range payments {
|
||
if _, exists := paidByOrder[payment.OrderID]; !exists {
|
||
paidByOrder[payment.OrderID] = payment
|
||
}
|
||
}
|
||
for index, order := range orders {
|
||
payment, ok := paidByOrder[order.ID]
|
||
if !ok || !orderHasOnlinePaymentInfo(order) {
|
||
continue
|
||
}
|
||
responses[index].ThirdPartyTradeNo = payment.ThirdPartyTradeNo
|
||
responses[index].PaymentChannel = paymentChannel(payment, channels)
|
||
responses[index].PaymentMerchantName = payment.MerchantNameSnapshot
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// listOrderPayments 返回订单详情的全部支付单(任何状态),按创建时间倒序;不适用的订单返回空数组。
|
||
func (s *Service) listOrderPayments(ctx context.Context, order *model.Order) ([]*dto.OrderPaymentResponse, error) {
|
||
result := make([]*dto.OrderPaymentResponse, 0)
|
||
if !orderIsOnlinePurchase(order) {
|
||
return result, nil
|
||
}
|
||
var payments []*model.Payment
|
||
if err := s.db.WithContext(ctx).
|
||
Where("order_id = ? AND order_type = ?", order.ID, model.PaymentOrderTypePackage).
|
||
Order("created_at DESC, id DESC").
|
||
Find(&payments).Error; err != nil {
|
||
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询订单支付单失败")
|
||
}
|
||
channels, err := s.resolvePaymentChannels(ctx, payments)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
for _, payment := range payments {
|
||
result = append(result, &dto.OrderPaymentResponse{
|
||
PaymentNo: payment.PaymentNo,
|
||
PaymentMethod: payment.PaymentMethod,
|
||
PaymentChannel: paymentChannel(payment, channels),
|
||
PaymentMerchantName: payment.MerchantNameSnapshot,
|
||
ThirdPartyTradeNo: payment.ThirdPartyTradeNo,
|
||
Amount: payment.Amount,
|
||
Status: payment.Status,
|
||
StatusName: constants.GetPaymentRecordStatusName(payment.Status),
|
||
CreatedAt: payment.CreatedAt,
|
||
PaidAt: payment.PaidAt,
|
||
ExpireAt: payment.ExpireAt,
|
||
})
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
// resolvePaymentChannels 为缺少渠道快照的历史支付单批量读取原支付配置的渠道类型(含已删除配置)。
|
||
func (s *Service) resolvePaymentChannels(ctx context.Context, payments []*model.Payment) (map[uint]string, error) {
|
||
configIDs := make([]uint, 0)
|
||
for _, payment := range payments {
|
||
if payment.MerchantProviderTypeSnapshot == "" && payment.PaymentConfigID != nil {
|
||
configIDs = append(configIDs, *payment.PaymentConfigID)
|
||
}
|
||
}
|
||
channels := make(map[uint]string, len(configIDs))
|
||
if len(configIDs) == 0 {
|
||
return channels, nil
|
||
}
|
||
var configs []model.WechatConfig
|
||
if err := s.db.WithContext(ctx).Unscoped().Select("id", "provider_type").
|
||
Where("id IN ?", configIDs).Find(&configs).Error; err != nil {
|
||
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询历史支付配置失败")
|
||
}
|
||
for _, config := range configs {
|
||
channels[config.ID] = config.ProviderType
|
||
}
|
||
return channels, nil
|
||
}
|
||
|
||
// paymentChannel 取支付单渠道类型快照,没有快照时按原支付配置推断,推断不出为空串。
|
||
func paymentChannel(payment *model.Payment, channels map[uint]string) string {
|
||
if payment.MerchantProviderTypeSnapshot != "" {
|
||
return payment.MerchantProviderTypeSnapshot
|
||
}
|
||
if payment.PaymentConfigID == nil {
|
||
return ""
|
||
}
|
||
return channels[*payment.PaymentConfigID]
|
||
}
|