Files
sendico/api/payments/quotation/internal/service/plan/plan_builder_gateways.go

143 lines
4.5 KiB
Go

package plan
import (
"context"
"sort"
"strings"
"github.com/shopspring/decimal"
"github.com/tech/sendico/payments/storage/model"
"github.com/tech/sendico/pkg/merrors"
"github.com/tech/sendico/pkg/mlogger"
paymenttypes "github.com/tech/sendico/pkg/payments/types"
"go.uber.org/zap"
)
func ensureGatewayForAction(ctx context.Context, logger mlogger.Logger, registry GatewayRegistry, cache map[model.Rail]*model.GatewayInstanceDescriptor, rail model.Rail, network string, amount *paymenttypes.Money, action model.RailOperation, instanceID string, dir sendDirection) (*model.GatewayInstanceDescriptor, error) {
if registry == nil {
return nil, merrors.InvalidArgument("plan builder: gateway registry is required")
}
if gw, ok := cache[rail]; ok && gw != nil {
if instanceID == "" || strings.EqualFold(gw.InstanceID, instanceID) {
if err := validateGatewayAction(gw, network, amount, action, dir); err != nil {
logger.Warn("Failed to validate gateway", zap.Error(err),
zap.String("instance_id", instanceID), zap.String("rail", string(rail)),
zap.String("network", network), zap.String("action", string(action)),
zap.String("direction", sendDirectionLabel(dir)), zap.Int("rails_qty", len(cache)),
)
return nil, err
}
return gw, nil
}
}
gw, err := selectGateway(ctx, registry, rail, network, amount, action, instanceID, dir)
if err != nil {
logger.Warn("Failed to select gateway", zap.Error(err),
zap.String("instance_id", instanceID), zap.String("rail", string(rail)),
zap.String("network", network), zap.String("action", string(action)),
zap.String("direction", sendDirectionLabel(dir)), zap.Int("rails_qty", len(cache)),
)
return nil, err
}
cache[rail] = gw
return gw, nil
}
func validateGatewayAction(gw *model.GatewayInstanceDescriptor, network string, amount *paymenttypes.Money, action model.RailOperation, dir sendDirection) error {
if gw == nil {
return merrors.InvalidArgument("plan builder: gateway instance is required")
}
currency := ""
amt := decimal.Zero
if amount != nil && strings.TrimSpace(amount.GetAmount()) != "" {
value, err := decimalFromMoney(amount)
if err != nil {
return err
}
amt = value
currency = strings.ToUpper(strings.TrimSpace(amount.GetCurrency()))
}
if err := model.IsGatewayEligible(gw, gw.Rail, network, currency, action, toGatewayDirection(dir), amt); err != nil {
return merrors.NoData(model.NoEligibleGatewayMessage(network, currency, action, toGatewayDirection(dir)))
}
return nil
}
type sendDirection int
const (
sendDirectionAny sendDirection = iota
sendDirectionOut
sendDirectionIn
)
func sendDirectionForRail(rail model.Rail) sendDirection {
switch rail {
case model.RailFiatOnRamp:
return sendDirectionIn
default:
return sendDirectionOut
}
}
func selectGateway(ctx context.Context, registry GatewayRegistry, rail model.Rail, network string, amount *paymenttypes.Money, action model.RailOperation, instanceID string, dir sendDirection) (*model.GatewayInstanceDescriptor, error) {
if registry == nil {
return nil, merrors.InvalidArgument("plan builder: gateway registry is required")
}
all, err := registry.List(ctx)
if err != nil {
return nil, err
}
if len(all) == 0 {
return nil, merrors.InvalidArgument("plan builder: no gateway instances available")
}
currency := ""
amt := decimal.Zero
if amount != nil && strings.TrimSpace(amount.GetAmount()) != "" {
amt, err = decimalFromMoney(amount)
if err != nil {
return nil, err
}
currency = strings.ToUpper(strings.TrimSpace(amount.GetCurrency()))
}
network = strings.ToUpper(strings.TrimSpace(network))
eligible := make([]*model.GatewayInstanceDescriptor, 0)
for _, gw := range all {
if err := model.IsGatewayEligible(gw, rail, network, currency, action, toGatewayDirection(dir), amt); err != nil {
continue
}
eligible = append(eligible, gw)
}
if len(eligible) == 0 {
return nil, merrors.NoData(model.NoEligibleGatewayMessage(network, currency, action, toGatewayDirection(dir)))
}
sort.Slice(eligible, func(i, j int) bool {
return eligible[i].ID < eligible[j].ID
})
if instanceID != "" {
for _, gw := range eligible {
if strings.EqualFold(strings.TrimSpace(gw.InstanceID), instanceID) {
return gw, nil
}
}
}
return eligible[0], nil
}
func sendDirectionLabel(dir sendDirection) string {
return toGatewayDirection(dir).String()
}
func toGatewayDirection(dir sendDirection) model.GatewayDirection {
switch dir {
case sendDirectionOut:
return model.GatewayDirectionOut
case sendDirectionIn:
return model.GatewayDirectionIn
default:
return model.GatewayDirectionAny
}
}