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) }