package quotation import ( "context" "strings" "time" "github.com/tech/sendico/payments/quotation/internal/service/quotation/gateway_funding_profile" "github.com/tech/sendico/payments/quotation/internal/service/quotation/quotation_service_v2" "github.com/tech/sendico/payments/quotation/internal/service/quotation/quote_computation_service" "github.com/tech/sendico/payments/quotation/internal/service/quotation/transfer_intent_hydrator" "github.com/tech/sendico/payments/storage/model" quotestorage "github.com/tech/sendico/payments/storage/quote" payecon "github.com/tech/sendico/pkg/payments/economics" paymentv1 "github.com/tech/sendico/pkg/proto/common/payment/v1" oraclev1 "github.com/tech/sendico/pkg/proto/oracle/v1" quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2" sharedv1 "github.com/tech/sendico/pkg/proto/payments/shared/v1" "google.golang.org/protobuf/proto" ) func newQuotationServiceV2(core *Service) *quotation_service_v2.QuotationServiceV2 { if core == nil { return nil } return quotation_service_v2.New(quotation_service_v2.Dependencies{ Logger: core.logger, QuotesStore: quoteStore(core), Hydrator: transfer_intent_hydrator.New(nil), Computation: newQuoteComputationService(core), }) } func quoteStore(core *Service) quotestorage.QuotesStore { if core == nil || core.storage == nil { return nil } return core.storage.Quotes() } func newQuoteComputationService(core *Service) *quote_computation_service.QuoteComputationService { opts := make([]quote_computation_service.Option, 0, 3) if core != nil && core.storage != nil { if routes := core.storage.Routes(); routes != nil { opts = append( opts, quote_computation_service.WithRouteStore(routes), quote_computation_service.WithLogger(core.logger), ) } } if core != nil && core.deps.gatewayRegistry != nil { opts = append(opts, quote_computation_service.WithGatewayRegistry(core.deps.gatewayRegistry)) } if resolver := newManagedWalletNetworkResolver(core); resolver != nil { opts = append(opts, quote_computation_service.WithManagedWalletNetworkResolver(resolver)) } if resolver := newLedgerAccountCurrencyResolver(core); resolver != nil { opts = append(opts, quote_computation_service.WithLedgerAccountCurrencyResolver(resolver)) } if resolver := fundingProfileResolver(core); resolver != nil { opts = append(opts, quote_computation_service.WithFundingProfileResolver(resolver)) } return quote_computation_service.New(legacyQuoteComputationCore{core: core}, opts...) } func fundingProfileResolver(core *Service) gateway_funding_profile.FundingProfileResolver { if core == nil { return nil } cardRoutes := make(map[string]gateway_funding_profile.CardGatewayFundingRoute, len(core.deps.cardRoutes)) for key, route := range core.deps.cardRoutes { routeKey := strings.TrimSpace(key) if routeKey == "" { continue } cardRoutes[routeKey] = gateway_funding_profile.CardGatewayFundingRoute{ FundingAddress: strings.TrimSpace(route.FundingAddress), FeeAddress: strings.TrimSpace(route.FeeAddress), FeeWalletRef: strings.TrimSpace(route.FeeWalletRef), } } feeLedgerAccounts := make(map[string]string, len(core.deps.feeLedgerAccounts)) for key, accountRef := range core.deps.feeLedgerAccounts { accountKey := strings.TrimSpace(key) account := strings.TrimSpace(accountRef) if accountKey == "" || account == "" { continue } feeLedgerAccounts[accountKey] = account } if len(cardRoutes) == 0 && len(feeLedgerAccounts) == 0 { return nil } return gateway_funding_profile.NewStaticFundingProfileResolver(gateway_funding_profile.StaticFundingProfileResolverInput{ DefaultCardGateway: defaultCardGateway, DefaultMode: model.FundingModeNone, CardRoutes: cardRoutes, FeeLedgerAccounts: feeLedgerAccounts, }) } type legacyQuoteComputationCore struct { core *Service } func (c legacyQuoteComputationCore) BuildQuote(ctx context.Context, in quote_computation_service.BuildQuoteInput) (*quote_computation_service.ComputedQuote, time.Time, error) { if c.core == nil { return nil, time.Time{}, errStorageUnavailable } request := "eRequest{ Meta: &sharedv1.RequestMeta{ OrganizationRef: strings.TrimSpace(in.OrganizationRef), }, IdempotencyKey: strings.TrimSpace(in.IdempotencyKey), Intent: protoIntentFromModel(in.Intent), } legacyQuote, expiresAt, err := c.core.buildPaymentQuote(ctx, strings.TrimSpace(in.OrganizationRef), request) if err != nil { return nil, time.Time{}, err } return mapLegacyQuote(in, legacyQuote), expiresAt, nil } func mapLegacyQuote(in quote_computation_service.BuildQuoteInput, src *sharedv1.PaymentQuote) *quote_computation_service.ComputedQuote { if src == nil { return "e_computation_service.ComputedQuote{} } resolvedSettlementMode := settlementModeToProto(in.Intent.SettlementMode) if resolvedSettlementMode == paymentv1.SettlementMode_SETTLEMENT_UNSPECIFIED { resolvedSettlementMode = paymentv1.SettlementMode_SETTLEMENT_FIX_SOURCE } resolvedFeeTreatment := feeTreatmentToProto(in.Intent.FeeTreatment) if resolvedFeeTreatment == quotationv2.FeeTreatment_FEE_TREATMENT_UNSPECIFIED { resolvedFeeTreatment = payecon.DefaultFeeTreatment() } return "e_computation_service.ComputedQuote{ DebitAmount: cloneProtoMoney(src.GetDebitSettlementAmount()), CreditAmount: cloneProtoMoney(src.GetExpectedSettlementAmount()), TotalCost: cloneProtoMoney(src.GetDebitAmount()), FeeLines: cloneFeeLines(src.GetFeeLines()), FeeRules: cloneFeeRules(src.GetFeeRules()), FXQuote: cloneFXQuote(src.GetFxQuote()), Route: cloneRouteSpecification(in.Route), ExecutionConditions: cloneExecutionConditions(in.ExecutionConditions), ResolvedSettlementMode: resolvedSettlementMode, ResolvedFeeTreatment: resolvedFeeTreatment, } } func feeTreatmentToProto(value model.FeeTreatment) quotationv2.FeeTreatment { switch value { case model.FeeTreatmentDeductFromDestination: return quotationv2.FeeTreatment_FEE_TREATMENT_DEDUCT_FROM_DESTINATION case model.FeeTreatmentAddToSource: return quotationv2.FeeTreatment_FEE_TREATMENT_ADD_TO_SOURCE default: return quotationv2.FeeTreatment_FEE_TREATMENT_UNSPECIFIED } } func cloneRouteSpecification(src *quotationv2.RouteSpecification) *quotationv2.RouteSpecification { if src == nil { return nil } cloned, ok := proto.Clone(src).(*quotationv2.RouteSpecification) if !ok { return nil } return cloned } func cloneExecutionConditions(src *quotationv2.ExecutionConditions) *quotationv2.ExecutionConditions { if src == nil { return nil } cloned, ok := proto.Clone(src).(*quotationv2.ExecutionConditions) if !ok { return nil } return cloned } func cloneFXQuote(src *oraclev1.Quote) *oraclev1.Quote { if src == nil { return nil } cloned, ok := proto.Clone(src).(*oraclev1.Quote) if !ok { return nil } return cloned }