fix: 富友订单支持退款,0 元退款不强制客户收款材料映射
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m20s

- decideRefundMethods 将 fuiou 与 wechat、alipay 按线上支付处理,修复富友订单报"该订单的支付方式不支持退款"
- 客户收款信息退款仅在金额大于 0 时强制映射并提交客户收款信息与凭证;0 元退款允许客户收款信息为空

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
邹进
2026-10-23 17:00:00 +09:00
parent 437d1c2c2c
commit 9e45097aa2
3 changed files with 25 additions and 3 deletions

View File

@@ -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{

View File

@@ -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), ".")

View File

@@ -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