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")
|
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"
|
"time"
|
||||||
|
|
||||||
"github.com/Wei-Shaw/sub2api/internal/payment"
|
"github.com/Wei-Shaw/sub2api/internal/payment"
|
||||||
|
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNormalizeVisibleMethods(t *testing.T) {
|
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) {
|
func TestVisibleMethodLoadBalancerRejectsMissingEnabledVisibleMethodProvider(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,19 @@ func filterEnabledVisibleMethodInstances(instances []*dbent.PaymentProviderInsta
|
|||||||
return filtered
|
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 {
|
func buildPaymentProviderConflictError(method string, conflicting *dbent.PaymentProviderInstance) error {
|
||||||
metadata := map[string]string{
|
metadata := map[string]string{
|
||||||
"payment_method": NormalizeVisibleMethod(method),
|
"payment_method": NormalizeVisibleMethod(method),
|
||||||
@@ -133,6 +146,32 @@ func (s *PaymentConfigService) validateVisibleMethodEnablementConflicts(
|
|||||||
return nil
|
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(
|
func (s *PaymentConfigService) resolveEnabledVisibleMethodInstance(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
method string,
|
method string,
|
||||||
@@ -161,6 +200,17 @@ func (s *PaymentConfigService) resolveEnabledVisibleMethodInstance(
|
|||||||
case 1:
|
case 1:
|
||||||
return matching[0], nil
|
return matching[0], nil
|
||||||
default:
|
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