Files
sendico/api/payments/quotation/internal/service/plan/plan_builder_default.go
2026-02-11 18:15:04 +01:00

103 lines
3.4 KiB
Go

package plan
import (
"context"
"github.com/tech/sendico/payments/storage/model"
"github.com/tech/sendico/pkg/merrors"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/mutil/mzap"
sharedv1 "github.com/tech/sendico/pkg/proto/payments/shared/v1"
"go.uber.org/zap"
)
type defaultBuilder struct {
logger mlogger.Logger
}
func newDefaultBuilder(logger mlogger.Logger) *defaultBuilder {
return &defaultBuilder{
logger: logger.Named("plan_builder"),
}
}
func (b *defaultBuilder) Build(ctx context.Context, payment *model.Payment, quote *sharedv1.PaymentQuote, routes RouteStore, templates PlanTemplateStore, gateways GatewayRegistry) (*model.PaymentPlan, error) {
if payment == nil {
return nil, merrors.InvalidArgument("plan builder: payment is required")
}
if routes == nil {
return nil, merrors.InvalidArgument("plan builder: routes store is required")
}
if templates == nil {
return nil, merrors.InvalidArgument("plan builder: plan templates store is required")
}
logger := b.logger.With(
zap.String("payment_ref", payment.PaymentRef),
zap.String("payment_kind", string(payment.Intent.Kind)),
)
logger.Debug("Building payment plan")
intent := payment.Intent
if intent.Kind == model.PaymentKindFXConversion {
logger.Debug("Building fx conversion plan")
plan, err := buildFXConversionPlan(payment)
if err != nil {
logger.Warn("Failed to build fx conversion plan", zap.Error(err))
return nil, err
}
logger.Info("fx conversion plan built", zap.Int("steps", len(plan.Steps)))
return plan, nil
}
sourceRail, sourceNetwork, err := railFromEndpoint(intent.Source, intent.Attributes, true)
if err != nil {
logger.Warn("Failed to resolve source rail", zap.Error(err))
return nil, err
}
destRail, destNetwork, err := railFromEndpoint(intent.Destination, intent.Attributes, false)
if err != nil {
logger.Warn("Failed to resolve destination rail", zap.Error(err))
return nil, err
}
logger = logger.With(
zap.String("source_rail", string(sourceRail)),
zap.String("dest_rail", string(destRail)),
zap.String("source_network", sourceNetwork),
zap.String("dest_network", destNetwork),
)
if sourceRail == model.RailUnspecified || destRail == model.RailUnspecified {
logger.Warn("Source and destination rails are required")
return nil, merrors.InvalidArgument("plan builder: source and destination rails are required")
}
if sourceRail == destRail && sourceRail != model.RailLedger {
logger.Warn("Unsupported same-rail payment")
return nil, merrors.InvalidArgument("plan builder: unsupported same-rail payment")
}
network, err := resolveRouteNetwork(intent.Attributes, sourceNetwork, destNetwork)
if err != nil {
logger.Warn("Failed to resolve route network", zap.Error(err))
return nil, err
}
logger = logger.With(zap.String("network", network))
route, err := selectRoute(ctx, routes, sourceRail, destRail, network)
if err != nil {
logger.Warn("Failed to select route", zap.Error(err))
return nil, err
}
logger.Debug("Route selected", mzap.StorableRef(route))
template, err := selectPlanTemplate(ctx, logger, templates, sourceRail, destRail, network)
if err != nil {
logger.Warn("Failed to select plan template", zap.Error(err))
return nil, err
}
logger.Debug("Plan template selected", mzap.StorableRef(template))
return b.buildPlanFromTemplate(ctx, payment, quote, template, sourceRail, destRail, sourceNetwork, destNetwork, gateways)
}