fix(payment): respect configured visible method source
This commit is contained in:
@@ -31,3 +31,68 @@ func TestUsesOfficialWxpayVisibleMethodDerivesFromEnabledProviderInstance(t *tes
|
||||
t.Fatal("expected official wxpay visible method to be detected from enabled provider instance")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUsesOfficialWxpayVisibleMethodRespectsConfiguredSourceWhenMultipleProvidersEnabled(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
source string
|
||||
wantOfficial bool
|
||||
}{
|
||||
{
|
||||
name: "official source selected",
|
||||
source: VisibleMethodSourceOfficialWechat,
|
||||
wantOfficial: true,
|
||||
},
|
||||
{
|
||||
name: "easypay source selected",
|
||||
source: VisibleMethodSourceEasyPayWechat,
|
||||
wantOfficial: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client := newPaymentConfigServiceTestClient(t)
|
||||
|
||||
_, err := client.PaymentProviderInstance.Create().
|
||||
SetProviderKey(payment.TypeWxpay).
|
||||
SetName("Official WeChat").
|
||||
SetConfig("{}").
|
||||
SetSupportedTypes("wxpay").
|
||||
SetEnabled(true).
|
||||
SetSortOrder(1).
|
||||
Save(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("create official wxpay instance: %v", err)
|
||||
}
|
||||
|
||||
_, err = client.PaymentProviderInstance.Create().
|
||||
SetProviderKey(payment.TypeEasyPay).
|
||||
SetName("EasyPay WeChat").
|
||||
SetConfig("{}").
|
||||
SetSupportedTypes("wxpay").
|
||||
SetEnabled(true).
|
||||
SetSortOrder(2).
|
||||
Save(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("create easypay wxpay instance: %v", err)
|
||||
}
|
||||
|
||||
svc := &PaymentService{
|
||||
configService: &PaymentConfigService{
|
||||
entClient: client,
|
||||
settingRepo: &paymentConfigSettingRepoStub{
|
||||
values: map[string]string{
|
||||
SettingPaymentVisibleMethodWxpaySource: tt.source,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if got := svc.usesOfficialWxpayVisibleMethod(ctx); got != tt.wantOfficial {
|
||||
t.Fatalf("usesOfficialWxpayVisibleMethod() = %v, want %v", got, tt.wantOfficial)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/payment"
|
||||
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
||||
)
|
||||
|
||||
func TestNormalizeVisibleMethods(t *testing.T) {
|
||||
@@ -419,6 +420,211 @@ func TestVisibleMethodLoadBalancerUsesEnabledProviderInstance(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestVisibleMethodLoadBalancerUsesConfiguredSourceWhenMultipleProvidersEnabled(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
method payment.PaymentType
|
||||
officialName string
|
||||
officialTypes string
|
||||
easyPayName string
|
||||
easyPayTypes string
|
||||
sourceSetting string
|
||||
wantProvider string
|
||||
}{
|
||||
{
|
||||
name: "alipay uses official source",
|
||||
method: payment.TypeAlipay,
|
||||
officialName: "Official Alipay",
|
||||
officialTypes: "alipay",
|
||||
easyPayName: "EasyPay Alipay",
|
||||
easyPayTypes: "alipay",
|
||||
sourceSetting: VisibleMethodSourceOfficialAlipay,
|
||||
wantProvider: payment.TypeAlipay,
|
||||
},
|
||||
{
|
||||
name: "alipay uses easypay source",
|
||||
method: payment.TypeAlipay,
|
||||
officialName: "Official Alipay",
|
||||
officialTypes: "alipay",
|
||||
easyPayName: "EasyPay Alipay",
|
||||
easyPayTypes: "alipay",
|
||||
sourceSetting: VisibleMethodSourceEasyPayAlipay,
|
||||
wantProvider: payment.TypeEasyPay,
|
||||
},
|
||||
{
|
||||
name: "wxpay uses official source",
|
||||
method: payment.TypeWxpay,
|
||||
officialName: "Official WeChat",
|
||||
officialTypes: "wxpay",
|
||||
easyPayName: "EasyPay WeChat",
|
||||
easyPayTypes: "wxpay",
|
||||
sourceSetting: VisibleMethodSourceOfficialWechat,
|
||||
wantProvider: payment.TypeWxpay,
|
||||
},
|
||||
{
|
||||
name: "wxpay uses easypay source",
|
||||
method: payment.TypeWxpay,
|
||||
officialName: "Official WeChat",
|
||||
officialTypes: "wxpay",
|
||||
easyPayName: "EasyPay WeChat",
|
||||
easyPayTypes: "wxpay",
|
||||
sourceSetting: VisibleMethodSourceEasyPayWechat,
|
||||
wantProvider: payment.TypeEasyPay,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
client := newPaymentConfigServiceTestClient(t)
|
||||
|
||||
officialProviderKey := payment.TypeAlipay
|
||||
if tt.method == payment.TypeWxpay {
|
||||
officialProviderKey = payment.TypeWxpay
|
||||
}
|
||||
|
||||
_, err = client.PaymentProviderInstance.Create().
|
||||
SetProviderKey(officialProviderKey).
|
||||
SetName(tt.officialName).
|
||||
SetConfig("{}").
|
||||
SetSupportedTypes(tt.officialTypes).
|
||||
SetEnabled(true).
|
||||
SetSortOrder(1).
|
||||
Save(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("create official provider: %v", err)
|
||||
}
|
||||
|
||||
_, err = client.PaymentProviderInstance.Create().
|
||||
SetProviderKey(payment.TypeEasyPay).
|
||||
SetName(tt.easyPayName).
|
||||
SetConfig("{}").
|
||||
SetSupportedTypes(tt.easyPayTypes).
|
||||
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(tt.method): tt.sourceSetting,
|
||||
},
|
||||
},
|
||||
}
|
||||
lb := newVisibleMethodLoadBalancer(inner, configService)
|
||||
|
||||
_, err = lb.SelectInstance(ctx, "", tt.method, payment.StrategyRoundRobin, 12.5)
|
||||
if err != nil {
|
||||
t.Fatalf("SelectInstance returned error: %v", err)
|
||||
}
|
||||
if inner.lastProviderKey != tt.wantProvider {
|
||||
t.Fatalf("lastProviderKey = %q, want %q", inner.lastProviderKey, tt.wantProvider)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVisibleMethodLoadBalancerRejectsMissingOrInvalidSourceWhenMultipleProvidersEnabled(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
method payment.PaymentType
|
||||
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,
|
||||
sourceValue: "stripe",
|
||||
wantMessage: "wxpay source must be one of the supported payment providers",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
client := newPaymentConfigServiceTestClient(t)
|
||||
|
||||
officialProviderKey := payment.TypeAlipay
|
||||
officialSupportedTypes := "alipay"
|
||||
officialName := "Official Alipay"
|
||||
easyPaySupportedTypes := "alipay"
|
||||
easyPayName := "EasyPay Alipay"
|
||||
if tt.method == payment.TypeWxpay {
|
||||
officialProviderKey = payment.TypeWxpay
|
||||
officialSupportedTypes = "wxpay"
|
||||
officialName = "Official WeChat"
|
||||
easyPaySupportedTypes = "wxpay"
|
||||
easyPayName = "EasyPay WeChat"
|
||||
}
|
||||
|
||||
_, err := client.PaymentProviderInstance.Create().
|
||||
SetProviderKey(officialProviderKey).
|
||||
SetName(officialName).
|
||||
SetConfig("{}").
|
||||
SetSupportedTypes(officialSupportedTypes).
|
||||
SetEnabled(true).
|
||||
SetSortOrder(1).
|
||||
Save(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("create official provider: %v", err)
|
||||
}
|
||||
|
||||
_, err = client.PaymentProviderInstance.Create().
|
||||
SetProviderKey(payment.TypeEasyPay).
|
||||
SetName(easyPayName).
|
||||
SetConfig("{}").
|
||||
SetSupportedTypes(easyPaySupportedTypes).
|
||||
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(tt.method): tt.sourceValue,
|
||||
},
|
||||
},
|
||||
}
|
||||
lb := newVisibleMethodLoadBalancer(inner, configService)
|
||||
|
||||
_, err = lb.SelectInstance(ctx, "", tt.method, payment.StrategyRoundRobin, 9.9)
|
||||
if err == nil {
|
||||
t.Fatal("SelectInstance should reject invalid visible method source configuration")
|
||||
}
|
||||
if infraerrors.Reason(err) != "INVALID_PAYMENT_VISIBLE_METHOD_SOURCE" {
|
||||
t.Fatalf("Reason(err) = %q, want %q", infraerrors.Reason(err), "INVALID_PAYMENT_VISIBLE_METHOD_SOURCE")
|
||||
}
|
||||
if infraerrors.Message(err) != tt.wantMessage {
|
||||
t.Fatalf("Message(err) = %q, want %q", infraerrors.Message(err), tt.wantMessage)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVisibleMethodLoadBalancerRejectsMissingEnabledVisibleMethodProvider(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -82,6 +82,19 @@ func filterEnabledVisibleMethodInstances(instances []*dbent.PaymentProviderInsta
|
||||
return filtered
|
||||
}
|
||||
|
||||
func selectVisibleMethodInstanceByProviderKey(instances []*dbent.PaymentProviderInstance, providerKey string) *dbent.PaymentProviderInstance {
|
||||
providerKey = strings.TrimSpace(providerKey)
|
||||
if providerKey == "" {
|
||||
return nil
|
||||
}
|
||||
for _, inst := range instances {
|
||||
if strings.EqualFold(strings.TrimSpace(inst.ProviderKey), providerKey) {
|
||||
return inst
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildPaymentProviderConflictError(method string, conflicting *dbent.PaymentProviderInstance) error {
|
||||
metadata := map[string]string{
|
||||
"payment_method": NormalizeVisibleMethod(method),
|
||||
@@ -133,6 +146,32 @@ func (s *PaymentConfigService) validateVisibleMethodEnablementConflicts(
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *PaymentConfigService) resolveVisibleMethodSourceProviderKey(ctx context.Context, method string) (string, error) {
|
||||
method = NormalizeVisibleMethod(method)
|
||||
sourceKey := visibleMethodSourceSettingKey(method)
|
||||
rawSource := ""
|
||||
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)
|
||||
}
|
||||
rawSource = value
|
||||
}
|
||||
|
||||
normalizedSource, err := normalizeVisibleMethodSettingSource(method, rawSource, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
providerKey, ok := VisibleMethodProviderKeyForSource(method, normalizedSource)
|
||||
if !ok {
|
||||
return "", infraerrors.BadRequest(
|
||||
"INVALID_PAYMENT_VISIBLE_METHOD_SOURCE",
|
||||
fmt.Sprintf("%s source must be one of the supported payment providers", method),
|
||||
)
|
||||
}
|
||||
return providerKey, nil
|
||||
}
|
||||
|
||||
func (s *PaymentConfigService) resolveEnabledVisibleMethodInstance(
|
||||
ctx context.Context,
|
||||
method string,
|
||||
@@ -161,6 +200,17 @@ func (s *PaymentConfigService) resolveEnabledVisibleMethodInstance(
|
||||
case 1:
|
||||
return matching[0], nil
|
||||
default:
|
||||
return nil, buildPaymentProviderConflictError(method, matching[0])
|
||||
providerKey, err := s.resolveVisibleMethodSourceProviderKey(ctx, method)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
selected := selectVisibleMethodInstanceByProviderKey(matching, providerKey)
|
||||
if selected == nil {
|
||||
return nil, infraerrors.BadRequest(
|
||||
"INVALID_PAYMENT_VISIBLE_METHOD_SOURCE",
|
||||
fmt.Sprintf("%s source has no enabled provider instance", method),
|
||||
)
|
||||
}
|
||||
return selected, nil
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user