quotation service fixed
This commit is contained in:
@@ -64,8 +64,8 @@ func networkPriority(edgeNetwork, requested string) int {
|
||||
}
|
||||
|
||||
func normalizeRail(value model.Rail) model.Rail {
|
||||
normalized := model.Rail(strings.ToUpper(strings.TrimSpace(string(value))))
|
||||
if normalized == "" {
|
||||
normalized := model.ParseRail(string(value))
|
||||
if normalized == model.RailUnspecified {
|
||||
return model.RailUnspecified
|
||||
}
|
||||
return normalized
|
||||
|
||||
@@ -23,7 +23,7 @@ func TestFind_NetworkFiltersEdges(t *testing.T) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
assertPathRails(t, path, []string{"CRYPTO", "LEDGER", "CARD_PAYOUT"})
|
||||
assertPathRails(t, path, []string{"CRYPTO", "LEDGER", "CARD"})
|
||||
if got, want := path.Edges[0].Network, "TRON"; got != want {
|
||||
t.Fatalf("unexpected first edge network: got=%q want=%q", got, want)
|
||||
}
|
||||
@@ -47,7 +47,7 @@ func TestFind_PrefersExactNetworkOverWildcard(t *testing.T) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
assertPathRails(t, path, []string{"CRYPTO", "PROVIDER_SETTLEMENT", "CARD_PAYOUT"})
|
||||
assertPathRails(t, path, []string{"CRYPTO", "SETTLEMENT", "CARD"})
|
||||
}
|
||||
|
||||
func TestFind_DeterministicTieBreak(t *testing.T) {
|
||||
@@ -68,7 +68,7 @@ func TestFind_DeterministicTieBreak(t *testing.T) {
|
||||
}
|
||||
|
||||
// Both routes have equal length; lexical tie-break chooses LEDGER branch.
|
||||
assertPathRails(t, path, []string{"CRYPTO", "LEDGER", "CARD_PAYOUT"})
|
||||
assertPathRails(t, path, []string{"CRYPTO", "LEDGER", "CARD"})
|
||||
}
|
||||
|
||||
func TestFind_IgnoresInvalidEdges(t *testing.T) {
|
||||
@@ -88,5 +88,5 @@ func TestFind_IgnoresInvalidEdges(t *testing.T) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
assertPathRails(t, path, []string{"CRYPTO", "LEDGER", "CARD_PAYOUT"})
|
||||
assertPathRails(t, path, []string{"CRYPTO", "LEDGER", "CARD"})
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ func TestFind_FindsIndirectPath(t *testing.T) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
assertPathRails(t, path, []string{"CRYPTO", "LEDGER", "CARD_PAYOUT"})
|
||||
assertPathRails(t, path, []string{"CRYPTO", "LEDGER", "CARD"})
|
||||
if got, want := len(path.Edges), 2; got != want {
|
||||
t.Fatalf("unexpected edge count: got=%d want=%d", got, want)
|
||||
}
|
||||
@@ -84,7 +84,7 @@ func TestFind_PrefersShortestPath(t *testing.T) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
assertPathRails(t, path, []string{"CRYPTO", "CARD_PAYOUT"})
|
||||
assertPathRails(t, path, []string{"CRYPTO", "CARD"})
|
||||
}
|
||||
|
||||
func TestFind_HandlesCycles(t *testing.T) {
|
||||
@@ -103,7 +103,7 @@ func TestFind_HandlesCycles(t *testing.T) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
assertPathRails(t, path, []string{"CRYPTO", "LEDGER", "CARD_PAYOUT"})
|
||||
assertPathRails(t, path, []string{"CRYPTO", "LEDGER", "CARD"})
|
||||
}
|
||||
|
||||
func TestFind_ReturnsErrorWhenPathUnavailable(t *testing.T) {
|
||||
|
||||
@@ -131,6 +131,42 @@ func TestBuildPlan_RequiresFXAddsMiddleStep(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildPlan_RequiresFXUsesSettlementCurrencyForDestinationStep(t *testing.T) {
|
||||
svc := New(nil)
|
||||
orgID := bson.NewObjectID()
|
||||
intent := sampleCryptoToCardQuoteIntent()
|
||||
intent.RequiresFX = true
|
||||
intent.SettlementCurrency = "RUB"
|
||||
|
||||
planModel, err := svc.BuildPlan(context.Background(), ComputeInput{
|
||||
OrganizationRef: orgID.Hex(),
|
||||
OrganizationID: orgID,
|
||||
BaseIdempotencyKey: "idem-key",
|
||||
PreviewOnly: false,
|
||||
Intents: []*transfer_intent_hydrator.QuoteIntent{intent},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(planModel.Items) != 1 {
|
||||
t.Fatalf("expected one plan item")
|
||||
}
|
||||
steps := planModel.Items[0].Steps
|
||||
if got, want := len(steps), 4; got != want {
|
||||
t.Fatalf("unexpected step count: got=%d want=%d", got, want)
|
||||
}
|
||||
last := steps[len(steps)-1]
|
||||
if last == nil || last.Amount == nil {
|
||||
t.Fatalf("expected destination step amount")
|
||||
}
|
||||
if got, want := strings.TrimSpace(last.Amount.GetCurrency()), "RUB"; got != want {
|
||||
t.Fatalf("unexpected destination step currency: got=%q want=%q", got, want)
|
||||
}
|
||||
if got, want := last.Operation, model.RailOperationSend; got != want {
|
||||
t.Fatalf("unexpected destination operation: got=%q want=%q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildPlan_SelectsGatewaysAndIgnoresIrrelevant(t *testing.T) {
|
||||
svc := New(nil, WithGatewayRegistry(staticGatewayRegistry{
|
||||
items: []*model.GatewayInstanceDescriptor{
|
||||
@@ -406,7 +442,7 @@ func TestCompute_FailsWhenCoreReturnsDifferentRoute(t *testing.T) {
|
||||
quote: &ComputedQuote{
|
||||
DebitAmount: &moneyv1.Money{Amount: "100", Currency: "USD"},
|
||||
Route: "ationv2.RouteSpecification{
|
||||
Rail: "CARD_PAYOUT",
|
||||
Rail: "CARD",
|
||||
Provider: "other-provider",
|
||||
PayoutMethod: "CARD",
|
||||
},
|
||||
|
||||
@@ -16,6 +16,9 @@ func (s *QuoteComputationService) resolveRouteRails(
|
||||
destinationRail model.Rail,
|
||||
network string,
|
||||
) ([]model.Rail, error) {
|
||||
sourceRail = model.ParseRail(string(sourceRail))
|
||||
destinationRail = model.ParseRail(string(destinationRail))
|
||||
|
||||
s.logger.Debug("Resolving route rails",
|
||||
zap.String("source_rail", string(sourceRail)),
|
||||
zap.String("dest_rail", string(destinationRail)),
|
||||
@@ -179,8 +182,8 @@ func (s *QuoteComputationService) routeGraphEdges(ctx context.Context) ([]graph_
|
||||
continue
|
||||
}
|
||||
|
||||
from := model.Rail(strings.ToUpper(strings.TrimSpace(string(route.FromRail))))
|
||||
to := model.Rail(strings.ToUpper(strings.TrimSpace(string(route.ToRail))))
|
||||
from := model.ParseRail(string(route.FromRail))
|
||||
to := model.ParseRail(string(route.ToRail))
|
||||
|
||||
if from == model.RailUnspecified || to == model.RailUnspecified {
|
||||
continue
|
||||
|
||||
@@ -74,7 +74,7 @@ func TestBuildPlan_UsesRouteGraphPath(t *testing.T) {
|
||||
if got, want := string(item.Steps[1].Rail), string(model.RailProviderSettlement); got != want {
|
||||
t.Fatalf("unexpected transit rail: got=%q want=%q", got, want)
|
||||
}
|
||||
if got := strings.ToUpper(strings.TrimSpace(item.Route.GetHops()[1].GetRail())); got != "PROVIDER_SETTLEMENT" {
|
||||
if got := strings.ToUpper(strings.TrimSpace(item.Route.GetHops()[1].GetRail())); got != "SETTLEMENT" {
|
||||
t.Fatalf("unexpected route transit hop rail: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/tech/sendico/payments/storage/model"
|
||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||
)
|
||||
|
||||
func buildComputationSteps(
|
||||
@@ -19,6 +20,7 @@ func buildComputationSteps(
|
||||
|
||||
attrs := intent.Attributes
|
||||
amount := protoMoneyFromModel(intent.Amount)
|
||||
destinationAmount := destinationStepAmount(intent, amount)
|
||||
sourceRail := sourceRailForIntent(intent)
|
||||
destinationRail := destinationRailForIntent(intent)
|
||||
rails := normalizeRouteRails(sourceRail, destinationRail, routeRails)
|
||||
@@ -101,7 +103,7 @@ func buildComputationSteps(
|
||||
GatewayID: destinationGatewayID,
|
||||
InstanceID: destinationInstanceID,
|
||||
DependsOn: []string{lastStepID},
|
||||
Amount: cloneProtoMoney(amount),
|
||||
Amount: destinationAmount,
|
||||
Optional: false,
|
||||
IncludeInAggregate: true,
|
||||
})
|
||||
@@ -196,3 +198,22 @@ func destinationOperationForRail(rail model.Rail) model.RailOperation {
|
||||
return model.RailOperationExternalCredit
|
||||
}
|
||||
}
|
||||
|
||||
func destinationStepAmount(intent model.PaymentIntent, sourceAmount *moneyv1.Money) *moneyv1.Money {
|
||||
amount := cloneProtoMoney(sourceAmount)
|
||||
if amount == nil {
|
||||
return nil
|
||||
}
|
||||
if !intent.RequiresFX {
|
||||
return amount
|
||||
}
|
||||
|
||||
settlementCurrency := strings.ToUpper(strings.TrimSpace(intent.SettlementCurrency))
|
||||
if settlementCurrency == "" && intent.FX != nil && intent.FX.Pair != nil {
|
||||
settlementCurrency = strings.ToUpper(strings.TrimSpace(intent.FX.Pair.Quote))
|
||||
}
|
||||
if settlementCurrency != "" {
|
||||
amount.Currency = settlementCurrency
|
||||
}
|
||||
return amount
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package quote_computation_service
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/tech/sendico/payments/storage/model"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
|
||||
)
|
||||
@@ -59,6 +60,9 @@ func normalizeSettlementParts(src *quotationv2.RouteSettlement) (chain, token, c
|
||||
}
|
||||
|
||||
func normalizeRail(value string) string {
|
||||
if rail := model.ParseRail(value); rail != model.RailUnspecified {
|
||||
return string(rail)
|
||||
}
|
||||
return strings.ToUpper(strings.TrimSpace(value))
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ func TestMap_ExecutableQuote(t *testing.T) {
|
||||
Currency: "USD",
|
||||
},
|
||||
Route: "ationv2.RouteSpecification{
|
||||
Rail: "CARD_PAYOUT",
|
||||
Rail: "CARD",
|
||||
Provider: "monetix",
|
||||
PayoutMethod: "CARD",
|
||||
Settlement: "ationv2.RouteSettlement{
|
||||
|
||||
Reference in New Issue
Block a user