Fully separated payment quotation and orchestration flows
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
package quotation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
chainclient "github.com/tech/sendico/gateway/chain/client"
|
||||
"github.com/tech/sendico/payments/quotation/internal/service/plan"
|
||||
"github.com/tech/sendico/payments/storage/model"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
paymenttypes "github.com/tech/sendico/pkg/payments/types"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (s *Service) resolveChainGatewayClient(ctx context.Context, network string, amount *paymenttypes.Money, actions []model.RailOperation, instanceID string, paymentRef string) (chainclient.Client, *model.GatewayInstanceDescriptor, error) {
|
||||
if s.deps.gatewayRegistry != nil && s.deps.gatewayInvokeResolver != nil {
|
||||
entry, err := selectGatewayForActions(ctx, s.deps.gatewayRegistry, model.RailCrypto, network, amount, actions, instanceID, sendDirectionForRail(model.RailCrypto))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
invokeURI := strings.TrimSpace(entry.InvokeURI)
|
||||
if invokeURI == "" {
|
||||
return nil, nil, merrors.InvalidArgument("chain gateway: invoke uri is required")
|
||||
}
|
||||
client, err := s.deps.gatewayInvokeResolver.Resolve(ctx, invokeURI)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if s.logger != nil {
|
||||
fields := []zap.Field{
|
||||
zap.String("gateway_id", entry.ID),
|
||||
zap.String("instance_id", entry.InstanceID),
|
||||
zap.String("rail", string(entry.Rail)),
|
||||
zap.String("network", entry.Network),
|
||||
zap.String("invoke_uri", invokeURI),
|
||||
}
|
||||
if paymentRef != "" {
|
||||
fields = append(fields, zap.String("payment_ref", paymentRef))
|
||||
}
|
||||
if len(actions) > 0 {
|
||||
fields = append(fields, zap.Strings("actions", railActionNames(actions)))
|
||||
}
|
||||
s.logger.Info("Chain gateway selected", fields...)
|
||||
}
|
||||
return client, entry, nil
|
||||
}
|
||||
if s.deps.gateway.resolver != nil {
|
||||
client, err := s.deps.gateway.resolver.Resolve(ctx, network)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return client, nil, nil
|
||||
}
|
||||
return nil, nil, merrors.NoData("chain gateway unavailable")
|
||||
}
|
||||
|
||||
func selectGatewayForActions(ctx context.Context, registry GatewayRegistry, rail model.Rail, network string, amount *paymenttypes.Money, actions []model.RailOperation, instanceID string, dir plan.SendDirection) (*model.GatewayInstanceDescriptor, error) {
|
||||
if registry == nil {
|
||||
return nil, merrors.NoData("gateway registry unavailable")
|
||||
}
|
||||
all, err := registry.List(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(all) == 0 {
|
||||
return nil, merrors.NoData("no gateway instances available")
|
||||
}
|
||||
if len(actions) == 0 {
|
||||
actions = []model.RailOperation{model.RailOperationSend}
|
||||
}
|
||||
|
||||
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)
|
||||
var lastErr error
|
||||
for _, entry := range all {
|
||||
if entry == nil || !entry.IsEnabled {
|
||||
continue
|
||||
}
|
||||
if entry.Rail != rail {
|
||||
continue
|
||||
}
|
||||
if instanceID != "" && !strings.EqualFold(strings.TrimSpace(entry.InstanceID), strings.TrimSpace(instanceID)) {
|
||||
continue
|
||||
}
|
||||
ok := true
|
||||
for _, action := range actions {
|
||||
if err := isGatewayEligible(entry, rail, network, currency, action, dir, amt); err != nil {
|
||||
lastErr = err
|
||||
ok = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
eligible = append(eligible, entry)
|
||||
}
|
||||
|
||||
if len(eligible) == 0 {
|
||||
if lastErr != nil {
|
||||
return nil, merrors.NoData("no eligible gateway instance found: " + lastErr.Error())
|
||||
}
|
||||
return nil, merrors.NoData("no eligible gateway instance found")
|
||||
}
|
||||
sort.Slice(eligible, func(i, j int) bool {
|
||||
return eligible[i].ID < eligible[j].ID
|
||||
})
|
||||
return eligible[0], nil
|
||||
}
|
||||
|
||||
func railActionNames(actions []model.RailOperation) []string {
|
||||
if len(actions) == 0 {
|
||||
return nil
|
||||
}
|
||||
names := make([]string, 0, len(actions))
|
||||
for _, action := range actions {
|
||||
name := strings.TrimSpace(string(action))
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
names = append(names, name)
|
||||
}
|
||||
if len(names) == 0 {
|
||||
return nil
|
||||
}
|
||||
return names
|
||||
}
|
||||
Reference in New Issue
Block a user