// Package paymentmethod 提供按资产类型读取和校验 C 端支付方式的能力。 package paymentmethod import ( "context" stderrors "errors" "slices" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/pkg/constants" "github.com/break/junhong_cmp_fiber/pkg/errors" "github.com/bytedance/sonic" ) // ConfigReader 提供受控系统配置读取能力。 type ConfigReader interface { GetStrict(ctx context.Context, key string) (string, error) } // Policy 负责返回资产允许的支付方式并执行后端强校验。 type Policy struct { reader ConfigReader } // NewPolicy 创建支付方式策略。 func NewPolicy(reader ConfigReader) *Policy { return &Policy{reader: reader} } // AllowedMethods 返回指定资产类型允许的支付方式;配置异常时失败关闭。 func (p *Policy) AllowedMethods(ctx context.Context, assetType string) ([]string, error) { if p == nil || p.reader == nil { return nil, errors.New(errors.CodeNoPaymentConfig) } key, err := configKey(assetType) if err != nil { return nil, err } raw, err := p.reader.GetStrict(ctx, key) if err != nil { return nil, errors.Wrap(errors.CodeNoPaymentConfig, err, "读取支付方式配置失败") } var methods []string if err := sonic.Unmarshal([]byte(raw), &methods); err != nil || !validMethods(methods) { return nil, errors.New(errors.CodeNoPaymentConfig, "支付方式配置无效") } return stableMethods(methods), nil } // EnsureAllowed 校验指定支付方式是否允许。 func (p *Policy) EnsureAllowed(ctx context.Context, assetType, paymentMethod string) error { methods, err := p.AllowedMethods(ctx, assetType) if err != nil { return err } if !slices.Contains(methods, paymentMethod) { return errors.New(errors.CodePaymentMethodUnavailable) } return nil } // AllowedRechargeMethods 返回资产允许且钱包充值接口支持的第三方支付方式。 func (p *Policy) AllowedRechargeMethods(ctx context.Context, assetType string) ([]string, error) { methods, err := p.AllowedMethods(ctx, assetType) if err != nil { return nil, err } result := make([]string, 0, 2) for _, method := range methods { if method == model.PaymentMethodWechat || method == model.PaymentMethodAlipay { result = append(result, method) } } return result, nil } // EnsureRechargeAllowed 校验普通充值只使用资产配置允许的第三方支付方式。 func (p *Policy) EnsureRechargeAllowed(ctx context.Context, assetType, paymentMethod string) error { methods, err := p.AllowedRechargeMethods(ctx, assetType) if err != nil { return err } if !slices.Contains(methods, paymentMethod) { return errors.New(errors.CodePaymentMethodUnavailable) } return nil } func configKey(assetType string) (string, error) { switch assetType { case "card", constants.AssetTypeIotCard, model.OrderTypeSingleCard: return constants.SystemConfigPaymentAllowedCard, nil case constants.AssetTypeDevice: return constants.SystemConfigPaymentAllowedDevice, nil default: return "", errors.New(errors.CodeInvalidParam, "无效的资产类型") } } func validMethods(methods []string) bool { if len(methods) == 0 { return false } seen := make(map[string]struct{}, len(methods)) for _, method := range methods { if method != model.PaymentMethodWallet && method != model.PaymentMethodWechat && method != model.PaymentMethodAlipay { return false } if _, exists := seen[method]; exists { return false } seen[method] = struct{}{} } return true } // ValidateConfigValue 校验支付方式系统配置的 JSON 集合。 func ValidateConfigValue(value string) error { var methods []string if err := sonic.Unmarshal([]byte(value), &methods); err != nil || !validMethods(methods) { return stderrors.New("支付方式配置必须是非空且不重复的合法数组") } return nil } func stableMethods(methods []string) []string { result := make([]string, 0, len(methods)) for _, method := range []string{model.PaymentMethodWallet, model.PaymentMethodWechat, model.PaymentMethodAlipay} { if slices.Contains(methods, method) { result = append(result, method) } } return result }