Files
junhong_cmp_fiber/internal/application/packagesilence/order_helpers.go
邹进 909f4d0ec2
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Failing after 8m39s
feat: 实名即生效套餐沉默期、延期办理与资产出库时间
- 迁移 000259~000265:套餐记录沉默期字段与扫描索引、套餐默认沉默期、延期政策/流水/订单/批次/明细 5 张表、默认政策与系统配置(总开关默认关闭)、企微审批白名单、卡与设备出库时间及出库记录
- 沉默期设置(订单发放、自动购包、钱包续费)、后台购买时调整(超默认值按政策免审或企微审批)、统一判定与激活闸门、排队与孤儿扫描跳过过期套餐、过期套餐禁止退款、每 10 分钟扫描
- 总开关专用接口与打开前 4 项检查;延期政策;延期资格与计费;H5 自助与后台二维码在线支付(商户池路由)、支付回调与查单恢复、线下登记、特殊/批量延期、异常重试与原路退款;沉默期审批场景与终态处理
- 换货沉默期流水与出库时间;卡/设备列表、详情、导出与出库记录接口;延期订单与即将过期套餐列表及导出;财务流水纳入延期支付
- 沉默期与出库时间回填工具 cmd/silence-backfill;前端接口变更说明

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 16:32:58 +09:00

146 lines
6.7 KiB
Go

