| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589 |
- package service
- import (
- "context"
- "fmt"
- "math"
- "os"
- "strconv"
- "strings"
- dbent "github.com/Wei-Shaw/sub2api/ent"
- "github.com/Wei-Shaw/sub2api/ent/paymentproviderinstance"
- "github.com/Wei-Shaw/sub2api/internal/payment"
- infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
- )
- const (
- SettingPaymentEnabled = "payment_enabled"
- SettingMinRechargeAmount = "MIN_RECHARGE_AMOUNT"
- SettingMaxRechargeAmount = "MAX_RECHARGE_AMOUNT"
- SettingDailyRechargeLimit = "DAILY_RECHARGE_LIMIT"
- SettingOrderTimeoutMinutes = "ORDER_TIMEOUT_MINUTES"
- SettingMaxPendingOrders = "MAX_PENDING_ORDERS"
- SettingEnabledPaymentTypes = "ENABLED_PAYMENT_TYPES"
- SettingLoadBalanceStrategy = "LOAD_BALANCE_STRATEGY"
- SettingBalancePayDisabled = "BALANCE_PAYMENT_DISABLED"
- SettingBalanceRechargeMult = "BALANCE_RECHARGE_MULTIPLIER"
- // SettingSubscriptionUSDToCNYRate 是订阅 CNY 换算汇率(1 USD = X CNY)。
- // 0/未配置 = 关闭换算(订阅按 price 数值直付),显式配置后 CNY 通道订阅按 price × rate 收款。
- SettingSubscriptionUSDToCNYRate = "SUBSCRIPTION_USD_TO_CNY_RATE"
- SettingRechargeFeeRate = "RECHARGE_FEE_RATE"
- SettingProductNamePrefix = "PRODUCT_NAME_PREFIX"
- SettingProductNameSuffix = "PRODUCT_NAME_SUFFIX"
- SettingHelpImageURL = "PAYMENT_HELP_IMAGE_URL"
- SettingHelpText = "PAYMENT_HELP_TEXT"
- SettingCancelRateLimitOn = "CANCEL_RATE_LIMIT_ENABLED"
- SettingCancelRateLimitMax = "CANCEL_RATE_LIMIT_MAX"
- SettingCancelWindowSize = "CANCEL_RATE_LIMIT_WINDOW"
- SettingCancelWindowUnit = "CANCEL_RATE_LIMIT_UNIT"
- SettingCancelWindowMode = "CANCEL_RATE_LIMIT_WINDOW_MODE"
- SettingAlipayForceQRCode = "ALIPAY_FORCE_QRCODE"
- SettingAlipayMobilePrecreateDeepLink = "ALIPAY_MOBILE_PRECREATE_DEEP_LINK"
- )
- // Default values for payment configuration settings.
- const (
- defaultOrderTimeoutMin = 30
- defaultMaxPendingOrders = 3
- )
- // PaymentConfig holds the payment system configuration.
- type PaymentConfig struct {
- Enabled bool `json:"enabled"`
- MinAmount float64 `json:"min_amount"`
- MaxAmount float64 `json:"max_amount"`
- DailyLimit float64 `json:"daily_limit"`
- OrderTimeoutMin int `json:"order_timeout_minutes"`
- MaxPendingOrders int `json:"max_pending_orders"`
- EnabledTypes []string `json:"enabled_payment_types"`
- BalanceDisabled bool `json:"balance_disabled"`
- BalanceRechargeMultiplier float64 `json:"balance_recharge_multiplier"`
- // SubscriptionUSDToCNYRate 为 0 时订阅换算关闭(兼容存量行为)。
- SubscriptionUSDToCNYRate float64 `json:"subscription_usd_to_cny_rate"`
- RechargeFeeRate float64 `json:"recharge_fee_rate"`
- LoadBalanceStrategy string `json:"load_balance_strategy"`
- ProductNamePrefix string `json:"product_name_prefix"`
- ProductNameSuffix string `json:"product_name_suffix"`
- HelpImageURL string `json:"help_image_url"`
- HelpText string `json:"help_text"`
- StripePublishableKey string `json:"stripe_publishable_key,omitempty"`
- // Cancel rate limit settings
- CancelRateLimitEnabled bool `json:"cancel_rate_limit_enabled"`
- CancelRateLimitMax int `json:"cancel_rate_limit_max"`
- CancelRateLimitWindow int `json:"cancel_rate_limit_window"`
- CancelRateLimitUnit string `json:"cancel_rate_limit_unit"`
- CancelRateLimitMode string `json:"cancel_rate_limit_window_mode"`
- // Force Alipay mobile users to use QR code instead of mobile redirect
- AlipayForceQRCode bool `json:"alipay_force_qrcode"`
- // Use Alipay face-to-face precreate and an app deep link on mobile clients.
- AlipayMobilePrecreateDeepLink bool `json:"alipay_mobile_precreate_deep_link"`
- }
- // UpdatePaymentConfigRequest contains fields to update payment configuration.
- type UpdatePaymentConfigRequest struct {
- Enabled *bool `json:"enabled"`
- MinAmount *float64 `json:"min_amount"`
- MaxAmount *float64 `json:"max_amount"`
- DailyLimit *float64 `json:"daily_limit"`
- OrderTimeoutMin *int `json:"order_timeout_minutes"`
- MaxPendingOrders *int `json:"max_pending_orders"`
- EnabledTypes []string `json:"enabled_payment_types"`
- BalanceDisabled *bool `json:"balance_disabled"`
- BalanceRechargeMultiplier *float64 `json:"balance_recharge_multiplier"`
- SubscriptionUSDToCNYRate *float64 `json:"subscription_usd_to_cny_rate"`
- RechargeFeeRate *float64 `json:"recharge_fee_rate"`
- LoadBalanceStrategy *string `json:"load_balance_strategy"`
- ProductNamePrefix *string `json:"product_name_prefix"`
- ProductNameSuffix *string `json:"product_name_suffix"`
- HelpImageURL *string `json:"help_image_url"`
- HelpText *string `json:"help_text"`
- // Cancel rate limit settings
- CancelRateLimitEnabled *bool `json:"cancel_rate_limit_enabled"`
- CancelRateLimitMax *int `json:"cancel_rate_limit_max"`
- CancelRateLimitWindow *int `json:"cancel_rate_limit_window"`
- CancelRateLimitUnit *string `json:"cancel_rate_limit_unit"`
- CancelRateLimitMode *string `json:"cancel_rate_limit_window_mode"`
- // Force Alipay mobile users to use QR code instead of mobile redirect
- AlipayForceQRCode *bool `json:"alipay_force_qrcode"`
- // Use Alipay face-to-face precreate and an app deep link on mobile clients.
- AlipayMobilePrecreateDeepLink *bool `json:"alipay_mobile_precreate_deep_link"`
- VisibleMethodAlipaySource *string `json:"payment_visible_method_alipay_source"`
- VisibleMethodWxpaySource *string `json:"payment_visible_method_wxpay_source"`
- VisibleMethodAlipayEnabled *bool `json:"payment_visible_method_alipay_enabled"`
- VisibleMethodWxpayEnabled *bool `json:"payment_visible_method_wxpay_enabled"`
- }
- // MethodLimits holds per-payment-type limits.
- type MethodLimits struct {
- PaymentType string `json:"payment_type"`
- DisplayName string `json:"display_name,omitempty"`
- Currency string `json:"currency"`
- FeeRate float64 `json:"fee_rate"`
- DailyLimit float64 `json:"daily_limit"`
- SingleMin float64 `json:"single_min"`
- SingleMax float64 `json:"single_max"`
- }
- // MethodLimitsResponse is the full response for the user-facing /limits API.
- // It includes per-method limits and the global widest range (union of all methods).
- type MethodLimitsResponse struct {
- Methods map[string]MethodLimits `json:"methods"`
- GlobalMin float64 `json:"global_min"` // 0 = no minimum
- GlobalMax float64 `json:"global_max"` // 0 = no maximum
- }
- type CreateProviderInstanceRequest struct {
- ProviderKey string `json:"provider_key"`
- Name string `json:"name"`
- Config map[string]string `json:"config"`
- SupportedTypes []string `json:"supported_types"`
- Enabled bool `json:"enabled"`
- PaymentMode string `json:"payment_mode"`
- SortOrder int `json:"sort_order"`
- Limits string `json:"limits"`
- RefundEnabled bool `json:"refund_enabled"`
- AllowUserRefund bool `json:"allow_user_refund"`
- }
- type UpdateProviderInstanceRequest struct {
- Name *string `json:"name"`
- Config map[string]string `json:"config"`
- SupportedTypes []string `json:"supported_types"`
- Enabled *bool `json:"enabled"`
- PaymentMode *string `json:"payment_mode"`
- SortOrder *int `json:"sort_order"`
- Limits *string `json:"limits"`
- RefundEnabled *bool `json:"refund_enabled"`
- AllowUserRefund *bool `json:"allow_user_refund"`
- }
- type CreatePlanRequest struct {
- GroupID int64 `json:"group_id"`
- Name string `json:"name"`
- Description string `json:"description"`
- Price float64 `json:"price"`
- OriginalPrice *float64 `json:"original_price"`
- Currency string `json:"currency"`
- ValidityDays int `json:"validity_days"`
- ValidityUnit string `json:"validity_unit"`
- Features string `json:"features"`
- ProductName string `json:"product_name"`
- ForSale bool `json:"for_sale"`
- SortOrder int `json:"sort_order"`
- }
- type UpdatePlanRequest struct {
- GroupID *int64 `json:"group_id"`
- Name *string `json:"name"`
- Description *string `json:"description"`
- Price *float64 `json:"price"`
- OriginalPrice *float64 `json:"original_price"`
- Currency *string `json:"currency"`
- ValidityDays *int `json:"validity_days"`
- ValidityUnit *string `json:"validity_unit"`
- Features *string `json:"features"`
- ProductName *string `json:"product_name"`
- ForSale *bool `json:"for_sale"`
- SortOrder *int `json:"sort_order"`
- }
- // PaymentConfigService manages payment configuration and CRUD for
- // provider instances, channels, and subscription plans.
- type PaymentConfigService struct {
- entClient *dbent.Client
- settingRepo SettingRepository
- encryptionKey []byte
- }
- // NewPaymentConfigService creates a new PaymentConfigService.
- func NewPaymentConfigService(entClient *dbent.Client, settingRepo SettingRepository, encryptionKey []byte) *PaymentConfigService {
- return &PaymentConfigService{entClient: entClient, settingRepo: settingRepo, encryptionKey: encryptionKey}
- }
- // IsPaymentEnabled returns whether the payment system is enabled.
- func (s *PaymentConfigService) IsPaymentEnabled(ctx context.Context) bool {
- val, err := s.settingRepo.GetValue(ctx, SettingPaymentEnabled)
- if err != nil {
- return false
- }
- return val == "true"
- }
- // GetPaymentConfig returns the full payment configuration.
- func (s *PaymentConfigService) GetPaymentConfig(ctx context.Context) (*PaymentConfig, error) {
- keys := []string{
- SettingPaymentEnabled, SettingMinRechargeAmount, SettingMaxRechargeAmount,
- SettingDailyRechargeLimit, SettingOrderTimeoutMinutes, SettingMaxPendingOrders,
- SettingEnabledPaymentTypes, SettingBalancePayDisabled, SettingBalanceRechargeMult, SettingSubscriptionUSDToCNYRate, SettingRechargeFeeRate, SettingLoadBalanceStrategy,
- SettingProductNamePrefix, SettingProductNameSuffix,
- SettingHelpImageURL, SettingHelpText,
- SettingCancelRateLimitOn, SettingCancelRateLimitMax,
- SettingCancelWindowSize, SettingCancelWindowUnit, SettingCancelWindowMode,
- SettingAlipayForceQRCode, SettingAlipayMobilePrecreateDeepLink,
- SettingPaymentVisibleMethodAlipayEnabled, SettingPaymentVisibleMethodAlipaySource,
- SettingPaymentVisibleMethodWxpayEnabled, SettingPaymentVisibleMethodWxpaySource,
- }
- vals, err := s.settingRepo.GetMultiple(ctx, keys)
- if err != nil {
- return nil, fmt.Errorf("get payment config settings: %w", err)
- }
- cfg := s.parsePaymentConfig(vals)
- // Load Stripe publishable key from the first enabled Stripe provider instance
- cfg.StripePublishableKey = s.getStripePublishableKey(ctx)
- return cfg, nil
- }
- func (s *PaymentConfigService) parsePaymentConfig(vals map[string]string) *PaymentConfig {
- cfg := &PaymentConfig{
- Enabled: vals[SettingPaymentEnabled] == "true",
- MinAmount: pcParseFloat(vals[SettingMinRechargeAmount], 1),
- MaxAmount: pcParseFloat(vals[SettingMaxRechargeAmount], 0),
- DailyLimit: pcParseFloat(vals[SettingDailyRechargeLimit], 0),
- OrderTimeoutMin: pcParseInt(vals[SettingOrderTimeoutMinutes], defaultOrderTimeoutMin),
- MaxPendingOrders: pcParseInt(vals[SettingMaxPendingOrders], defaultMaxPendingOrders),
- BalanceDisabled: vals[SettingBalancePayDisabled] == "true",
- BalanceRechargeMultiplier: normalizeBalanceRechargeMultiplier(pcParseFloat(vals[SettingBalanceRechargeMult], defaultBalanceRechargeMultiplier)),
- SubscriptionUSDToCNYRate: normalizeSubscriptionUSDToCNYRate(pcParseFloat(vals[SettingSubscriptionUSDToCNYRate], 0)),
- RechargeFeeRate: pcParseFloat(vals[SettingRechargeFeeRate], 0),
- LoadBalanceStrategy: vals[SettingLoadBalanceStrategy],
- ProductNamePrefix: vals[SettingProductNamePrefix],
- ProductNameSuffix: vals[SettingProductNameSuffix],
- HelpImageURL: vals[SettingHelpImageURL],
- HelpText: vals[SettingHelpText],
- CancelRateLimitEnabled: vals[SettingCancelRateLimitOn] == "true",
- CancelRateLimitMax: pcParseInt(vals[SettingCancelRateLimitMax], 10),
- CancelRateLimitWindow: pcParseInt(vals[SettingCancelWindowSize], 1),
- CancelRateLimitUnit: vals[SettingCancelWindowUnit],
- CancelRateLimitMode: vals[SettingCancelWindowMode],
- AlipayForceQRCode: vals[SettingAlipayForceQRCode] == "true",
- AlipayMobilePrecreateDeepLink: vals[SettingAlipayMobilePrecreateDeepLink] == "true",
- }
- cfg.AlipayMobilePrecreateDeepLink = pcEnvBoolOverride(
- SettingAlipayMobilePrecreateDeepLink,
- cfg.AlipayMobilePrecreateDeepLink,
- )
- if cfg.LoadBalanceStrategy == "" {
- cfg.LoadBalanceStrategy = payment.DefaultLoadBalanceStrategy
- }
- if raw := vals[SettingEnabledPaymentTypes]; raw != "" {
- types := make([]string, 0, len(strings.Split(raw, ",")))
- for _, t := range strings.Split(raw, ",") {
- t = strings.TrimSpace(t)
- if t != "" {
- types = append(types, t)
- }
- }
- cfg.EnabledTypes = NormalizeVisibleMethods(types)
- }
- return cfg
- }
- func pcEnvBoolOverride(key string, fallback bool) bool {
- raw, ok := os.LookupEnv(key)
- if !ok || strings.TrimSpace(raw) == "" {
- return fallback
- }
- value, err := strconv.ParseBool(strings.TrimSpace(raw))
- if err != nil {
- return fallback
- }
- return value
- }
- // getStripePublishableKey finds the publishable key from the first enabled Stripe provider instance.
- func (s *PaymentConfigService) getStripePublishableKey(ctx context.Context) string {
- if s.entClient == nil {
- return ""
- }
- instances, err := s.entClient.PaymentProviderInstance.Query().
- Where(
- paymentproviderinstance.EnabledEQ(true),
- paymentproviderinstance.ProviderKeyEQ(payment.TypeStripe),
- ).Limit(1).All(ctx)
- if err != nil || len(instances) == 0 {
- return ""
- }
- cfg, err := s.decryptConfig(instances[0].Config)
- if err != nil || cfg == nil {
- return ""
- }
- return cfg[payment.ConfigKeyPublishableKey]
- }
- // UpdatePaymentConfig updates the payment configuration settings.
- // NOTE: This function exceeds 30 lines because each field requires an independent
- // nil-check before serialisation — this is inherent to patch-style update patterns
- // and cannot be meaningfully decomposed without introducing unnecessary abstraction.
- func (s *PaymentConfigService) UpdatePaymentConfig(ctx context.Context, req UpdatePaymentConfigRequest) error {
- if req.BalanceRechargeMultiplier != nil {
- if math.IsNaN(*req.BalanceRechargeMultiplier) || math.IsInf(*req.BalanceRechargeMultiplier, 0) || *req.BalanceRechargeMultiplier <= 0 {
- return infraerrors.BadRequest("INVALID_BALANCE_RECHARGE_MULTIPLIER", "balance recharge multiplier must be greater than 0")
- }
- }
- if req.SubscriptionUSDToCNYRate != nil {
- v := *req.SubscriptionUSDToCNYRate
- if math.IsNaN(v) || math.IsInf(v, 0) || v < 0 {
- return infraerrors.BadRequest("INVALID_SUBSCRIPTION_USD_TO_CNY_RATE", "subscription USD to CNY rate must be 0 (disabled) or a positive number")
- }
- }
- if req.RechargeFeeRate != nil {
- v := *req.RechargeFeeRate
- if math.IsNaN(v) || math.IsInf(v, 0) || v < 0 || v > 100 {
- return infraerrors.BadRequest("INVALID_RECHARGE_FEE_RATE", "recharge fee rate must be between 0 and 100")
- }
- // Enforce max 2 decimal places
- if math.Round(v*100) != v*100 {
- return infraerrors.BadRequest("INVALID_RECHARGE_FEE_RATE", "recharge fee rate allows at most 2 decimal places")
- }
- }
- m := make(map[string]string)
- if req.Enabled != nil {
- m[SettingPaymentEnabled] = formatBoolOrEmpty(req.Enabled)
- }
- if req.MinAmount != nil {
- m[SettingMinRechargeAmount] = formatPositiveFloat(req.MinAmount)
- }
- if req.MaxAmount != nil {
- m[SettingMaxRechargeAmount] = formatPositiveFloat(req.MaxAmount)
- }
- if req.DailyLimit != nil {
- m[SettingDailyRechargeLimit] = formatPositiveFloat(req.DailyLimit)
- }
- if req.OrderTimeoutMin != nil {
- m[SettingOrderTimeoutMinutes] = formatPositiveInt(req.OrderTimeoutMin)
- }
- if req.MaxPendingOrders != nil {
- m[SettingMaxPendingOrders] = formatPositiveInt(req.MaxPendingOrders)
- }
- if req.EnabledTypes != nil {
- m[SettingEnabledPaymentTypes] = strings.Join(req.EnabledTypes, ",")
- }
- if req.BalanceDisabled != nil {
- m[SettingBalancePayDisabled] = formatBoolOrEmpty(req.BalanceDisabled)
- }
- if req.BalanceRechargeMultiplier != nil {
- m[SettingBalanceRechargeMult] = formatPositiveFloat(req.BalanceRechargeMultiplier)
- }
- if req.SubscriptionUSDToCNYRate != nil {
- m[SettingSubscriptionUSDToCNYRate] = formatPositiveFloatExact(req.SubscriptionUSDToCNYRate)
- }
- if req.RechargeFeeRate != nil {
- m[SettingRechargeFeeRate] = formatNonNegativeFloat(req.RechargeFeeRate)
- }
- if req.LoadBalanceStrategy != nil {
- m[SettingLoadBalanceStrategy] = derefStr(req.LoadBalanceStrategy)
- }
- if req.ProductNamePrefix != nil {
- m[SettingProductNamePrefix] = derefStr(req.ProductNamePrefix)
- }
- if req.ProductNameSuffix != nil {
- m[SettingProductNameSuffix] = derefStr(req.ProductNameSuffix)
- }
- if req.HelpImageURL != nil {
- m[SettingHelpImageURL] = derefStr(req.HelpImageURL)
- }
- if req.HelpText != nil {
- m[SettingHelpText] = derefStr(req.HelpText)
- }
- if req.CancelRateLimitEnabled != nil {
- m[SettingCancelRateLimitOn] = formatBoolOrEmpty(req.CancelRateLimitEnabled)
- }
- if req.CancelRateLimitMax != nil {
- m[SettingCancelRateLimitMax] = formatPositiveInt(req.CancelRateLimitMax)
- }
- if req.CancelRateLimitWindow != nil {
- m[SettingCancelWindowSize] = formatPositiveInt(req.CancelRateLimitWindow)
- }
- if req.CancelRateLimitUnit != nil {
- m[SettingCancelWindowUnit] = derefStr(req.CancelRateLimitUnit)
- }
- if req.CancelRateLimitMode != nil {
- m[SettingCancelWindowMode] = derefStr(req.CancelRateLimitMode)
- }
- if req.AlipayForceQRCode != nil {
- m[SettingAlipayForceQRCode] = formatBoolOrEmpty(req.AlipayForceQRCode)
- }
- if req.AlipayMobilePrecreateDeepLink != nil {
- m[SettingAlipayMobilePrecreateDeepLink] = formatBoolOrEmpty(req.AlipayMobilePrecreateDeepLink)
- }
- if req.VisibleMethodAlipaySource != nil {
- m[SettingPaymentVisibleMethodAlipaySource] = derefStr(req.VisibleMethodAlipaySource)
- }
- if req.VisibleMethodWxpaySource != nil {
- m[SettingPaymentVisibleMethodWxpaySource] = derefStr(req.VisibleMethodWxpaySource)
- }
- if req.VisibleMethodAlipayEnabled != nil {
- m[SettingPaymentVisibleMethodAlipayEnabled] = formatBoolOrEmpty(req.VisibleMethodAlipayEnabled)
- }
- if req.VisibleMethodWxpayEnabled != nil {
- m[SettingPaymentVisibleMethodWxpayEnabled] = formatBoolOrEmpty(req.VisibleMethodWxpayEnabled)
- }
- return s.settingRepo.SetMultiple(ctx, m)
- }
- func formatBoolOrEmpty(v *bool) string {
- if v == nil {
- return ""
- }
- return strconv.FormatBool(*v)
- }
- func formatPositiveFloat(v *float64) string {
- if v == nil || *v <= 0 {
- return "" // empty → parsePaymentConfig uses default
- }
- return strconv.FormatFloat(*v, 'f', 2, 64)
- }
- // formatPositiveFloatExact 保留完整精度,用于汇率等对小数位敏感的配置。
- func formatPositiveFloatExact(v *float64) string {
- if v == nil || *v <= 0 {
- return "" // empty → parsePaymentConfig 视为未配置(换算关闭)
- }
- return strconv.FormatFloat(*v, 'f', -1, 64)
- }
- func formatNonNegativeFloat(v *float64) string {
- if v == nil || *v < 0 {
- return ""
- }
- return strconv.FormatFloat(*v, 'f', 2, 64)
- }
- func formatPositiveInt(v *int) string {
- if v == nil || *v <= 0 {
- return ""
- }
- return strconv.Itoa(*v)
- }
- func derefStr(v *string) string {
- if v == nil {
- return ""
- }
- return *v
- }
- func splitTypes(s string) []string {
- if s == "" {
- return nil
- }
- parts := strings.Split(s, ",")
- result := make([]string, 0, len(parts))
- for _, p := range parts {
- p = strings.TrimSpace(p)
- if p != "" {
- result = append(result, p)
- }
- }
- return result
- }
- func joinTypes(types []string) string {
- return strings.Join(types, ",")
- }
- func pcParseFloat(s string, defaultVal float64) float64 {
- if s == "" {
- return defaultVal
- }
- v, err := strconv.ParseFloat(s, 64)
- if err != nil {
- return defaultVal
- }
- return v
- }
- func pcParseInt(s string, defaultVal int) int {
- if s == "" {
- return defaultVal
- }
- v, err := strconv.Atoi(s)
- if err != nil {
- return defaultVal
- }
- return v
- }
- func buildVisibleMethodSourceAvailability(instances []*dbent.PaymentProviderInstance) map[string]bool {
- available := make(map[string]bool, 4)
- for _, inst := range instances {
- switch inst.ProviderKey {
- case payment.TypeAlipay:
- if inst.SupportedTypes == "" || payment.InstanceSupportsType(inst.SupportedTypes, payment.TypeAlipay) || payment.InstanceSupportsType(inst.SupportedTypes, payment.TypeAlipayDirect) {
- available[VisibleMethodSourceOfficialAlipay] = true
- }
- case payment.TypeWxpay:
- if inst.SupportedTypes == "" || payment.InstanceSupportsType(inst.SupportedTypes, payment.TypeWxpay) || payment.InstanceSupportsType(inst.SupportedTypes, payment.TypeWxpayDirect) {
- available[VisibleMethodSourceOfficialWechat] = true
- }
- case payment.TypeEasyPay:
- for _, supportedType := range splitTypes(inst.SupportedTypes) {
- switch NormalizeVisibleMethod(supportedType) {
- case payment.TypeAlipay:
- available[VisibleMethodSourceEasyPayAlipay] = true
- case payment.TypeWxpay:
- available[VisibleMethodSourceEasyPayWechat] = true
- }
- }
- }
- }
- return available
- }
- func applyVisibleMethodRoutingToEnabledTypes(base []string, vals map[string]string, available map[string]bool) []string {
- shouldExpose := map[string]bool{
- payment.TypeAlipay: visibleMethodShouldBeExposed(payment.TypeAlipay, vals, available),
- payment.TypeWxpay: visibleMethodShouldBeExposed(payment.TypeWxpay, vals, available),
- }
- seen := make(map[string]struct{}, len(base)+2)
- out := make([]string, 0, len(base)+2)
- appendType := func(paymentType string) {
- paymentType = NormalizeVisibleMethod(paymentType)
- if paymentType == "" {
- return
- }
- if _, ok := seen[paymentType]; ok {
- return
- }
- seen[paymentType] = struct{}{}
- out = append(out, paymentType)
- }
- for _, paymentType := range base {
- visibleMethod := NormalizeVisibleMethod(paymentType)
- switch visibleMethod {
- case payment.TypeAlipay, payment.TypeWxpay:
- if shouldExpose[visibleMethod] {
- appendType(visibleMethod)
- }
- default:
- appendType(visibleMethod)
- }
- }
- for _, visibleMethod := range []string{payment.TypeAlipay, payment.TypeWxpay} {
- if shouldExpose[visibleMethod] {
- appendType(visibleMethod)
- }
- }
- return out
- }
- func visibleMethodShouldBeExposed(method string, vals map[string]string, available map[string]bool) bool {
- enabledKey := visibleMethodEnabledSettingKey(method)
- sourceKey := visibleMethodSourceSettingKey(method)
- if enabledKey == "" || sourceKey == "" || vals[enabledKey] != "true" {
- return false
- }
- source := NormalizeVisibleMethodSource(method, vals[sourceKey])
- return source != "" && available[source]
- }
|