优化结构
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
bizpayment "kra/internal/biz/payment"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Adapter is the provider boundary used by the payment repository. Provider
|
||||
@@ -15,42 +16,38 @@ type Adapter interface {
|
||||
Callback(context.Context, *bizpayment.PaymentCallback, map[string]any) (*bizpayment.PaymentResult, error)
|
||||
}
|
||||
|
||||
// New constructs the SDK-backed adapter for a configured provider.
|
||||
type adapterFactory func() Adapter
|
||||
|
||||
// adapterFactories is the single payment integration registration point.
|
||||
// Keep constructors zero-state: provider configuration belongs to each call,
|
||||
// so an adapter can never accidentally retain secrets or order data.
|
||||
var adapterFactories = map[string]adapterFactory{
|
||||
bizpayment.PaymentAlipay: func() Adapter { return &alipayAdapter{} },
|
||||
bizpayment.PaymentAlipayV3: func() Adapter { return &alipayV3Adapter{} },
|
||||
bizpayment.PaymentWechatV2: func() Adapter { return &wechatV2Adapter{} },
|
||||
bizpayment.PaymentWechatV3: func() Adapter { return &wechatV3Adapter{} },
|
||||
bizpayment.PaymentApple: func() Adapter { return &appleAdapter{} },
|
||||
bizpayment.PaymentDouyin: func() Adapter { return &douyinAdapter{} },
|
||||
bizpayment.PaymentQQ: func() Adapter { return &qqAdapter{} },
|
||||
bizpayment.PaymentAllinPay: func() Adapter { return &allinpayAdapter{} },
|
||||
bizpayment.PaymentLakala: func() Adapter { return &lakalaAdapter{} },
|
||||
bizpayment.PaymentPayPal: func() Adapter { return &paypalAdapter{} },
|
||||
bizpayment.PaymentSaobei: func() Adapter { return &saobeiAdapter{} },
|
||||
bizpayment.PaymentChinaums: func() Adapter { return newVendorAdapter(bizpayment.PaymentChinaums, vendorChinaums) },
|
||||
bizpayment.PaymentSFT: func() Adapter { return newVendorAdapter(bizpayment.PaymentSFT, vendorSFT) },
|
||||
bizpayment.PaymentSuperPay: func() Adapter { return newVendorAdapter(bizpayment.PaymentSuperPay, vendorSupperPay) },
|
||||
bizpayment.PaymentWechatGame: func() Adapter { return newVendorAdapter(bizpayment.PaymentWechatGame, vendorWechatGame) },
|
||||
bizpayment.PaymentDouyinGame: func() Adapter { return newVendorAdapter(bizpayment.PaymentDouyinGame, vendorDouyinGame) },
|
||||
}
|
||||
|
||||
// New constructs the SDK-backed adapter for a configured provider. Provider
|
||||
// identifiers are normalized at this I/O boundary so direct callers get the
|
||||
// same behavior as the configuration and business layers.
|
||||
func New(provider string) (Adapter, error) {
|
||||
switch provider {
|
||||
case bizpayment.PaymentAlipay:
|
||||
return &alipayAdapter{}, nil
|
||||
case bizpayment.PaymentAlipayV3:
|
||||
return &alipayV3Adapter{}, nil
|
||||
case bizpayment.PaymentWechatV2:
|
||||
return &wechatV2Adapter{}, nil
|
||||
case bizpayment.PaymentWechatV3:
|
||||
return &wechatV3Adapter{}, nil
|
||||
case bizpayment.PaymentApple:
|
||||
return &appleAdapter{}, nil
|
||||
case bizpayment.PaymentDouyin:
|
||||
return &douyinAdapter{}, nil
|
||||
case bizpayment.PaymentQQ:
|
||||
return &qqAdapter{}, nil
|
||||
case bizpayment.PaymentAllinPay:
|
||||
return &allinpayAdapter{}, nil
|
||||
case bizpayment.PaymentLakala:
|
||||
return &lakalaAdapter{}, nil
|
||||
case bizpayment.PaymentPayPal:
|
||||
return &paypalAdapter{}, nil
|
||||
case bizpayment.PaymentSaobei:
|
||||
return &saobeiAdapter{}, nil
|
||||
case bizpayment.PaymentChinaums:
|
||||
return newVendorAdapter(provider, vendorChinaums), nil
|
||||
case bizpayment.PaymentSFT:
|
||||
return newVendorAdapter(provider, vendorSFT), nil
|
||||
case bizpayment.PaymentSuperPay:
|
||||
return newVendorAdapter(provider, vendorSupperPay), nil
|
||||
case bizpayment.PaymentWechatGame:
|
||||
return newVendorAdapter(provider, vendorWechatGame), nil
|
||||
case bizpayment.PaymentDouyinGame:
|
||||
return newVendorAdapter(provider, vendorDouyinGame), nil
|
||||
default:
|
||||
provider = strings.ToLower(strings.TrimSpace(provider))
|
||||
factory, ok := adapterFactories[provider]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("支付渠道 %s 没有适配器", provider)
|
||||
}
|
||||
return factory(), nil
|
||||
}
|
||||
|
||||
@@ -18,3 +18,40 @@ func TestEverySupportedProviderHasAdapter(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewNormalizesProviderIdentifier(t *testing.T) {
|
||||
for _, provider := range bizpayment.SupportedPaymentProviders {
|
||||
adapter, err := New(" " + provider + " ")
|
||||
if err != nil {
|
||||
t.Fatalf("New(%q): %v", provider, err)
|
||||
}
|
||||
if adapter == nil {
|
||||
t.Fatalf("New(%q) returned nil", provider)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRejectsUnknownProvider(t *testing.T) {
|
||||
if _, err := New("unknown-provider"); err == nil {
|
||||
t.Fatal("unknown provider unexpectedly accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNonceIsUniqueForConcurrentRequests(t *testing.T) {
|
||||
const count = 1000
|
||||
values := make(chan string, count)
|
||||
for i := 0; i < count; i++ {
|
||||
go func() { values <- nonce() }()
|
||||
}
|
||||
seen := make(map[string]struct{}, count)
|
||||
for i := 0; i < count; i++ {
|
||||
value := <-values
|
||||
if value == "" {
|
||||
t.Fatal("nonce returned an empty value")
|
||||
}
|
||||
if _, exists := seen[value]; exists {
|
||||
t.Fatalf("nonce collision for %q", value)
|
||||
}
|
||||
seen[value] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@ package payment
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -570,7 +572,15 @@ func paymentHTTPClient(c map[string]any) (*http.Client, error) {
|
||||
return &http.Client{Timeout: 20 * time.Second, Transport: &http.Transport{TLSClientConfig: &tls.Config{Certificates: []tls.Certificate{cert}, RootCAs: pool, MinVersion: tls.VersionTLS12}}}, nil
|
||||
}
|
||||
|
||||
func nonce() string { return fmt.Sprintf("%d", time.Now().UnixNano()) }
|
||||
// nonce returns a compact request token. Clock-only values can collide when
|
||||
// several payment requests are prepared in the same scheduler tick.
|
||||
func nonce() string {
|
||||
var raw [8]byte
|
||||
if _, err := rand.Read(raw[:]); err == nil {
|
||||
return hex.EncodeToString(raw[:])
|
||||
}
|
||||
return fmt.Sprintf("%d", time.Now().UnixNano())
|
||||
}
|
||||
func stringOr(extra map[string]any, key, fallback string) string {
|
||||
if value, ok := extra[key].(string); ok {
|
||||
return value
|
||||
|
||||
Reference in New Issue
Block a user