All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m26s
135 lines
4.1 KiB
Go
135 lines
4.1 KiB
Go
// 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
|
|
}
|