fix(upgrade): preserve legacy auth and payment compatibility

This commit is contained in:
IanShaw027
2026-04-22 13:18:10 +08:00
parent 29caf85104
commit 06136af805
14 changed files with 311 additions and 76 deletions

View File

@@ -45,10 +45,18 @@ func (s *PaymentConfigService) pcApplyEnabledVisibleMethodInstances(ctx context.
for _, method := range []string{payment.TypeAlipay, payment.TypeWxpay} {
matching := filterEnabledVisibleMethodInstances(instances, method)
providerKey, err := s.resolveVisibleMethodProviderKey(ctx, method, matching)
if err != nil || providerKey == "" {
if err != nil {
delete(filtered, method)
continue
}
if providerKey == "" {
if len(matching) == 0 {
delete(filtered, method)
continue
}
filtered[method] = matching
continue
}
selectedInstances := filterVisibleMethodInstancesByProviderKey(instances, method, providerKey)
if len(selectedInstances) == 0 {
delete(filtered, method)

View File

@@ -6,6 +6,7 @@ import (
dbent "github.com/Wei-Shaw/sub2api/ent"
"github.com/Wei-Shaw/sub2api/internal/payment"
"github.com/stretchr/testify/require"
)
func TestUnionFloat(t *testing.T) {
@@ -402,3 +403,59 @@ func TestGetAvailableMethodLimitsUsesConfiguredVisibleMethodSource(t *testing.T)
})
}
}
func TestGetAvailableMethodLimitsPreservesLegacyCrossProviderBehaviorWhenVisibleMethodSourceMissing(t *testing.T) {
ctx := context.Background()
client := newPaymentConfigServiceTestClient(t)
_, err := client.PaymentProviderInstance.Create().
SetProviderKey(payment.TypeAlipay).
SetName("Official Alipay").
SetConfig("{}").
SetSupportedTypes("alipay").
SetLimits(`{"alipay":{"singleMin":10,"singleMax":100}}`).
SetEnabled(true).
Save(ctx)
require.NoError(t, err)
_, err = client.PaymentProviderInstance.Create().
SetProviderKey(payment.TypeEasyPay).
SetName("EasyPay Mixed").
SetConfig("{}").
SetSupportedTypes("alipay,wxpay").
SetLimits(`{"alipay":{"singleMin":20,"singleMax":200},"wxpay":{"singleMin":40,"singleMax":400}}`).
SetEnabled(true).
Save(ctx)
require.NoError(t, err)
_, err = client.PaymentProviderInstance.Create().
SetProviderKey(payment.TypeWxpay).
SetName("Official WeChat").
SetConfig("{}").
SetSupportedTypes("wxpay").
SetLimits(`{"wxpay":{"singleMin":30,"singleMax":300}}`).
SetEnabled(true).
Save(ctx)
require.NoError(t, err)
svc := &PaymentConfigService{
entClient: client,
settingRepo: &paymentConfigSettingRepoStub{values: map[string]string{}},
}
resp, err := svc.GetAvailableMethodLimits(ctx)
require.NoError(t, err)
alipayLimits, ok := resp.Methods[payment.TypeAlipay]
require.True(t, ok, "expected alipay limits to remain visible")
require.Equal(t, 10.0, alipayLimits.SingleMin)
require.Equal(t, 200.0, alipayLimits.SingleMax)
wxpayLimits, ok := resp.Methods[payment.TypeWxpay]
require.True(t, ok, "expected wxpay limits to remain visible")
require.Equal(t, 30.0, wxpayLimits.SingleMin)
require.Equal(t, 400.0, wxpayLimits.SingleMax)
require.Equal(t, 10.0, resp.GlobalMin)
require.Equal(t, 400.0, resp.GlobalMax)
}

View File

@@ -586,7 +586,60 @@ func TestVisibleMethodLoadBalancerUsesConfiguredSourceWhenMultipleProvidersEnabl
}
}
func TestVisibleMethodLoadBalancerRejectsMissingOrInvalidSourceWhenMultipleProvidersEnabled(t *testing.T) {
func TestVisibleMethodLoadBalancerPreservesLegacyCrossProviderRoutingWhenSourceMissing(t *testing.T) {
t.Parallel()
ctx := context.Background()
client := newPaymentConfigServiceTestClient(t)
_, err := client.PaymentProviderInstance.Create().
SetProviderKey(payment.TypeAlipay).
SetName("Official Alipay").
SetConfig("{}").
SetSupportedTypes("alipay").
SetEnabled(true).
SetSortOrder(1).
Save(ctx)
if err != nil {
t.Fatalf("create official provider: %v", err)
}
_, err = client.PaymentProviderInstance.Create().
SetProviderKey(payment.TypeEasyPay).
SetName("EasyPay Alipay").
SetConfig("{}").
SetSupportedTypes("alipay").
SetEnabled(true).
SetSortOrder(2).
Save(ctx)
if err != nil {
t.Fatalf("create easypay provider: %v", err)
}
inner := &captureLoadBalancer{}
configService := &PaymentConfigService{
entClient: client,
settingRepo: &paymentConfigSettingRepoStub{
values: map[string]string{
visibleMethodSourceSettingKey(payment.TypeAlipay): "",
},
},
}
lb := newVisibleMethodLoadBalancer(inner, configService)
_, err = lb.SelectInstance(ctx, "", payment.TypeAlipay, payment.StrategyRoundRobin, 9.9)
if err != nil {
t.Fatalf("SelectInstance returned error: %v", err)
}
if inner.lastProviderKey != "" {
t.Fatalf("lastProviderKey = %q, want legacy cross-provider empty key", inner.lastProviderKey)
}
if inner.lastPaymentType != payment.TypeAlipay {
t.Fatalf("lastPaymentType = %q, want %q", inner.lastPaymentType, payment.TypeAlipay)
}
}
func TestVisibleMethodLoadBalancerRejectsInvalidSourceWhenMultipleProvidersEnabled(t *testing.T) {
t.Parallel()
tests := []struct {
@@ -595,12 +648,6 @@ func TestVisibleMethodLoadBalancerRejectsMissingOrInvalidSourceWhenMultipleProvi
sourceValue string
wantMessage string
}{
{
name: "missing alipay source",
method: payment.TypeAlipay,
sourceValue: "",
wantMessage: "alipay source is required when the visible method is enabled",
},
{
name: "invalid wxpay source",
method: payment.TypeWxpay,

View File

@@ -2,6 +2,7 @@ package service
import (
"context"
"errors"
"fmt"
"strings"
@@ -166,15 +167,21 @@ func (s *PaymentConfigService) resolveVisibleMethodSourceProviderKey(ctx context
if s != nil && s.settingRepo != nil && sourceKey != "" {
value, err := s.settingRepo.GetValue(ctx, sourceKey)
if err != nil {
return "", fmt.Errorf("get %s: %w", sourceKey, err)
if !errors.Is(err, ErrSettingNotFound) {
return "", fmt.Errorf("get %s: %w", sourceKey, err)
}
} else {
rawSource = value
}
rawSource = value
}
normalizedSource, err := normalizeVisibleMethodSettingSource(method, rawSource, true)
if err != nil {
return "", err
}
if normalizedSource == "" {
return "", nil
}
providerKey, ok := VisibleMethodProviderKeyForSource(method, normalizedSource)
if !ok {
return "", infraerrors.BadRequest(
@@ -200,6 +207,9 @@ func (s *PaymentConfigService) resolveVisibleMethodProviderKey(
if err != nil {
return "", err
}
if providerKey == "" {
return "", nil
}
selected := selectVisibleMethodInstanceByProviderKey(matching, providerKey)
if selected == nil {
return "", infraerrors.BadRequest(
@@ -237,5 +247,11 @@ func (s *PaymentConfigService) resolveEnabledVisibleMethodInstance(
if err != nil {
return nil, err
}
if providerKey == "" {
if len(matching) == 0 {
return nil, nil
}
return &dbent.PaymentProviderInstance{ProviderKey: ""}, nil
}
return selectVisibleMethodInstanceByProviderKey(matching, providerKey), nil
}

View File

@@ -282,7 +282,19 @@ func mergeWeChatConnectCapabilitySettings(settings map[string]string, base confi
mobileConfigured := hasMobile && strings.TrimSpace(rawMobile) != ""
if openConfigured || mpConfigured || mobileConfigured {
return parseWeChatConnectCapabilitySettings(settings, enabled, mode)
openEnabled := strings.TrimSpace(rawOpen) == "true"
mpEnabled := strings.TrimSpace(rawMP) == "true"
mobileEnabled := strings.TrimSpace(rawMobile) == "true"
_, enabledConfigured := settings[SettingKeyWeChatConnectEnabled]
if !enabledConfigured &&
enabled &&
!openEnabled &&
!mpEnabled &&
!mobileEnabled &&
(base.OpenEnabled || base.MPEnabled || base.MobileEnabled) {
return base.OpenEnabled, base.MPEnabled, base.MobileEnabled
}
return openEnabled, mpEnabled, mobileEnabled
}
if !enabled {
return false, false, false
@@ -1921,14 +1933,9 @@ func isFalseSettingValue(value string) bool {
}
func normalizeVisibleMethodSettingSource(method, source string, enabled bool) (string, error) {
_ = enabled
source = strings.TrimSpace(source)
if source == "" {
if enabled {
return "", infraerrors.BadRequest(
"INVALID_PAYMENT_VISIBLE_METHOD_SOURCE",
fmt.Sprintf("%s source is required when the visible method is enabled", method),
)
}
return "", nil
}

View File

@@ -109,6 +109,36 @@ func TestSettingService_GetWeChatConnectOAuthConfig_FallsBackToConfigWhenDatabas
require.Empty(t, got.RedirectURL)
}
func TestSettingService_GetWeChatConnectOAuthConfig_IgnoresSyntheticDisabledCapabilitiesFromMigration118(t *testing.T) {
repo := &settingWeChatRepoStub{
values: map[string]string{
SettingKeyWeChatConnectOpenEnabled: "false",
SettingKeyWeChatConnectMPEnabled: "false",
},
}
svc := NewSettingService(repo, &config.Config{
WeChat: config.WeChatConnectConfig{
Enabled: true,
OpenEnabled: true,
MPEnabled: true,
Mode: "open",
OpenAppID: "wx-open-config",
OpenAppSecret: "wx-open-secret",
MPAppID: "wx-mp-config",
MPAppSecret: "wx-mp-secret",
FrontendRedirectURL: "/auth/wechat/config-callback",
},
})
got, err := svc.GetWeChatConnectOAuthConfig(context.Background())
require.NoError(t, err)
require.True(t, got.Enabled)
require.True(t, got.OpenEnabled)
require.True(t, got.MPEnabled)
require.Equal(t, "wx-open-config", got.AppIDForMode("open"))
require.Equal(t, "wx-mp-config", got.AppIDForMode("mp"))
}
func TestSettingService_ParseSettings_FallsBackToConfigForWeChatAdminView(t *testing.T) {
svc := NewSettingService(&settingWeChatRepoStub{values: map[string]string{}}, &config.Config{
WeChat: config.WeChatConnectConfig{