From 7ca79ebf2857fe8c78c83bf2dc213eea64b846ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E8=BF=9B?= Date: Fri, 23 Oct 2026 17:00:00 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=AF=8C=E5=8F=8B=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E9=80=80=E6=AC=BE=EF=BC=8C0=20=E5=85=83?= =?UTF-8?q?=E9=80=80=E6=AC=BE=E4=B8=8D=E5=BC=BA=E5=88=B6=E5=AE=A2=E6=88=B7?= =?UTF-8?q?=E6=94=B6=E6=AC=BE=E6=9D=90=E6=96=99=E6=98=A0=E5=B0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - decideRefundMethods 将 fuiou 与 wechat、alipay 按线上支付处理,修复富友订单报"该订单的支付方式不支持退款" - 客户收款信息退款仅在金额大于 0 时强制映射并提交客户收款信息与凭证;0 元退款允许客户收款信息为空 Co-Authored-By: Claude Opus 5.5 --- internal/application/wecom/scene.go | 14 +++++++++++++- internal/infrastructure/wecom/approval_form.go | 11 ++++++++++- internal/service/refund/method.go | 3 ++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/internal/application/wecom/scene.go b/internal/application/wecom/scene.go index 95633425..a832741e 100644 --- a/internal/application/wecom/scene.go +++ b/internal/application/wecom/scene.go @@ -6,6 +6,7 @@ import ( "encoding/hex" stdErrors "errors" "fmt" + "strconv" "strings" "time" @@ -332,6 +333,7 @@ func RequiredSceneFields(businessType string) []RequiredSceneField { } // RequiredRefundFields 返回退款提交固定与条件必需字段;快照方式编码缺失或未知时由表单构建拒绝。 +// 客户收款信息退款要求客户收款信息与凭证必须映射并提交;0 元退款不退钱,两者不强制。 func RequiredRefundFields(snapshot map[string]any) []RequiredSceneField { methodCode, ok := snapshot[constants.ApprovalFieldRefundMethodCode].(string) methodCode = strings.TrimSpace(methodCode) @@ -339,12 +341,22 @@ func RequiredRefundFields(snapshot map[string]any) []RequiredSceneField { return []RequiredSceneField{{Code: constants.ApprovalFieldRefundMethodCode, Name: "退款方式编码"}} } result := []RequiredSceneField{{Code: constants.ApprovalFieldRefundMethod, Name: "退款方式", RequiredMapping: true}} - if methodCode == constants.RefundMethodCustomerAccount { + if methodCode == constants.RefundMethodCustomerAccount && !isZeroAmountRefund(snapshot) { result = append(result, RequiredSceneField{Code: constants.ApprovalFieldCustomerAccountInfo, Name: "客户收款信息"}, RequiredSceneField{Code: constants.ApprovalFieldCustomerVoucherKey, Name: "客户收款凭证"}) } return result } +// isZeroAmountRefund 判断快照中的申请退款金额是否为 0;金额缺失或无法解析时按非 0 处理,保持强制校验。 +func isZeroAmountRefund(snapshot map[string]any) bool { + raw, ok := snapshot[constants.ApprovalFieldRequestedRefundAmount].(string) + if !ok { + return false + } + amount, err := strconv.ParseFloat(strings.TrimSpace(raw), 64) + return err == nil && amount == 0 +} + // approvalBusinessTypes 返回全部已登记审批业务场景,供只读字段汇总复用。 func approvalBusinessTypes() []string { return []string{ diff --git a/internal/infrastructure/wecom/approval_form.go b/internal/infrastructure/wecom/approval_form.go index 70efedbd..c9b2de4a 100644 --- a/internal/infrastructure/wecom/approval_form.go +++ b/internal/infrastructure/wecom/approval_form.go @@ -101,7 +101,7 @@ func buildApprovalForm(ctx context.Context, businessType string, applicationID, attachmentCount := 0 for _, mapping := range mappings { raw, exists := lookupSnapshotValue(snapshot, mapping.BusinessField) - if !exists || raw == nil || isEmptyFileValue(mapping, raw) { + if !exists || raw == nil || isEmptyFileValue(mapping, raw) || isEmptyCustomerAccountInfo(mapping, raw) { if requiredControls[mapping.ControlID] { return approvalFormBuildResult{}, errors.New(errors.CodeInvalidStatus, "审批业务快照缺少模板必填字段: "+mapping.BusinessField) } @@ -142,6 +142,15 @@ func isEmptyFileValue(mapping dto.WeComControlMappingItem, raw any) bool { } } +// isEmptyCustomerAccountInfo 判断客户收款信息是否为空文本;只有 0 元退款可能为空,按未填写处理,由模板必填规则决定是否拦截。 +func isEmptyCustomerAccountInfo(mapping dto.WeComControlMappingItem, raw any) bool { + if strings.TrimSpace(mapping.BusinessField) != constants.ApprovalFieldCustomerAccountInfo { + return false + } + value, ok := raw.(string) + return ok && strings.TrimSpace(value) == "" +} + // lookupSnapshotValue 支持用点分路径读取嵌套业务快照,避免模板映射绑定具体 DTO。 func lookupSnapshotValue(snapshot map[string]any, path string) (any, bool) { segments := strings.Split(strings.TrimSpace(path), ".") diff --git a/internal/service/refund/method.go b/internal/service/refund/method.go index cc430047..8326f02d 100644 --- a/internal/service/refund/method.go +++ b/internal/service/refund/method.go @@ -45,7 +45,8 @@ func (s *Service) decideRefundMethods(ctx context.Context, order *model.Order) ( decision := &refundMethodDecision{} switch order.PaymentMethod { - case model.PaymentMethodWechat, model.PaymentMethodAlipay: + // 富友是微信支付的上游通道,套餐订单回调写入 fuiou,按线上支付处理。 + case model.PaymentMethodWechat, model.PaymentMethodAlipay, model.ProviderTypeFuiou: payment, err := s.loadPaidPackagePayment(ctx, order.ID) if err != nil { return nil, err