package packagesilence
import (
"context"
"github.com/break/junhong_cmp_fiber/internal/application/refundchannel"
"github.com/break/junhong_cmp_fiber/internal/infrastructure/audit"
"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"
)
func refundTarget(order *model.PackageSilenceExtensionOrder, payment *model.Payment, config *model.WechatConfig, providerType, refundNo string, amount int64) refundchannel.Target {
target := refundchannel.Target{
RefundNo: refundNo, OrderID: order.ID, OrderNo: order.OrderNo, ProviderType: providerType, Config: config,
PaymentNo: payment.PaymentNo, ChannelTradeNo: payment.ThirdPartyTradeNo, PaidAt: payment.PaidAt,
PaidAmount: payment.Amount, RefundAmount: amount, FrozenActualReceivedAmount: amount, ChannelRefundRequestNo: refundNo,
}
if providerType == model.ProviderTypeFuiou {
target.ChannelOrderType = "WECHAT"
}
return target
}
func orderAuditInput(usage *model.PackageUsage, order *model.PackageSilenceExtensionOrder, before, summary string) audit.SilenceAuditInput {
return audit.SilenceAuditInput{
ActionCode: constants.AuditActionUsageSilenceExtensionOrderState, Summary: summary, Usage: usage,
BeforeData: map[string]any{"order_status": before},
AfterData: map[string]any{"order_status": order.Status},
Metadata: map[string]any{
"order_no": order.OrderNo, "batch_id": order.BatchID, "source": order.Source, "payment_method": order.PaymentMethod,
"months": order.Months, "extension_days": order.ExtensionDays, "amount": order.Amount,
"paid_payment_id": order.PaidPaymentID, "exception_code": order.ExceptionCode,
"offline_trade_no": order.OfflineTradeNo, "approval_instance_id": order.ApprovalInstanceID,
"before_deadline_at": order.BeforeDeadlineAt, "expected_deadline_at": order.ExpectedDeadlineAt,
},
}
}
func auditInputForException(usage *model.PackageUsage, extension *model.PackageSilenceExtension, code, text string) audit.SilenceAuditInput {
return audit.SilenceAuditInput{
ActionCode: constants.AuditActionUsageSilenceExtensionException, Summary: "沉默期延期执行异常:" + text, Usage: usage,
AfterData: map[string]any{"extension_status": extension.Status},
Metadata: map[string]any{
"batch_id": extension.BatchID, "extension_id": extension.ID, "extension_type": extension.ExtensionType,
"months": extension.Months, "extension_days": extension.ExtensionDays, "amount": extension.Amount,
"extension_order_id": extension.ExtensionOrderID, "failure_code": code,
},
}
}
// orderResponse 构造延期订单响应:支付方式、收款商户、交易流水号取已支付或已退款支付单快照;待支付时返回支付内容。
func (s *ExtensionService) orderResponse(ctx context.Context, order *model.PackageSilenceExtensionOrder, includeQR bool) (*dto.SilenceExtensionOrderResponse, error) {
resp := OrderDTO(order)
var payments []model.Payment
if order.PaymentMethod != model.PaymentByOffline {
if err := s.db.WithContext(ctx).Where("order_type = ? AND order_id = ?", constants.PaymentOrderTypeSilenceExtension, order.ID).
Order("id DESC").Find(&payments).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询延期支付单失败")
}
}
for index := range payments {
payment := &payments[index]
if order.PaidPaymentID != nil && payment.ID == *order.PaidPaymentID {
resp.PaymentMerchantName = payment.MerchantNameSnapshot
resp.ThirdPartyTradeNo = payment.ThirdPartyTradeNo
resp.PaymentNo = payment.PaymentNo
}
if includeQR && order.Status == constants.SilenceOrderStatusPendingPayment && payment.Status == model.PaymentRecordStatusPending && resp.QRContent == "" {
resp.QRContent = payment.QRContent
resp.PaymentNo = payment.PaymentNo
}
}
var batch model.PackageSilenceExtensionBatch
if err := s.db.WithContext(ctx).Select("id", "batch_no").Where("id = ?", order.BatchID).First(&batch).Error; err == nil {
resp.BatchNo = batch.BatchNo
}
return resp, nil
}
// OrderDTO 构造延期订单基础响应(不含支付单快照)。
func OrderDTO(order *model.PackageSilenceExtensionOrder) *dto.SilenceExtensionOrderResponse {
resp := &dto.SilenceExtensionOrderResponse{
ID: order.ID, OrderNo: order.OrderNo, PackageUsageID: order.PackageUsageID, PackageID: order.PackageID,
PackageName: order.PackageNameSnapshot, AssetType: order.AssetType, AssetID: order.AssetID,
AssetIdentifier: order.AssetIdentifierSnapshot, ShopID: order.ShopID, CustomerID: order.CustomerID,
Source: order.Source, SourceName: orderSourceName(order.Source), InputDays: order.InputDays, Months: order.Months,
ExtensionDays: order.ExtensionDays, UnitPrice: order.UnitPrice, Amount: order.Amount, PolicyName: order.PolicyNameSnapshot,
BeforeDeadlineAt: order.BeforeDeadlineAt, ExpectedDeadlineAt: order.ExpectedDeadlineAt, Status: order.Status,
StatusName: OrderStatusName(order.Status), PaymentMethod: order.PaymentMethod,
PaymentVoucherKeys: []string(order.PaymentVoucherKeys), EvidenceRefs: []string(order.EvidenceRefs),
ApprovalRequired: order.ApprovalRequired, ApprovalInstanceID: order.ApprovalInstanceID, PaidAmount: order.PaidAmount,
PaidAt: order.PaidAt, ExpireAt: order.ExpireAt, CompletedAt: order.CompletedAt, ExceptionCode: order.ExceptionCode,
ExceptionSummary: order.ExceptionSummary, RefundAmount: order.RefundAmount, RefundedAt: order.RefundedAt,
CreatedBy: order.CreatedBy, CreatedAt: order.CreatedAt,
}
if order.OfflinePaymentMethodName != nil {
resp.OfflinePaymentMethodName = *order.OfflinePaymentMethodName
}
if order.OfflineTradeNo != nil {
resp.OfflineTradeNo = *order.OfflineTradeNo
}
if order.RefundNo != nil {
resp.RefundNo = *order.RefundNo
}
return resp
}
// OrderStatusName 返回延期订单状态名称。
func OrderStatusName(status string) string {
switch status {
case constants.SilenceOrderStatusPendingPayment:
return "待支付"
case constants.SilenceOrderStatusPendingApproval:
return "待审批"
case constants.SilenceOrderStatusRejected:
return "已驳回"
case constants.SilenceOrderStatusPaid:
return "已支付"
case constants.SilenceOrderStatusCompleted:
return "已延期"
case constants.SilenceOrderStatusClosed:
return "已关闭"
case constants.SilenceOrderStatusException:
return "执行异常"
case constants.SilenceOrderStatusRefunded:
return "已退款"
default:
return ""
}
}
func orderSourceName(source string) string {
switch source {
case constants.SilenceOrderSourceClient:
return "客户 H5 下单"
case constants.SilenceOrderSourceAdmin:
return "后台二维码"
case constants.SilenceOrderSourceOffline:
return "线下登记"
default:
return ""
}
}