Files
sendico/api/payments/quotation/internal/service/quotation/quotation_v2_wiring.go
2026-03-04 04:50:31 +01:00

200 lines
6.8 KiB
Go

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 := &quoteRequest{
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 &quote_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 &quote_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
}