feat: add Airwallex payments and multi-currency support
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
dbent "github.com/Wei-Shaw/sub2api/ent"
|
||||
"github.com/Wei-Shaw/sub2api/ent/paymentproviderinstance"
|
||||
"github.com/Wei-Shaw/sub2api/internal/payment"
|
||||
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
||||
)
|
||||
|
||||
// GetAvailableMethodLimits collects all payment types from enabled provider
|
||||
@@ -25,7 +26,12 @@ func (s *PaymentConfigService) GetAvailableMethodLimits(ctx context.Context) (*M
|
||||
Methods: make(map[string]MethodLimits, len(typeInstances)),
|
||||
}
|
||||
for pt, insts := range typeInstances {
|
||||
currency, ok := s.pcAggregateMethodCurrency(insts)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
ml := pcAggregateMethodLimits(pt, insts)
|
||||
ml.Currency = currency
|
||||
resp.Methods[ml.PaymentType] = ml
|
||||
}
|
||||
resp.GlobalMin, resp.GlobalMax = pcComputeGlobalRange(resp.Methods)
|
||||
@@ -82,11 +88,81 @@ func (s *PaymentConfigService) GetMethodLimits(ctx context.Context, types []stri
|
||||
matching = append(matching, inst)
|
||||
}
|
||||
}
|
||||
result = append(result, pcAggregateMethodLimits(pt, matching))
|
||||
currency, ok := s.pcAggregateMethodCurrency(matching)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
ml := pcAggregateMethodLimits(pt, matching)
|
||||
ml.Currency = currency
|
||||
result = append(result, ml)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *PaymentConfigService) ValidateMethodCurrencyConsistency(ctx context.Context, paymentType string) (string, error) {
|
||||
method := NormalizeVisibleMethod(paymentType)
|
||||
if method == "" || s == nil || s.entClient == nil {
|
||||
return payment.DefaultPaymentCurrency, nil
|
||||
}
|
||||
|
||||
instances, err := s.entClient.PaymentProviderInstance.Query().
|
||||
Where(paymentproviderinstance.EnabledEQ(true)).All(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("query provider instances: %w", err)
|
||||
}
|
||||
|
||||
typeInstances := pcGroupByPaymentType(instances)
|
||||
typeInstances = s.pcApplyEnabledVisibleMethodInstances(ctx, typeInstances, instances)
|
||||
matching := typeInstances[method]
|
||||
if len(matching) == 0 {
|
||||
return payment.DefaultPaymentCurrency, nil
|
||||
}
|
||||
|
||||
currency, ok := s.pcAggregateMethodCurrency(matching)
|
||||
if !ok {
|
||||
return "", infraerrors.ServiceUnavailable(
|
||||
"PAYMENT_METHOD_CURRENCY_CONFLICT",
|
||||
"payment method has enabled provider instances with mixed currencies",
|
||||
).WithMetadata(map[string]string{"payment_type": method})
|
||||
}
|
||||
return currency, nil
|
||||
}
|
||||
|
||||
func (s *PaymentConfigService) pcAggregateMethodCurrency(instances []*dbent.PaymentProviderInstance) (string, bool) {
|
||||
currency := ""
|
||||
for _, inst := range instances {
|
||||
next := s.pcInstancePaymentCurrency(inst)
|
||||
if next == "" {
|
||||
continue
|
||||
}
|
||||
if currency == "" {
|
||||
currency = next
|
||||
continue
|
||||
}
|
||||
if currency != next {
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
if currency == "" {
|
||||
return payment.DefaultPaymentCurrency, true
|
||||
}
|
||||
return currency, true
|
||||
}
|
||||
|
||||
func (s *PaymentConfigService) pcInstancePaymentCurrency(inst *dbent.PaymentProviderInstance) string {
|
||||
if inst == nil {
|
||||
return payment.DefaultPaymentCurrency
|
||||
}
|
||||
cfg := map[string]string{}
|
||||
if s != nil {
|
||||
decrypted, err := s.decryptConfig(inst.Config)
|
||||
if err == nil && decrypted != nil {
|
||||
cfg = decrypted
|
||||
}
|
||||
}
|
||||
return paymentProviderConfigCurrency(inst.ProviderKey, cfg)
|
||||
}
|
||||
|
||||
// pcGroupByPaymentType groups instances by user-facing payment type.
|
||||
// For Stripe providers, ALL sub-types (card, link, alipay, wxpay) map to "stripe"
|
||||
// because the user sees a single "Stripe" button, not individual sub-methods.
|
||||
|
||||
Reference in New Issue
Block a user