Merge pull request #1610 from touwaeriol/fix/alipay-wxpay-type-mapping

fix(payment): register Alipay/Wxpay providers for base payment types
This commit is contained in:
Wesley Liddick
2026-04-13 21:44:19 +08:00
committed by GitHub
5 changed files with 22 additions and 19 deletions

View File

@@ -94,17 +94,21 @@ func (lb *DefaultLoadBalancer) SelectInstance(
return lb.buildSelection(selected.inst) return lb.buildSelection(selected.inst)
} }
// queryEnabledInstances returns enabled instances for providerKey that support paymentType. // queryEnabledInstances returns enabled instances that support paymentType.
// When providerKey is non-empty, only instances with that provider key are considered.
// When providerKey is empty, instances across all providers are considered,
// enabling cross-provider load balancing (e.g. EasyPay + Alipay direct for "alipay").
func (lb *DefaultLoadBalancer) queryEnabledInstances( func (lb *DefaultLoadBalancer) queryEnabledInstances(
ctx context.Context, ctx context.Context,
providerKey string, providerKey string,
paymentType PaymentType, paymentType PaymentType,
) ([]*dbent.PaymentProviderInstance, error) { ) ([]*dbent.PaymentProviderInstance, error) {
instances, err := lb.db.PaymentProviderInstance.Query(). query := lb.db.PaymentProviderInstance.Query().
Where( Where(paymentproviderinstance.Enabled(true))
paymentproviderinstance.ProviderKey(providerKey), if providerKey != "" {
paymentproviderinstance.Enabled(true), query = query.Where(paymentproviderinstance.ProviderKey(providerKey))
). }
instances, err := query.
Order(dbent.Asc(paymentproviderinstance.FieldSortOrder)). Order(dbent.Asc(paymentproviderinstance.FieldSortOrder)).
All(ctx) All(ctx)
if err != nil { if err != nil {
@@ -113,12 +117,12 @@ func (lb *DefaultLoadBalancer) queryEnabledInstances(
var matched []*dbent.PaymentProviderInstance var matched []*dbent.PaymentProviderInstance
for _, inst := range instances { for _, inst := range instances {
if paymentType == providerKey || InstanceSupportsType(inst.SupportedTypes, paymentType) { if InstanceSupportsType(inst.SupportedTypes, paymentType) {
matched = append(matched, inst) matched = append(matched, inst)
} }
} }
if len(matched) == 0 { if len(matched) == 0 {
return nil, fmt.Errorf("no enabled instance for provider %s type %s", providerKey, paymentType) return nil, fmt.Errorf("no enabled instance for payment type %s", paymentType)
} }
return matched, nil return matched, nil
} }
@@ -258,6 +262,7 @@ func (lb *DefaultLoadBalancer) buildSelection(selected *dbent.PaymentProviderIns
return &InstanceSelection{ return &InstanceSelection{
InstanceID: fmt.Sprintf("%d", selected.ID), InstanceID: fmt.Sprintf("%d", selected.ID),
ProviderKey: selected.ProviderKey,
Config: config, Config: config,
SupportedTypes: selected.SupportedTypes, SupportedTypes: selected.SupportedTypes,
PaymentMode: selected.PaymentMode, PaymentMode: selected.PaymentMode,

View File

@@ -76,7 +76,7 @@ func (a *Alipay) getClient() (*alipay.Client, error) {
func (a *Alipay) Name() string { return "Alipay" } func (a *Alipay) Name() string { return "Alipay" }
func (a *Alipay) ProviderKey() string { return payment.TypeAlipay } func (a *Alipay) ProviderKey() string { return payment.TypeAlipay }
func (a *Alipay) SupportedTypes() []payment.PaymentType { func (a *Alipay) SupportedTypes() []payment.PaymentType {
return []payment.PaymentType{payment.TypeAlipayDirect} return []payment.PaymentType{payment.TypeAlipay}
} }
// CreatePayment creates an Alipay payment page URL. // CreatePayment creates an Alipay payment page URL.

View File

@@ -72,7 +72,7 @@ func NewWxpay(instanceID string, config map[string]string) (*Wxpay, error) {
func (w *Wxpay) Name() string { return "Wxpay" } func (w *Wxpay) Name() string { return "Wxpay" }
func (w *Wxpay) ProviderKey() string { return payment.TypeWxpay } func (w *Wxpay) ProviderKey() string { return payment.TypeWxpay }
func (w *Wxpay) SupportedTypes() []payment.PaymentType { func (w *Wxpay) SupportedTypes() []payment.PaymentType {
return []payment.PaymentType{payment.TypeWxpayDirect} return []payment.PaymentType{payment.TypeWxpay}
} }
func formatPEM(key, keyType string) string { func formatPEM(key, keyType string) string {

View File

@@ -148,6 +148,7 @@ type RefundResponse struct {
// InstanceSelection holds the selected provider instance and its decrypted config. // InstanceSelection holds the selected provider instance and its decrypted config.
type InstanceSelection struct { type InstanceSelection struct {
InstanceID string InstanceID string
ProviderKey string // Provider key of the selected instance (e.g. "alipay", "easypay")
Config map[string]string Config map[string]string
SupportedTypes string // Comma-separated list of supported payment types from the instance SupportedTypes string // Comma-separated list of supported payment types from the instance
PaymentMode string // Payment display mode: "qrcode", "redirect", "popup" PaymentMode string // Payment display mode: "qrcode", "redirect", "popup"

View File

@@ -189,19 +189,16 @@ func (s *PaymentService) checkDailyLimit(ctx context.Context, tx *dbent.Tx, user
} }
func (s *PaymentService) invokeProvider(ctx context.Context, order *dbent.PaymentOrder, req CreateOrderRequest, cfg *PaymentConfig, payAmountStr string, payAmount float64, plan *dbent.SubscriptionPlan) (*CreateOrderResponse, error) { func (s *PaymentService) invokeProvider(ctx context.Context, order *dbent.PaymentOrder, req CreateOrderRequest, cfg *PaymentConfig, payAmountStr string, payAmount float64, plan *dbent.SubscriptionPlan) (*CreateOrderResponse, error) {
s.EnsureProviders(ctx) // Select an instance across all providers that support the requested payment type.
providerKey := s.registry.GetProviderKey(req.PaymentType) // This enables cross-provider load balancing (e.g. EasyPay + Alipay direct for "alipay").
if providerKey == "" { sel, err := s.loadBalancer.SelectInstance(ctx, "", req.PaymentType, payment.Strategy(cfg.LoadBalanceStrategy), payAmount)
return nil, infraerrors.ServiceUnavailable("PAYMENT_GATEWAY_ERROR", fmt.Sprintf("payment method (%s) is not configured", req.PaymentType))
}
sel, err := s.loadBalancer.SelectInstance(ctx, providerKey, req.PaymentType, payment.Strategy(cfg.LoadBalanceStrategy), payAmount)
if err != nil { if err != nil {
return nil, fmt.Errorf("select provider instance: %w", err) return nil, infraerrors.ServiceUnavailable("PAYMENT_GATEWAY_ERROR", fmt.Sprintf("payment method (%s) is not configured", req.PaymentType))
} }
if sel == nil { if sel == nil {
return nil, infraerrors.TooManyRequests("NO_AVAILABLE_INSTANCE", "no available payment instance") return nil, infraerrors.TooManyRequests("NO_AVAILABLE_INSTANCE", "no available payment instance")
} }
prov, err := provider.CreateProvider(providerKey, sel.InstanceID, sel.Config) prov, err := provider.CreateProvider(sel.ProviderKey, sel.InstanceID, sel.Config)
if err != nil { if err != nil {
return nil, infraerrors.ServiceUnavailable("PAYMENT_GATEWAY_ERROR", "payment method is temporarily unavailable") return nil, infraerrors.ServiceUnavailable("PAYMENT_GATEWAY_ERROR", "payment method is temporarily unavailable")
} }
@@ -209,7 +206,7 @@ func (s *PaymentService) invokeProvider(ctx context.Context, order *dbent.Paymen
outTradeNo := order.OutTradeNo outTradeNo := order.OutTradeNo
pr, err := prov.CreatePayment(ctx, payment.CreatePaymentRequest{OrderID: outTradeNo, Amount: payAmountStr, PaymentType: req.PaymentType, Subject: subject, ClientIP: req.ClientIP, IsMobile: req.IsMobile, InstanceSubMethods: sel.SupportedTypes}) pr, err := prov.CreatePayment(ctx, payment.CreatePaymentRequest{OrderID: outTradeNo, Amount: payAmountStr, PaymentType: req.PaymentType, Subject: subject, ClientIP: req.ClientIP, IsMobile: req.IsMobile, InstanceSubMethods: sel.SupportedTypes})
if err != nil { if err != nil {
slog.Error("[PaymentService] CreatePayment failed", "provider", providerKey, "instance", sel.InstanceID, "error", err) slog.Error("[PaymentService] CreatePayment failed", "provider", sel.ProviderKey, "instance", sel.InstanceID, "error", err)
return nil, infraerrors.ServiceUnavailable("PAYMENT_GATEWAY_ERROR", fmt.Sprintf("payment gateway error: %s", err.Error())) return nil, infraerrors.ServiceUnavailable("PAYMENT_GATEWAY_ERROR", fmt.Sprintf("payment gateway error: %s", err.Error()))
} }
_, err = s.entClient.PaymentOrder.UpdateOneID(order.ID).SetNillablePaymentTradeNo(psNilIfEmpty(pr.TradeNo)).SetNillablePayURL(psNilIfEmpty(pr.PayURL)).SetNillableQrCode(psNilIfEmpty(pr.QRCode)).SetNillableProviderInstanceID(psNilIfEmpty(sel.InstanceID)).Save(ctx) _, err = s.entClient.PaymentOrder.UpdateOneID(order.ID).SetNillablePaymentTradeNo(psNilIfEmpty(pr.TradeNo)).SetNillablePayURL(psNilIfEmpty(pr.PayURL)).SetNillableQrCode(psNilIfEmpty(pr.QRCode)).SetNillableProviderInstanceID(psNilIfEmpty(sel.InstanceID)).Save(ctx)