Fixed billing fees unreachable error propagation. Added USDT ledger creation. Fixed ledger boundaries operation types

This commit is contained in:
Stephan D
2026-02-26 16:25:52 +01:00
parent 54e5c799e8
commit 336f352858
37 changed files with 838 additions and 302 deletions

View File

@@ -52,7 +52,7 @@ func (i *Imp) startDiscoveryAnnouncer(cfg *config, producer msg.Producer) {
announce := discovery.Announcement{
Service: "PAYMENTS_QUOTATION",
Operations: []string{"payment.quote", "payment.multiquote"},
Operations: []string{discovery.OperationPaymentQuote, discovery.OperationPaymentMultiQuote},
InvokeURI: invokeURI,
Version: appversion.Create().Short(),
}

View File

@@ -197,18 +197,6 @@ func (r *managedWalletNetworkResolver) listDiscoveredGatewayCandidates(ctx conte
return candidates, nil
}
func managedWalletNetworkFromResponse(resp *chainv1.GetManagedWalletResponse) (string, error) {
asset, err := managedWalletAssetFromResponse(resp)
if err != nil {
return "", err
}
network := strings.ToUpper(strings.TrimSpace(asset.GetChain()))
if network == "" || network == "UNSPECIFIED" {
return "", merrors.NoData("managed wallet network is missing")
}
return network, nil
}
func managedWalletAssetFromResponse(resp *chainv1.GetManagedWalletResponse) (*paymenttypes.Asset, error) {
wallet := resp.GetWallet()
if wallet == nil || wallet.GetAsset() == nil {

View File

@@ -8,7 +8,3 @@ import (
func railFromEndpoint(endpoint model.PaymentEndpoint, attrs map[string]string, isSource bool) (model.Rail, string, error) {
return plan.RailFromEndpoint(endpoint, attrs, isSource)
}
func resolveRouteNetwork(attrs map[string]string, sourceNetwork, destNetwork string) (string, error) {
return plan.ResolveRouteNetwork(attrs, sourceNetwork, destNetwork)
}

View File

@@ -152,7 +152,8 @@ func (s *Service) buildPaymentQuote(ctx context.Context, orgRef string, req *quo
func (s *Service) quoteFees(ctx context.Context, orgRef string, req *quoteRequest, baseAmount *moneyv1.Money) (*feesv1.PrecomputeFeesResponse, error) {
if !s.deps.fees.available() {
return &feesv1.PrecomputeFeesResponse{}, nil
s.logger.Warn("Fees precompute failed: fee engine unavailable")
return nil, merrors.Internal("fees_precompute_failed")
}
intent := req.GetIntent()
amount := cloneProtoMoney(baseAmount)
@@ -188,7 +189,8 @@ func (s *Service) quoteFees(ctx context.Context, orgRef string, req *quoteReques
func (s *Service) quoteConversionFees(ctx context.Context, orgRef string, req *quoteRequest, baseAmount *moneyv1.Money) (*feesv1.PrecomputeFeesResponse, error) {
if !s.deps.fees.available() {
return &feesv1.PrecomputeFeesResponse{}, nil
s.logger.Warn("Conversion fee precompute failed: fee engine unavailable")
return nil, merrors.Internal("fees_precompute_failed")
}
intent := req.GetIntent()
amount := cloneProtoMoney(baseAmount)

View File

@@ -2,6 +2,7 @@ package quotation
import (
"context"
"errors"
"strings"
"testing"
@@ -152,9 +153,89 @@ func TestBuildPaymentQuote_DoesNotRequestConversionFeesForManagedWalletToLedger(
}
}
func TestBuildPaymentQuote_ReturnsErrorWhenFeeEngineUnavailable(t *testing.T) {
svc := NewService(zap.NewNop(), nil)
req := &quoteRequest{
Meta: &sharedv1.RequestMeta{OrganizationRef: "org_1"},
IdempotencyKey: "idem_1",
Intent: testManagedWalletToCardIntent(),
}
_, _, err := svc.buildPaymentQuote(context.Background(), "org_1", req)
if err == nil {
t.Fatalf("expected error")
}
if !errors.Is(err, merrors.ErrInternal) {
t.Fatalf("expected internal error, got: %v", err)
}
if !strings.Contains(err.Error(), "fees_precompute_failed") {
t.Fatalf("expected fees_precompute_failed error, got: %v", err)
}
}
func TestBuildPaymentQuote_ReturnsErrorWhenBaseFeePrecomputeFails(t *testing.T) {
feeClient := &stubFeeEngineClient{
precomputeErrByOrigin: map[string]error{
"payments.orchestrator.quote": merrors.Internal("billing_fees_unreachable"),
},
}
svc := NewService(zap.NewNop(), nil, WithFeeEngine(feeClient, 0))
req := &quoteRequest{
Meta: &sharedv1.RequestMeta{OrganizationRef: "org_1"},
IdempotencyKey: "idem_1",
Intent: testManagedWalletToLedgerIntent(),
}
_, _, err := svc.buildPaymentQuote(context.Background(), "org_1", req)
if err == nil {
t.Fatalf("expected error")
}
if !errors.Is(err, merrors.ErrInternal) {
t.Fatalf("expected internal error, got: %v", err)
}
if !strings.Contains(err.Error(), "fees_precompute_failed") {
t.Fatalf("expected fees_precompute_failed error, got: %v", err)
}
}
func TestBuildPaymentQuote_ReturnsErrorWhenConversionFeePrecomputeFails(t *testing.T) {
feeClient := &stubFeeEngineClient{
precomputeByOrigin: map[string]*feesv1.PrecomputeFeesResponse{
"payments.orchestrator.quote": {
Lines: []*feesv1.DerivedPostingLine{
testFeeLine("1.00", "USDT"),
},
},
},
precomputeErrByOrigin: map[string]error{
"payments.orchestrator.conversion_quote": merrors.Internal("billing_fees_unreachable"),
},
}
svc := NewService(zap.NewNop(), nil, WithFeeEngine(feeClient, 0))
req := &quoteRequest{
Meta: &sharedv1.RequestMeta{OrganizationRef: "org_1"},
IdempotencyKey: "idem_1",
Intent: testManagedWalletToCardIntent(),
}
_, _, err := svc.buildPaymentQuote(context.Background(), "org_1", req)
if err == nil {
t.Fatalf("expected error")
}
if !errors.Is(err, merrors.ErrInternal) {
t.Fatalf("expected internal error, got: %v", err)
}
if !strings.Contains(err.Error(), "fees_precompute_failed") {
t.Fatalf("expected fees_precompute_failed error, got: %v", err)
}
}
type stubFeeEngineClient struct {
precomputeByOrigin map[string]*feesv1.PrecomputeFeesResponse
precomputeReqs []*feesv1.PrecomputeFeesRequest
precomputeByOrigin map[string]*feesv1.PrecomputeFeesResponse
precomputeErrByOrigin map[string]error
precomputeReqs []*feesv1.PrecomputeFeesRequest
}
func (s *stubFeeEngineClient) QuoteFees(context.Context, *feesv1.QuoteFeesRequest, ...grpc.CallOption) (*feesv1.QuoteFeesResponse, error) {
@@ -177,6 +258,9 @@ func (s *stubFeeEngineClient) PrecomputeFees(_ context.Context, in *feesv1.Preco
}
originType := strings.TrimSpace(in.GetIntent().GetOriginType())
if err := s.precomputeErrByOrigin[originType]; err != nil {
return nil, err
}
resp, ok := s.precomputeByOrigin[originType]
if !ok || resp == nil {
return &feesv1.PrecomputeFeesResponse{}, nil