diff --git a/api/billing/documents/go.mod b/api/billing/documents/go.mod index 23467825..cf89e01d 100644 --- a/api/billing/documents/go.mod +++ b/api/billing/documents/go.mod @@ -15,7 +15,7 @@ require ( github.com/tech/sendico/pkg v0.1.0 go.mongodb.org/mongo-driver/v2 v2.5.0 go.uber.org/zap v1.27.1 - google.golang.org/grpc v1.79.0 + google.golang.org/grpc v1.79.1 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/api/billing/documents/go.sum b/api/billing/documents/go.sum index b2fa29f2..cbc4195b 100644 --- a/api/billing/documents/go.sum +++ b/api/billing/documents/go.sum @@ -260,8 +260,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= -google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/billing/fees/go.mod b/api/billing/fees/go.mod index 7e99f7ee..7c9329a4 100644 --- a/api/billing/fees/go.mod +++ b/api/billing/fees/go.mod @@ -10,7 +10,7 @@ require ( github.com/tech/sendico/fx/oracle v0.0.0 github.com/tech/sendico/pkg v0.1.0 go.uber.org/zap v1.27.1 - google.golang.org/grpc v1.79.0 + google.golang.org/grpc v1.79.1 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/api/billing/fees/go.sum b/api/billing/fees/go.sum index 35825f0b..3552c709 100644 --- a/api/billing/fees/go.sum +++ b/api/billing/fees/go.sum @@ -210,8 +210,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= -google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/discovery/go.mod b/api/discovery/go.mod index c1005c0b..aacafd49 100644 --- a/api/discovery/go.mod +++ b/api/discovery/go.mod @@ -44,6 +44,6 @@ require ( golang.org/x/sys v0.41.0 // indirect golang.org/x/text v0.34.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect - google.golang.org/grpc v1.79.0 // indirect + google.golang.org/grpc v1.79.1 // indirect google.golang.org/protobuf v1.36.11 // indirect ) diff --git a/api/discovery/go.sum b/api/discovery/go.sum index 35825f0b..3552c709 100644 --- a/api/discovery/go.sum +++ b/api/discovery/go.sum @@ -210,8 +210,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= -google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/fx/ingestor/go.mod b/api/fx/ingestor/go.mod index a9defa7e..eb28b3f9 100644 --- a/api/fx/ingestor/go.mod +++ b/api/fx/ingestor/go.mod @@ -48,6 +48,6 @@ require ( golang.org/x/sys v0.41.0 // indirect golang.org/x/text v0.34.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect - google.golang.org/grpc v1.79.0 // indirect + google.golang.org/grpc v1.79.1 // indirect google.golang.org/protobuf v1.36.11 // indirect ) diff --git a/api/fx/ingestor/go.sum b/api/fx/ingestor/go.sum index 35825f0b..3552c709 100644 --- a/api/fx/ingestor/go.sum +++ b/api/fx/ingestor/go.sum @@ -210,8 +210,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= -google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/fx/oracle/client/client.go b/api/fx/oracle/client/client.go index ca8b6fda..81453ebd 100644 --- a/api/fx/oracle/client/client.go +++ b/api/fx/oracle/client/client.go @@ -68,6 +68,7 @@ type Quote struct { BaseAmount *moneyv1.Money QuoteAmount *moneyv1.Money ExpiresAt time.Time + PricedAt time.Time Provider string RateRef string Firm bool @@ -237,6 +238,10 @@ func fromProtoQuote(quote *oraclev1.Quote) *Quote { if quote == nil { return nil } + pricedAt := time.Time{} + if ts := quote.GetPricedAt(); ts != nil { + pricedAt = ts.AsTime() + } return &Quote{ QuoteRef: quote.GetQuoteRef(), Pair: quote.Pair, @@ -245,6 +250,7 @@ func fromProtoQuote(quote *oraclev1.Quote) *Quote { BaseAmount: quote.BaseAmount, QuoteAmount: quote.QuoteAmount, ExpiresAt: time.UnixMilli(quote.GetExpiresAtUnixMs()), + PricedAt: pricedAt, Provider: quote.GetProvider(), RateRef: quote.GetRateRef(), Firm: quote.GetFirm(), diff --git a/api/fx/oracle/client/client_test.go b/api/fx/oracle/client/client_test.go index 3a2aca1e..06778980 100644 --- a/api/fx/oracle/client/client_test.go +++ b/api/fx/oracle/client/client_test.go @@ -9,6 +9,7 @@ import ( moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1" oraclev1 "github.com/tech/sendico/pkg/proto/oracle/v1" "google.golang.org/grpc" + "google.golang.org/protobuf/types/known/timestamppb" ) type stubOracle struct { @@ -75,6 +76,7 @@ func TestLatestRate(t *testing.T) { func TestGetQuote(t *testing.T) { expiresAt := time.Date(2024, 2, 2, 12, 0, 0, 0, time.UTC) + pricedAt := time.Date(2024, 2, 2, 11, 59, 0, 0, time.UTC) stub := &stubOracle{ quoteResp: &oraclev1.GetQuoteResponse{ Quote: &oraclev1.Quote{ @@ -85,6 +87,7 @@ func TestGetQuote(t *testing.T) { BaseAmount: &moneyv1.Money{Amount: "100.00", Currency: "GBP"}, QuoteAmount: &moneyv1.Money{Amount: "125.00", Currency: "USD"}, ExpiresAtUnixMs: expiresAt.UnixMilli(), + PricedAt: timestamppb.New(pricedAt), Provider: "Test", RateRef: "test-ref", Firm: true, @@ -113,4 +116,7 @@ func TestGetQuote(t *testing.T) { if resp.QuoteRef != "quote-123" || resp.Price != "1.2500" || !resp.ExpiresAt.Equal(expiresAt) { t.Fatalf("unexpected quote response: %+v", resp) } + if !resp.PricedAt.Equal(pricedAt) { + t.Fatalf("expected priced_at %s, got %s", pricedAt, resp.PricedAt) + } } diff --git a/api/fx/oracle/go.mod b/api/fx/oracle/go.mod index b7ea0e59..a79335c1 100644 --- a/api/fx/oracle/go.mod +++ b/api/fx/oracle/go.mod @@ -13,7 +13,7 @@ require ( github.com/tech/sendico/pkg v0.1.0 go.mongodb.org/mongo-driver/v2 v2.5.0 go.uber.org/zap v1.27.1 - google.golang.org/grpc v1.79.0 + google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/api/fx/oracle/go.sum b/api/fx/oracle/go.sum index 35825f0b..3552c709 100644 --- a/api/fx/oracle/go.sum +++ b/api/fx/oracle/go.sum @@ -210,8 +210,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= -google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/fx/oracle/internal/service/oracle/calculator.go b/api/fx/oracle/internal/service/oracle/calculator.go index cb4790ca..fc55c471 100644 --- a/api/fx/oracle/internal/service/oracle/calculator.go +++ b/api/fx/oracle/internal/service/oracle/calculator.go @@ -130,6 +130,10 @@ func (qc *quoteComputation) buildModelQuote(firm bool, expiryMillis int64, req * if qc.baseRounded == nil || qc.quoteRounded == nil || qc.priceRounded == nil { return nil, merrors.Internal("oracle: computation not executed") } + pricedAtUnixMs := qc.rate.AsOfUnixMs + if pricedAtUnixMs <= 0 { + pricedAtUnixMs = time.Now().UnixMilli() + } quote := &model.Quote{ QuoteRef: uuid.NewString(), @@ -147,6 +151,7 @@ func (qc *quoteComputation) buildModelQuote(firm bool, expiryMillis int64, req * Amount: formatRat(qc.quoteRounded, qc.quoteScale), }, AmountType: qc.amountType, + PricedAtUnixMs: pricedAtUnixMs, RateRef: qc.rate.RateRef, Provider: qc.provider, PreferredProvider: req.GetPreferredProvider(), diff --git a/api/fx/oracle/internal/service/oracle/service_test.go b/api/fx/oracle/internal/service/oracle/service_test.go index be573e63..a8c5f2de 100644 --- a/api/fx/oracle/internal/service/oracle/service_test.go +++ b/api/fx/oracle/internal/service/oracle/service_test.go @@ -111,6 +111,7 @@ func (currencyStoreStub) List(ctx context.Context, codes ...string) ([]*model.Cu func (currencyStoreStub) Upsert(ctx context.Context, currency *model.Currency) error { return nil } func TestServiceGetQuoteFirm(t *testing.T) { + pricedAt := time.Date(2024, 1, 2, 3, 4, 5, 0, time.UTC) repo := &repositoryStub{} repo.pairs = &pairStoreStub{ getFn: func(ctx context.Context, pair model.CurrencyPair) (*model.Pair, error) { @@ -129,7 +130,7 @@ func TestServiceGetQuoteFirm(t *testing.T) { Ask: "1.10", Bid: "1.08", RateRef: "rate#1", - AsOfUnixMs: time.Now().UnixMilli(), + AsOfUnixMs: pricedAt.UnixMilli(), }, nil }, } @@ -169,9 +170,15 @@ func TestServiceGetQuoteFirm(t *testing.T) { if resp.GetQuote().GetQuoteAmount().GetAmount() != "110.00" { t.Fatalf("unexpected quote amount: %s", resp.GetQuote().GetQuoteAmount().GetAmount()) } + if got := resp.GetQuote().GetPricedAt(); got == nil || !got.AsTime().Equal(pricedAt) { + t.Fatalf("expected priced_at %s, got %v", pricedAt, got) + } if savedQuote.QuoteRef == "" { t.Fatalf("expected quote persisted") } + if savedQuote.PricedAtUnixMs != pricedAt.UnixMilli() { + t.Fatalf("expected stored pricedAtUnixMs %d, got %d", pricedAt.UnixMilli(), savedQuote.PricedAtUnixMs) + } } func TestServiceGetQuoteRateNotFound(t *testing.T) { diff --git a/api/fx/oracle/internal/service/oracle/transform.go b/api/fx/oracle/internal/service/oracle/transform.go index b96f8aae..d68ef8d4 100644 --- a/api/fx/oracle/internal/service/oracle/transform.go +++ b/api/fx/oracle/internal/service/oracle/transform.go @@ -2,12 +2,14 @@ package oracle import ( "strings" + "time" "github.com/tech/sendico/fx/storage/model" paymenttypes "github.com/tech/sendico/pkg/payments/types" fxv1 "github.com/tech/sendico/pkg/proto/common/fx/v1" moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1" oraclev1 "github.com/tech/sendico/pkg/proto/oracle/v1" + "google.golang.org/protobuf/types/known/timestamppb" ) func buildResponseMeta(meta *oraclev1.RequestMeta) *oraclev1.ResponseMeta { @@ -36,6 +38,7 @@ func quoteModelToProto(q *model.Quote) *oraclev1.Quote { BaseAmount: moneyModelToProto(&q.BaseAmount), QuoteAmount: moneyModelToProto(&q.QuoteAmount), ExpiresAtUnixMs: q.ExpiresAtUnixMs, + PricedAt: timestampFromUnixMillis(q.PricedAtUnixMs, q.CreatedAt), Provider: q.Provider, RateRef: q.RateRef, Firm: q.Firm, @@ -117,3 +120,13 @@ func decimalStringToProto(value string) *moneyv1.Decimal { } return &moneyv1.Decimal{Value: value} } + +func timestampFromUnixMillis(ms int64, fallback time.Time) *timestamppb.Timestamp { + if ms > 0 { + return timestamppb.New(time.UnixMilli(ms).UTC()) + } + if !fallback.IsZero() { + return timestamppb.New(fallback.UTC()) + } + return nil +} diff --git a/api/fx/storage/model/quote.go b/api/fx/storage/model/quote.go index 7b15580f..1274e132 100644 --- a/api/fx/storage/model/quote.go +++ b/api/fx/storage/model/quote.go @@ -21,6 +21,7 @@ type Quote struct { QuoteAmount paymenttypes.Money `bson:"quoteAmount" json:"quoteAmount"` AmountType QuoteAmountType `bson:"amountType" json:"amountType"` ExpiresAtUnixMs int64 `bson:"expiresAtUnixMs" json:"expiresAtUnixMs"` + PricedAtUnixMs int64 `bson:"pricedAtUnixMs,omitempty" json:"pricedAtUnixMs,omitempty"` ExpiresAt *time.Time `bson:"expiresAt,omitempty" json:"expiresAt,omitempty"` RateRef string `bson:"rateRef" json:"rateRef"` Provider string `bson:"provider" json:"provider"` diff --git a/api/fx/storage/mongo/store/quotes.go b/api/fx/storage/mongo/store/quotes.go index 22201768..4fca596f 100644 --- a/api/fx/storage/mongo/store/quotes.go +++ b/api/fx/storage/mongo/store/quotes.go @@ -95,6 +95,9 @@ func (q *quotesStore) Issue(ctx context.Context, quote *model.Quote) error { expiry := time.UnixMilli(quote.ExpiresAtUnixMs) quote.ExpiresAt = &expiry } + if quote.PricedAtUnixMs <= 0 { + quote.PricedAtUnixMs = time.Now().UnixMilli() + } quote.Status = model.QuoteStatusIssued quote.ConsumedByLedgerTxnRef = "" diff --git a/api/fx/storage/mongo/store/quotes_test.go b/api/fx/storage/mongo/store/quotes_test.go index d65779d6..efdec5a3 100644 --- a/api/fx/storage/mongo/store/quotes_test.go +++ b/api/fx/storage/mongo/store/quotes_test.go @@ -32,6 +32,9 @@ func TestQuotesStoreIssue(t *testing.T) { if inserted == nil || inserted.Status != model.QuoteStatusIssued { t.Fatalf("expected issued quote to be inserted") } + if inserted.PricedAtUnixMs <= 0 { + t.Fatalf("expected pricedAtUnixMs to be populated") + } } func TestQuotesStoreIssueSetsExpiryDate(t *testing.T) { diff --git a/api/gateway/chain/go.mod b/api/gateway/chain/go.mod index bdf94ce7..53abec77 100644 --- a/api/gateway/chain/go.mod +++ b/api/gateway/chain/go.mod @@ -15,14 +15,14 @@ require ( github.com/tech/sendico/pkg v0.1.0 go.mongodb.org/mongo-driver/v2 v2.5.0 go.uber.org/zap v1.27.1 - google.golang.org/grpc v1.79.0 + google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260212005555-3a7e5700f354 // indirect + github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260213131322-086e44a26cf3 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.24.4 // indirect github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect diff --git a/api/gateway/chain/go.sum b/api/gateway/chain/go.sum index 0adbce5a..be993286 100644 --- a/api/gateway/chain/go.sum +++ b/api/gateway/chain/go.sum @@ -6,8 +6,8 @@ github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260212005555-3a7e5700f354 h1:BgaMXBpcqcW74afzqI3iKo07K3tC+VuyWU3/FIvLlNI= -github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260212005555-3a7e5700f354/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI= +github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260213131322-086e44a26cf3 h1:QD30TjDPWtvXb5PBZGZ6Wdvaq7HQixIBtZ/yuseNXc8= +github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260213131322-086e44a26cf3/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI= github.com/VictoriaMetrics/fastcache v1.13.0 h1:AW4mheMR5Vd9FkAPUv+NH6Nhw+fmbTMGMsNAoA/+4G0= github.com/VictoriaMetrics/fastcache v1.13.0/go.mod h1:hHXhl4DA2fTL2HTZDJFXWgW0LNjo6B+4aj2Wmng3TjU= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -362,8 +362,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= -google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/gateway/mntx/go.mod b/api/gateway/mntx/go.mod index 6bdedf08..0bfa7825 100644 --- a/api/gateway/mntx/go.mod +++ b/api/gateway/mntx/go.mod @@ -11,7 +11,7 @@ require ( github.com/tech/sendico/pkg v0.1.0 go.mongodb.org/mongo-driver/v2 v2.5.0 go.uber.org/zap v1.27.1 - google.golang.org/grpc v1.79.0 + google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/api/gateway/mntx/go.sum b/api/gateway/mntx/go.sum index 546641fd..5cf7861e 100644 --- a/api/gateway/mntx/go.sum +++ b/api/gateway/mntx/go.sum @@ -212,8 +212,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= -google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/gateway/tgsettle/go.mod b/api/gateway/tgsettle/go.mod index 991c6fff..e3eab7da 100644 --- a/api/gateway/tgsettle/go.mod +++ b/api/gateway/tgsettle/go.mod @@ -8,7 +8,7 @@ require ( github.com/tech/sendico/pkg v0.1.0 go.mongodb.org/mongo-driver/v2 v2.5.0 go.uber.org/zap v1.27.1 - google.golang.org/grpc v1.79.0 + google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/api/gateway/tgsettle/go.sum b/api/gateway/tgsettle/go.sum index 35825f0b..3552c709 100644 --- a/api/gateway/tgsettle/go.sum +++ b/api/gateway/tgsettle/go.sum @@ -210,8 +210,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= -google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/gateway/tron/go.mod b/api/gateway/tron/go.mod index aaa406d1..a7cb8a06 100644 --- a/api/gateway/tron/go.mod +++ b/api/gateway/tron/go.mod @@ -17,14 +17,14 @@ require ( github.com/tech/sendico/pkg v0.1.0 go.mongodb.org/mongo-driver/v2 v2.5.0 go.uber.org/zap v1.27.1 - google.golang.org/grpc v1.79.0 + google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260212005555-3a7e5700f354 // indirect + github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260213131322-086e44a26cf3 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.24.4 // indirect github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect diff --git a/api/gateway/tron/go.sum b/api/gateway/tron/go.sum index 21f0b502..7b0b6710 100644 --- a/api/gateway/tron/go.sum +++ b/api/gateway/tron/go.sum @@ -6,8 +6,8 @@ github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260212005555-3a7e5700f354 h1:BgaMXBpcqcW74afzqI3iKo07K3tC+VuyWU3/FIvLlNI= -github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260212005555-3a7e5700f354/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI= +github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260213131322-086e44a26cf3 h1:QD30TjDPWtvXb5PBZGZ6Wdvaq7HQixIBtZ/yuseNXc8= +github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260213131322-086e44a26cf3/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI= github.com/VictoriaMetrics/fastcache v1.13.0 h1:AW4mheMR5Vd9FkAPUv+NH6Nhw+fmbTMGMsNAoA/+4G0= github.com/VictoriaMetrics/fastcache v1.13.0/go.mod h1:hHXhl4DA2fTL2HTZDJFXWgW0LNjo6B+4aj2Wmng3TjU= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -383,8 +383,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1: google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= -google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/ledger/go.mod b/api/ledger/go.mod index 4c30bd16..91f63e60 100644 --- a/api/ledger/go.mod +++ b/api/ledger/go.mod @@ -11,7 +11,7 @@ require ( github.com/tech/sendico/pkg v0.1.0 go.mongodb.org/mongo-driver/v2 v2.5.0 go.uber.org/zap v1.27.1 - google.golang.org/grpc v1.79.0 + google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/api/ledger/go.sum b/api/ledger/go.sum index 80161577..fd724be9 100644 --- a/api/ledger/go.sum +++ b/api/ledger/go.sum @@ -212,8 +212,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= -google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/notification/go.mod b/api/notification/go.mod index ef296a3f..7766b98c 100644 --- a/api/notification/go.mod +++ b/api/notification/go.mod @@ -51,6 +51,6 @@ require ( golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.41.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect - google.golang.org/grpc v1.79.0 // indirect + google.golang.org/grpc v1.79.1 // indirect google.golang.org/protobuf v1.36.11 // indirect ) diff --git a/api/notification/go.sum b/api/notification/go.sum index 8246be3b..1e2c6e6b 100644 --- a/api/notification/go.sum +++ b/api/notification/go.sum @@ -227,8 +227,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= -google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/payments/methods/go.mod b/api/payments/methods/go.mod index 75040980..2980ab15 100644 --- a/api/payments/methods/go.mod +++ b/api/payments/methods/go.mod @@ -11,7 +11,8 @@ require ( github.com/tech/sendico/pkg v0.1.0 go.mongodb.org/mongo-driver/v2 v2.5.0 go.uber.org/zap v1.27.1 - google.golang.org/grpc v1.79.0 + google.golang.org/grpc v1.79.1 + google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 ) @@ -48,5 +49,4 @@ require ( golang.org/x/sys v0.41.0 // indirect golang.org/x/text v0.34.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect - google.golang.org/protobuf v1.36.11 // indirect ) diff --git a/api/payments/methods/go.sum b/api/payments/methods/go.sum index 35825f0b..3552c709 100644 --- a/api/payments/methods/go.sum +++ b/api/payments/methods/go.sum @@ -210,8 +210,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= -google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/payments/methods/internal/service/methods/create.go b/api/payments/methods/internal/service/methods/create.go index 4f5eb58c..7753e584 100644 --- a/api/payments/methods/internal/service/methods/create.go +++ b/api/payments/methods/internal/service/methods/create.go @@ -24,7 +24,7 @@ func (s *Service) CreatePaymentMethod(ctx context.Context, req *methodsv1.Create return autoError[methodsv1.CreatePaymentMethodResponse](ctx, s.logger, err) } - pm, err := decodePaymentMethod(req.GetPaymentMethodJson()) + pm, err := decodePaymentMethodPayload(req.GetPaymentMethod(), "payment_method") if err != nil { return autoError[methodsv1.CreatePaymentMethodResponse](ctx, s.logger, err) } @@ -32,10 +32,10 @@ func (s *Service) CreatePaymentMethod(ctx context.Context, req *methodsv1.Create return autoError[methodsv1.CreatePaymentMethodResponse](ctx, s.logger, err) } - payload, err := encodePaymentMethod(pm) + record, err := encodePaymentMethodRecord(pm) if err != nil { return autoError[methodsv1.CreatePaymentMethodResponse](ctx, s.logger, err) } - return &methodsv1.CreatePaymentMethodResponse{PaymentMethodJson: payload}, nil + return &methodsv1.CreatePaymentMethodResponse{PaymentMethodRecord: record}, nil } diff --git a/api/payments/methods/internal/service/methods/get.go b/api/payments/methods/internal/service/methods/get.go index d014767a..64da7171 100644 --- a/api/payments/methods/internal/service/methods/get.go +++ b/api/payments/methods/internal/service/methods/get.go @@ -29,10 +29,10 @@ func (s *Service) GetPaymentMethod(ctx context.Context, req *methodsv1.GetPaymen return autoError[methodsv1.GetPaymentMethodResponse](ctx, s.logger, err) } - payload, err := encodePaymentMethod(pm) + record, err := encodePaymentMethodRecord(pm) if err != nil { return autoError[methodsv1.GetPaymentMethodResponse](ctx, s.logger, err) } - return &methodsv1.GetPaymentMethodResponse{PaymentMethodJson: payload}, nil + return &methodsv1.GetPaymentMethodResponse{PaymentMethodRecord: record}, nil } diff --git a/api/payments/methods/internal/service/methods/list.go b/api/payments/methods/internal/service/methods/list.go index 698406ce..48d702d7 100644 --- a/api/payments/methods/internal/service/methods/list.go +++ b/api/payments/methods/internal/service/methods/list.go @@ -4,6 +4,7 @@ import ( "context" "github.com/tech/sendico/pkg/merrors" + endpointv1 "github.com/tech/sendico/pkg/proto/payments/endpoint/v1" methodsv1 "github.com/tech/sendico/pkg/proto/payments/methods/v1" ) @@ -33,16 +34,16 @@ func (s *Service) ListPaymentMethods(ctx context.Context, req *methodsv1.ListPay return autoError[methodsv1.ListPaymentMethodsResponse](ctx, s.logger, err) } - result := make([][]byte, 0, len(items)) + result := make([]*endpointv1.PaymentMethodRecord, 0, len(items)) for i := range items { - payload, err := encodePaymentMethod(&items[i]) + record, err := encodePaymentMethodRecord(&items[i]) if err != nil { return autoError[methodsv1.ListPaymentMethodsResponse](ctx, s.logger, err) } - result = append(result, payload) + result = append(result, record) } return &methodsv1.ListPaymentMethodsResponse{ - PaymentMethodsJson: result, + PaymentMethods: result, }, nil } diff --git a/api/payments/methods/internal/service/methods/update.go b/api/payments/methods/internal/service/methods/update.go index 2f880c51..d136fb00 100644 --- a/api/payments/methods/internal/service/methods/update.go +++ b/api/payments/methods/internal/service/methods/update.go @@ -20,7 +20,7 @@ func (s *Service) UpdatePaymentMethod(ctx context.Context, req *methodsv1.Update return autoError[methodsv1.UpdatePaymentMethodResponse](ctx, s.logger, err) } - pm, err := decodePaymentMethod(req.GetPaymentMethodJson()) + pm, err := decodePaymentMethodRecord(req.GetPaymentMethodRecord()) if err != nil { return autoError[methodsv1.UpdatePaymentMethodResponse](ctx, s.logger, err) } @@ -28,10 +28,10 @@ func (s *Service) UpdatePaymentMethod(ctx context.Context, req *methodsv1.Update return autoError[methodsv1.UpdatePaymentMethodResponse](ctx, s.logger, err) } - payload, err := encodePaymentMethod(pm) + record, err := encodePaymentMethodRecord(pm) if err != nil { return autoError[methodsv1.UpdatePaymentMethodResponse](ctx, s.logger, err) } - return &methodsv1.UpdatePaymentMethodResponse{PaymentMethodJson: payload}, nil + return &methodsv1.UpdatePaymentMethodResponse{PaymentMethodRecord: record}, nil } diff --git a/api/payments/methods/internal/service/methods/util.go b/api/payments/methods/internal/service/methods/util.go index da1d1fac..25496261 100644 --- a/api/payments/methods/internal/service/methods/util.go +++ b/api/payments/methods/internal/service/methods/util.go @@ -2,17 +2,24 @@ package methods import ( "context" - "encoding/json" "fmt" "strings" + "time" "github.com/tech/sendico/pkg/api/routers/gsresponse" "github.com/tech/sendico/pkg/merrors" "github.com/tech/sendico/pkg/mlogger" "github.com/tech/sendico/pkg/model" "github.com/tech/sendico/pkg/mservice" + archivablev1 "github.com/tech/sendico/pkg/proto/common/archivable/v1" + describablev1 "github.com/tech/sendico/pkg/proto/common/describable/v1" + oboundv1 "github.com/tech/sendico/pkg/proto/common/organization_bound/v1" paginationv2 "github.com/tech/sendico/pkg/proto/common/pagination/v2" + pboundv1 "github.com/tech/sendico/pkg/proto/common/permission_bound/v1" + storablev1 "github.com/tech/sendico/pkg/proto/common/storable/v1" + endpointv1 "github.com/tech/sendico/pkg/proto/payments/endpoint/v1" "go.mongodb.org/mongo-driver/v2/bson" + "google.golang.org/protobuf/types/known/timestamppb" ) func autoError[T any](ctx context.Context, logger mlogger.Logger, err error) (*T, error) { @@ -31,26 +38,228 @@ func parseObjectID(value, field string) (bson.ObjectID, error) { return ref, nil } -func decodePaymentMethod(data []byte) (*model.PaymentMethod, error) { - if len(data) == 0 { - return nil, merrors.InvalidArgument("payment_method_json is required", "payment_method_json") +func decodePaymentMethodRecord(record *endpointv1.PaymentMethodRecord) (*model.PaymentMethod, error) { + if record == nil { + return nil, merrors.InvalidArgument("payment_method_record is required", "payment_method_record") } - res := &model.PaymentMethod{} - if err := json.Unmarshal(data, res); err != nil { - return nil, merrors.InvalidArgumentWrap(err, "failed to decode payment method", "payment_method_json") + res, err := decodePaymentMethodPayload(record.GetPaymentMethod(), "payment_method_record.payment_method") + if err != nil { + return nil, err + } + if err := applyPermissionBoundRecord(res, record.GetPermissionBound()); err != nil { + return nil, err } return res, nil } -func encodePaymentMethod(pm *model.PaymentMethod) ([]byte, error) { +func decodePaymentMethodPayload(method *endpointv1.PaymentMethod, field string) (*model.PaymentMethod, error) { + if method == nil { + return nil, merrors.InvalidArgument(field+" is required", field) + } + + recipientRef, err := parseObjectID(method.GetRecipientRef(), field+".recipient_ref") + if err != nil { + return nil, err + } + pt, err := paymentTypeFromProto(method.GetType(), field+".type") + if err != nil { + return nil, err + } + + return &model.PaymentMethod{ + Describable: describableFromProto(method.GetDescribable()), + RecipientRef: recipientRef, + Type: pt, + Data: cloneBytes(method.GetData()), + IsMain: method.GetIsMain(), + }, nil +} + +func encodePaymentMethodRecord(pm *model.PaymentMethod) (*endpointv1.PaymentMethodRecord, error) { if pm == nil { return nil, merrors.InvalidArgument("payment method is required") } - payload, err := json.Marshal(pm) + pt, err := paymentTypeToProto(pm.Type) if err != nil { - return nil, merrors.InternalWrap(err, "failed to encode payment method") + return nil, err } - return payload, nil + + return &endpointv1.PaymentMethodRecord{ + PermissionBound: permissionBoundFromModel(pm), + PaymentMethod: &endpointv1.PaymentMethod{ + Describable: describableToProto(pm.Describable), + RecipientRef: toObjectHex(pm.RecipientRef), + Type: pt, + Data: cloneBytes(pm.Data), + IsMain: pm.IsMain, + }, + }, nil +} + +func paymentTypeFromProto(value endpointv1.PaymentMethodType, field string) (model.PaymentType, error) { + switch value { + case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_IBAN: + return model.PaymentTypeIban, nil + case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_CARD: + return model.PaymentTypeCard, nil + case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_CARD_TOKEN: + return model.PaymentTypeCardToken, nil + case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_BANK_ACCOUNT: + return model.PaymentTypeBankAccount, nil + case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_WALLET: + return model.PaymentTypeWallet, nil + case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_CRYPTO_ADDRESS: + return model.PaymentTypeCryptoAddress, nil + case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_LEDGER: + return model.PaymentTypeLedger, nil + default: + return model.PaymentTypeIban, merrors.InvalidArgument(fmt.Sprintf("%s has unsupported value: %s", field, value.String()), field) + } +} + +func paymentTypeToProto(value model.PaymentType) (endpointv1.PaymentMethodType, error) { + switch value { + case model.PaymentTypeIban: + return endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_IBAN, nil + case model.PaymentTypeCard: + return endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_CARD, nil + case model.PaymentTypeCardToken: + return endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_CARD_TOKEN, nil + case model.PaymentTypeBankAccount: + return endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_BANK_ACCOUNT, nil + case model.PaymentTypeWallet: + return endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_WALLET, nil + case model.PaymentTypeCryptoAddress: + return endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_CRYPTO_ADDRESS, nil + case model.PaymentTypeLedger: + return endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_LEDGER, nil + default: + return endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_UNSPECIFIED, merrors.InvalidArgument(fmt.Sprintf("unsupported payment method type: %s", value.String()), "type") + } +} + +func describableFromProto(src *describablev1.Describable) model.Describable { + if src == nil { + return model.Describable{} + } + res := model.Describable{Name: src.GetName()} + if src.Description != nil { + v := src.GetDescription() + res.Description = &v + } + return res +} + +func describableToProto(src model.Describable) *describablev1.Describable { + if strings.TrimSpace(src.Name) == "" && src.Description == nil { + return nil + } + res := &describablev1.Describable{ + Name: src.Name, + } + if src.Description != nil { + v := *src.Description + res.Description = &v + } + return res +} + +func cloneBytes(src []byte) []byte { + if len(src) == 0 { + return nil + } + dst := make([]byte, len(src)) + copy(dst, src) + return dst +} + +func applyPermissionBoundRecord(pm *model.PaymentMethod, src *pboundv1.PermissionBound) error { + if pm == nil || src == nil { + return nil + } + + if storable := src.GetStorable(); storable != nil { + if methodRef, err := parseOptionalObjectID(storable.GetId(), "payment_method_record.permission_bound.storable.id"); err != nil { + return err + } else if methodRef != bson.NilObjectID { + pm.ID = methodRef + } + pm.CreatedAt = fromProtoTime(storable.GetCreatedAt()) + pm.UpdatedAt = fromProtoTime(storable.GetUpdatedAt()) + } + + if archivable := src.GetArchivable(); archivable != nil { + pm.Archived = archivable.GetIsArchived() + } + + if orgBound := src.GetOrganizationBound(); orgBound != nil { + if orgRef, err := parseOptionalObjectID(orgBound.GetOrganizationRef(), "payment_method_record.permission_bound.organization_bound.organization_ref"); err != nil { + return err + } else if orgRef != bson.NilObjectID { + pm.SetOrganizationRef(orgRef) + } + } + + if permissionRef, err := parseOptionalObjectID(src.GetPermissionRef(), "payment_method_record.permission_bound.permission_ref"); err != nil { + return err + } else if permissionRef != bson.NilObjectID { + pm.SetPermissionRef(permissionRef) + } + + return nil +} + +func permissionBoundFromModel(pm *model.PaymentMethod) *pboundv1.PermissionBound { + if pm == nil { + return nil + } + return &pboundv1.PermissionBound{ + Storable: &storablev1.Storable{ + Id: toObjectHex(pm.ID), + CreatedAt: toProtoTime(pm.CreatedAt), + UpdatedAt: toProtoTime(pm.UpdatedAt), + }, + Archivable: &archivablev1.Archivable{ + IsArchived: pm.Archived, + }, + OrganizationBound: &oboundv1.OrganizationBound{ + OrganizationRef: toObjectHex(pm.GetOrganizationRef()), + }, + PermissionRef: toObjectHex(pm.GetPermissionRef()), + } +} + +func parseOptionalObjectID(value, field string) (bson.ObjectID, error) { + trimmed := strings.TrimSpace(value) + if trimmed == "" { + return bson.NilObjectID, nil + } + ref, err := bson.ObjectIDFromHex(trimmed) + if err != nil { + return bson.NilObjectID, merrors.InvalidArgument(fmt.Sprintf("%s must be a valid object id", field), field) + } + return ref, nil +} + +func toObjectHex(value bson.ObjectID) string { + if value == bson.NilObjectID { + return "" + } + return value.Hex() +} + +func toProtoTime(value time.Time) *timestamppb.Timestamp { + if value.IsZero() { + return nil + } + return timestamppb.New(value) +} + +func fromProtoTime(value *timestamppb.Timestamp) time.Time { + if value == nil { + return time.Time{} + } + return value.AsTime() } func toModelCursor(cursor *paginationv2.ViewCursor) *model.ViewCursor { diff --git a/api/payments/orchestrator/go.mod b/api/payments/orchestrator/go.mod index f30ae969..97632f98 100644 --- a/api/payments/orchestrator/go.mod +++ b/api/payments/orchestrator/go.mod @@ -28,7 +28,7 @@ require ( github.com/tech/sendico/pkg v0.1.0 go.mongodb.org/mongo-driver/v2 v2.5.0 go.uber.org/zap v1.27.1 - google.golang.org/grpc v1.79.0 + google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/api/payments/orchestrator/go.sum b/api/payments/orchestrator/go.sum index 07e82f8b..db932601 100644 --- a/api/payments/orchestrator/go.sum +++ b/api/payments/orchestrator/go.sum @@ -213,8 +213,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= -google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/payments/orchestrator/internal/service/orchestrator/convert.go b/api/payments/orchestrator/internal/service/orchestrator/convert.go index 6aa8ea7d..3516d077 100644 --- a/api/payments/orchestrator/internal/service/orchestrator/convert.go +++ b/api/payments/orchestrator/internal/service/orchestrator/convert.go @@ -685,6 +685,10 @@ func fxQuoteFromProto(quote *oraclev1.Quote) *paymenttypes.FXQuote { if quote == nil { return nil } + pricedAtUnixMs := int64(0) + if ts := quote.GetPricedAt(); ts != nil { + pricedAtUnixMs = ts.AsTime().UnixMilli() + } return &paymenttypes.FXQuote{ QuoteRef: strings.TrimSpace(quote.GetQuoteRef()), Pair: pairFromProto(quote.GetPair()), @@ -693,6 +697,7 @@ func fxQuoteFromProto(quote *oraclev1.Quote) *paymenttypes.FXQuote { BaseAmount: moneyFromProto(quote.GetBaseAmount()), QuoteAmount: moneyFromProto(quote.GetQuoteAmount()), ExpiresAtUnixMs: quote.GetExpiresAtUnixMs(), + PricedAtUnixMs: pricedAtUnixMs, Provider: strings.TrimSpace(quote.GetProvider()), RateRef: strings.TrimSpace(quote.GetRateRef()), Firm: quote.GetFirm(), @@ -703,6 +708,10 @@ func fxQuoteToProto(quote *paymenttypes.FXQuote) *oraclev1.Quote { if quote == nil { return nil } + var pricedAt *timestamppb.Timestamp + if quote.PricedAtUnixMs > 0 { + pricedAt = timestamppb.New(time.UnixMilli(quote.PricedAtUnixMs).UTC()) + } return &oraclev1.Quote{ QuoteRef: strings.TrimSpace(quote.QuoteRef), Pair: pairToProto(quote.Pair), @@ -711,6 +720,7 @@ func fxQuoteToProto(quote *paymenttypes.FXQuote) *oraclev1.Quote { BaseAmount: protoMoney(quote.BaseAmount), QuoteAmount: protoMoney(quote.QuoteAmount), ExpiresAtUnixMs: quote.ExpiresAtUnixMs, + PricedAt: pricedAt, Provider: strings.TrimSpace(quote.Provider), RateRef: strings.TrimSpace(quote.RateRef), Firm: quote.Firm, diff --git a/api/payments/orchestrator/internal/service/orchestrator/convert_types_test.go b/api/payments/orchestrator/internal/service/orchestrator/convert_types_test.go index 4362a380..40fc62b5 100644 --- a/api/payments/orchestrator/internal/service/orchestrator/convert_types_test.go +++ b/api/payments/orchestrator/internal/service/orchestrator/convert_types_test.go @@ -2,6 +2,7 @@ package orchestrator import ( "testing" + "time" paymenttypes "github.com/tech/sendico/pkg/payments/types" feesv1 "github.com/tech/sendico/pkg/proto/billing/fees/v1" @@ -10,6 +11,7 @@ import ( moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1" chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1" oraclev1 "github.com/tech/sendico/pkg/proto/oracle/v1" + "google.golang.org/protobuf/types/known/timestamppb" ) func TestMoneyConversionRoundTrip(t *testing.T) { @@ -69,6 +71,7 @@ func TestFeeLineConversionRoundTrip(t *testing.T) { } func TestFXQuoteConversionRoundTrip(t *testing.T) { + pricedAt := int64(1700000000000) proto := &oraclev1.Quote{ QuoteRef: "q1", Pair: &fxv1.CurrencyPair{Base: "USD", Quote: "EUR"}, @@ -77,6 +80,7 @@ func TestFXQuoteConversionRoundTrip(t *testing.T) { BaseAmount: &moneyv1.Money{Currency: "USD", Amount: "100"}, QuoteAmount: &moneyv1.Money{Currency: "EUR", Amount: "90"}, ExpiresAtUnixMs: 1700000000000, + PricedAt: timestamppb.New(time.UnixMilli(pricedAt).UTC()), Provider: "provider", RateRef: "rate", Firm: true, @@ -88,6 +92,9 @@ func TestFXQuoteConversionRoundTrip(t *testing.T) { if model.Side != paymenttypes.FXSideSellBaseBuyQuote || model.Price.GetValue() != "0.9" { t.Fatalf("fxQuoteFromProto enums mismatch: %#v", model) } + if model.PricedAtUnixMs != pricedAt { + t.Fatalf("fxQuoteFromProto priced_at mismatch: %#v", model) + } back := fxQuoteToProto(model) if back == nil || back.GetQuoteRef() != "q1" || back.GetPair().GetBase() != "USD" || back.GetPair().GetQuote() != "EUR" { t.Fatalf("fxQuoteToProto mismatch: %#v", back) @@ -95,6 +102,9 @@ func TestFXQuoteConversionRoundTrip(t *testing.T) { if back.GetSide() != fxv1.Side_SELL_BASE_BUY_QUOTE || back.GetPrice().GetValue() != "0.9" { t.Fatalf("fxQuoteToProto enums mismatch: %#v", back) } + if got := back.GetPricedAt(); got == nil || got.AsTime().UnixMilli() != pricedAt { + t.Fatalf("fxQuoteToProto priced_at mismatch: %#v", back) + } } func TestAssetConversionRoundTrip(t *testing.T) { diff --git a/api/payments/orchestrator/internal/service/orchestrator/payment_plan_storage.go b/api/payments/orchestrator/internal/service/orchestrator/payment_plan_storage.go index a5b6eda1..c77fb794 100644 --- a/api/payments/orchestrator/internal/service/orchestrator/payment_plan_storage.go +++ b/api/payments/orchestrator/internal/service/orchestrator/payment_plan_storage.go @@ -69,6 +69,7 @@ func cloneStoredFXQuote(src *paymenttypes.FXQuote) *paymenttypes.FXQuote { QuoteRef: strings.TrimSpace(src.QuoteRef), Side: src.Side, ExpiresAtUnixMs: src.ExpiresAtUnixMs, + PricedAtUnixMs: src.PricedAtUnixMs, Provider: strings.TrimSpace(src.Provider), RateRef: strings.TrimSpace(src.RateRef), Firm: src.Firm, diff --git a/api/payments/orchestrator/internal/service/plan_builder/helpers.go b/api/payments/orchestrator/internal/service/plan_builder/helpers.go index 667d7e1a..865eaa6f 100644 --- a/api/payments/orchestrator/internal/service/plan_builder/helpers.go +++ b/api/payments/orchestrator/internal/service/plan_builder/helpers.go @@ -2,6 +2,7 @@ package plan_builder import ( "strings" + "time" "github.com/shopspring/decimal" "github.com/tech/sendico/payments/storage/model" @@ -15,6 +16,7 @@ import ( chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1" oraclev1 "github.com/tech/sendico/pkg/proto/oracle/v1" sharedv1 "github.com/tech/sendico/pkg/proto/payments/shared/v1" + "google.golang.org/protobuf/types/known/timestamppb" ) type moneyGetter interface { @@ -226,6 +228,10 @@ func fxQuoteFromProto(quote *oraclev1.Quote) *paymenttypes.FXQuote { if quote == nil { return nil } + pricedAtUnixMs := int64(0) + if ts := quote.GetPricedAt(); ts != nil { + pricedAtUnixMs = ts.AsTime().UnixMilli() + } return &paymenttypes.FXQuote{ QuoteRef: strings.TrimSpace(quote.GetQuoteRef()), Pair: pairFromProto(quote.GetPair()), @@ -234,6 +240,7 @@ func fxQuoteFromProto(quote *oraclev1.Quote) *paymenttypes.FXQuote { BaseAmount: moneyFromProto(quote.GetBaseAmount()), QuoteAmount: moneyFromProto(quote.GetQuoteAmount()), ExpiresAtUnixMs: quote.GetExpiresAtUnixMs(), + PricedAtUnixMs: pricedAtUnixMs, Provider: strings.TrimSpace(quote.GetProvider()), RateRef: strings.TrimSpace(quote.GetRateRef()), Firm: quote.GetFirm(), @@ -244,6 +251,10 @@ func fxQuoteToProto(quote *paymenttypes.FXQuote) *oraclev1.Quote { if quote == nil { return nil } + var pricedAt *timestamppb.Timestamp + if quote.PricedAtUnixMs > 0 { + pricedAt = timestamppb.New(time.UnixMilli(quote.PricedAtUnixMs).UTC()) + } return &oraclev1.Quote{ QuoteRef: strings.TrimSpace(quote.QuoteRef), Pair: pairToProto(quote.Pair), @@ -252,6 +263,7 @@ func fxQuoteToProto(quote *paymenttypes.FXQuote) *oraclev1.Quote { BaseAmount: protoMoney(quote.BaseAmount), QuoteAmount: protoMoney(quote.QuoteAmount), ExpiresAtUnixMs: quote.ExpiresAtUnixMs, + PricedAt: pricedAt, Provider: strings.TrimSpace(quote.Provider), RateRef: strings.TrimSpace(quote.RateRef), Firm: quote.Firm, diff --git a/api/payments/quotation/go.mod b/api/payments/quotation/go.mod index eb998142..8da6fa14 100644 --- a/api/payments/quotation/go.mod +++ b/api/payments/quotation/go.mod @@ -26,7 +26,7 @@ require ( github.com/tech/sendico/pkg v0.1.0 go.mongodb.org/mongo-driver/v2 v2.5.0 go.uber.org/zap v1.27.1 - google.golang.org/grpc v1.79.0 + google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/api/payments/quotation/go.sum b/api/payments/quotation/go.sum index c4d799eb..470bab37 100644 --- a/api/payments/quotation/go.sum +++ b/api/payments/quotation/go.sum @@ -213,8 +213,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= -google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/payments/quotation/internal/service/plan/helpers.go b/api/payments/quotation/internal/service/plan/helpers.go index e9a5ccbf..127f84e6 100644 --- a/api/payments/quotation/internal/service/plan/helpers.go +++ b/api/payments/quotation/internal/service/plan/helpers.go @@ -2,6 +2,7 @@ package plan import ( "strings" + "time" "github.com/shopspring/decimal" "github.com/tech/sendico/payments/storage/model" @@ -13,6 +14,7 @@ import ( moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1" oraclev1 "github.com/tech/sendico/pkg/proto/oracle/v1" sharedv1 "github.com/tech/sendico/pkg/proto/payments/shared/v1" + "google.golang.org/protobuf/types/known/timestamppb" ) type moneyGetter interface { @@ -158,6 +160,10 @@ func fxQuoteFromProto(quote *oraclev1.Quote) *paymenttypes.FXQuote { if quote == nil { return nil } + pricedAtUnixMs := int64(0) + if ts := quote.GetPricedAt(); ts != nil { + pricedAtUnixMs = ts.AsTime().UnixMilli() + } return &paymenttypes.FXQuote{ QuoteRef: strings.TrimSpace(quote.GetQuoteRef()), Pair: pairFromProto(quote.GetPair()), @@ -166,6 +172,7 @@ func fxQuoteFromProto(quote *oraclev1.Quote) *paymenttypes.FXQuote { BaseAmount: moneyFromProto(quote.GetBaseAmount()), QuoteAmount: moneyFromProto(quote.GetQuoteAmount()), ExpiresAtUnixMs: quote.GetExpiresAtUnixMs(), + PricedAtUnixMs: pricedAtUnixMs, Provider: strings.TrimSpace(quote.GetProvider()), RateRef: strings.TrimSpace(quote.GetRateRef()), Firm: quote.GetFirm(), @@ -176,6 +183,10 @@ func fxQuoteToProto(quote *paymenttypes.FXQuote) *oraclev1.Quote { if quote == nil { return nil } + var pricedAt *timestamppb.Timestamp + if quote.PricedAtUnixMs > 0 { + pricedAt = timestamppb.New(time.UnixMilli(quote.PricedAtUnixMs).UTC()) + } return &oraclev1.Quote{ QuoteRef: strings.TrimSpace(quote.QuoteRef), Pair: pairToProto(quote.Pair), @@ -184,6 +195,7 @@ func fxQuoteToProto(quote *paymenttypes.FXQuote) *oraclev1.Quote { BaseAmount: protoMoney(quote.BaseAmount), QuoteAmount: protoMoney(quote.QuoteAmount), ExpiresAtUnixMs: quote.ExpiresAtUnixMs, + PricedAt: pricedAt, Provider: strings.TrimSpace(quote.Provider), RateRef: strings.TrimSpace(quote.RateRef), Firm: quote.Firm, diff --git a/api/payments/quotation/internal/service/quotation/convert.go b/api/payments/quotation/internal/service/quotation/convert.go index 719d5af4..5d31a541 100644 --- a/api/payments/quotation/internal/service/quotation/convert.go +++ b/api/payments/quotation/internal/service/quotation/convert.go @@ -2,6 +2,7 @@ package quotation import ( "strings" + "time" "github.com/tech/sendico/payments/storage/model" chainasset "github.com/tech/sendico/pkg/chain" @@ -13,6 +14,7 @@ import ( chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1" oraclev1 "github.com/tech/sendico/pkg/proto/oracle/v1" sharedv1 "github.com/tech/sendico/pkg/proto/payments/shared/v1" + "google.golang.org/protobuf/types/known/timestamppb" ) func intentFromProto(src *sharedv1.PaymentIntent) model.PaymentIntent { @@ -423,6 +425,10 @@ func fxQuoteFromProto(quote *oraclev1.Quote) *paymenttypes.FXQuote { if quote == nil { return nil } + pricedAtUnixMs := int64(0) + if ts := quote.GetPricedAt(); ts != nil { + pricedAtUnixMs = ts.AsTime().UnixMilli() + } return &paymenttypes.FXQuote{ QuoteRef: strings.TrimSpace(quote.GetQuoteRef()), Pair: pairFromProto(quote.GetPair()), @@ -431,6 +437,7 @@ func fxQuoteFromProto(quote *oraclev1.Quote) *paymenttypes.FXQuote { BaseAmount: moneyFromProto(quote.GetBaseAmount()), QuoteAmount: moneyFromProto(quote.GetQuoteAmount()), ExpiresAtUnixMs: quote.GetExpiresAtUnixMs(), + PricedAtUnixMs: pricedAtUnixMs, Provider: strings.TrimSpace(quote.GetProvider()), RateRef: strings.TrimSpace(quote.GetRateRef()), Firm: quote.GetFirm(), @@ -441,6 +448,10 @@ func fxQuoteToProto(quote *paymenttypes.FXQuote) *oraclev1.Quote { if quote == nil { return nil } + var pricedAt *timestamppb.Timestamp + if quote.PricedAtUnixMs > 0 { + pricedAt = timestamppb.New(time.UnixMilli(quote.PricedAtUnixMs).UTC()) + } return &oraclev1.Quote{ QuoteRef: strings.TrimSpace(quote.QuoteRef), Pair: pairToProto(quote.Pair), @@ -449,6 +460,7 @@ func fxQuoteToProto(quote *paymenttypes.FXQuote) *oraclev1.Quote { BaseAmount: protoMoney(quote.BaseAmount), QuoteAmount: protoMoney(quote.QuoteAmount), ExpiresAtUnixMs: quote.ExpiresAtUnixMs, + PricedAt: pricedAt, Provider: strings.TrimSpace(quote.Provider), RateRef: strings.TrimSpace(quote.RateRef), Firm: quote.Firm, diff --git a/api/payments/quotation/internal/service/quotation/helpers.go b/api/payments/quotation/internal/service/quotation/helpers.go index 07e86cee..4389fa22 100644 --- a/api/payments/quotation/internal/service/quotation/helpers.go +++ b/api/payments/quotation/internal/service/quotation/helpers.go @@ -10,6 +10,7 @@ import ( oraclev1 "github.com/tech/sendico/pkg/proto/oracle/v1" sharedv1 "github.com/tech/sendico/pkg/proto/payments/shared/v1" "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" feesv1 "github.com/tech/sendico/pkg/proto/billing/fees/v1" accountingv1 "github.com/tech/sendico/pkg/proto/common/accounting/v1" @@ -293,6 +294,10 @@ func quoteToProto(src *oracleclient.Quote) *oraclev1.Quote { if src == nil { return nil } + var pricedAt *timestamppb.Timestamp + if !src.PricedAt.IsZero() { + pricedAt = timestamppb.New(src.PricedAt.UTC()) + } return &oraclev1.Quote{ QuoteRef: src.QuoteRef, Pair: src.Pair, @@ -301,6 +306,7 @@ func quoteToProto(src *oracleclient.Quote) *oraclev1.Quote { BaseAmount: cloneProtoMoney(src.BaseAmount), QuoteAmount: cloneProtoMoney(src.QuoteAmount), ExpiresAtUnixMs: src.ExpiresAt.UnixMilli(), + PricedAt: pricedAt, Provider: src.Provider, RateRef: src.RateRef, Firm: src.Firm, diff --git a/api/payments/quotation/internal/service/quotation/payment_plan_factory.go b/api/payments/quotation/internal/service/quotation/payment_plan_factory.go index c36083d7..2a31e594 100644 --- a/api/payments/quotation/internal/service/quotation/payment_plan_factory.go +++ b/api/payments/quotation/internal/service/quotation/payment_plan_factory.go @@ -119,6 +119,7 @@ func cloneStoredFXQuote(src *paymenttypes.FXQuote) *paymenttypes.FXQuote { QuoteRef: strings.TrimSpace(src.QuoteRef), Side: src.Side, ExpiresAtUnixMs: src.ExpiresAtUnixMs, + PricedAtUnixMs: src.PricedAtUnixMs, Provider: strings.TrimSpace(src.Provider), RateRef: strings.TrimSpace(src.RateRef), Firm: src.Firm, diff --git a/api/pkg/go.mod b/api/pkg/go.mod index ce46df58..f2ddc820 100644 --- a/api/pkg/go.mod +++ b/api/pkg/go.mod @@ -17,7 +17,7 @@ require ( go.mongodb.org/mongo-driver/v2 v2.5.0 go.uber.org/zap v1.27.1 golang.org/x/crypto v0.48.0 - google.golang.org/grpc v1.79.0 + google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 ) diff --git a/api/pkg/go.sum b/api/pkg/go.sum index cc8962ea..d129e3dc 100644 --- a/api/pkg/go.sum +++ b/api/pkg/go.sum @@ -273,8 +273,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1: google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= -google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/pkg/payments/types/fx.go b/api/pkg/payments/types/fx.go index 3d4cb81a..9399e248 100644 --- a/api/pkg/payments/types/fx.go +++ b/api/pkg/payments/types/fx.go @@ -38,6 +38,7 @@ type FXQuote struct { BaseAmount *Money `bson:"baseAmount,omitempty" json:"baseAmount,omitempty"` QuoteAmount *Money `bson:"quoteAmount,omitempty" json:"quoteAmount,omitempty"` ExpiresAtUnixMs int64 `bson:"expiresAtUnixMs,omitempty" json:"expiresAtUnixMs,omitempty"` + PricedAtUnixMs int64 `bson:"pricedAtUnixMs,omitempty" json:"pricedAtUnixMs,omitempty"` Provider string `bson:"provider,omitempty" json:"provider,omitempty"` RateRef string `bson:"rateRef,omitempty" json:"rateRef,omitempty"` Firm bool `bson:"firm,omitempty" json:"firm,omitempty"` @@ -85,6 +86,13 @@ func (q *FXQuote) GetExpiresAtUnixMs() int64 { return q.ExpiresAtUnixMs } +func (q *FXQuote) GetPricedAtUnixMs() int64 { + if q == nil { + return 0 + } + return q.PricedAtUnixMs +} + func (q *FXQuote) GetProvider() string { if q == nil { return "" diff --git a/api/proto/billing/fees/v1/fees.proto b/api/proto/billing/fees/v1/fees.proto index c9d29c5b..4b5e051d 100644 --- a/api/proto/billing/fees/v1/fees.proto +++ b/api/proto/billing/fees/v1/fees.proto @@ -5,10 +5,10 @@ package fees.v1; option go_package = "github.com/tech/sendico/pkg/proto/billing/fees/v1;feesv1"; import "google/protobuf/timestamp.proto"; -import "common/money/v1/money.proto"; -import "common/fx/v1/fx.proto"; -import "common/accounting/v1/posting.proto"; -import "common/trace/v1/trace.proto"; +import "api/proto/common/money/v1/money.proto"; +import "api/proto/common/fx/v1/fx.proto"; +import "api/proto/common/accounting/v1/posting.proto"; +import "api/proto/common/trace/v1/trace.proto"; // -------------------- // Core business enums @@ -74,10 +74,10 @@ message FXUsed { // A derived posting line ready for the ledger to post as-is. message DerivedPostingLine { - string ledger_account_ref = 1; // resolved account - common.money.v1.Money money = 2; // amount/currency - common.accounting.v1.PostingLineType line_type = 3; // FEE/TAX/SPREAD/REVERSAL - common.accounting.v1.EntrySide side = 4; // DEBIT/CREDIT + string ledger_account_ref = 1; // resolved account + common.money.v1.Money money = 2; // amount/currency + common.accounting.v1.PostingLineType line_type = 3; // FEE/TAX/SPREAD/REVERSAL + common.accounting.v1.EntrySide side = 4; // DEBIT/CREDIT map meta = 5; // fee_rule_id, rule_version, tax_code, tax_rate, fx_rate_used, etc. } @@ -87,8 +87,8 @@ message AppliedRule { string rule_version = 2; string formula = 3; // e.g., "2.90% + 0.30 (min 0.50)" common.money.v1.RoundingMode rounding = 4; - string tax_code = 5; // if applicable - string tax_rate = 6; // decimal string + string tax_code = 5; // if applicable + string tax_rate = 6; // decimal string map parameters = 7; // thresholds, tiers, etc. } diff --git a/api/proto/common/archivable/v1/archivable.proto b/api/proto/common/archivable/v1/archivable.proto new file mode 100644 index 00000000..2ba21733 --- /dev/null +++ b/api/proto/common/archivable/v1/archivable.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package common.archivable.v1; + +option go_package = "github.com/tech/sendico/pkg/proto/common/archivable/v1;archivablev1"; + + + +message Archivable { + bool is_archived = 1; +} \ No newline at end of file diff --git a/api/proto/common/gateway/v1/gateway.proto b/api/proto/common/gateway/v1/gateway.proto index 83da719f..a163508c 100644 --- a/api/proto/common/gateway/v1/gateway.proto +++ b/api/proto/common/gateway/v1/gateway.proto @@ -1,8 +1,11 @@ syntax = "proto3"; + package common.gateway.v1; + option go_package = "github.com/tech/sendico/pkg/proto/common/gateway/v1;gatewayv1"; -import "common/money/v1/money.proto"; +import "api/proto/common/money/v1/money.proto"; +import "api/proto/payments/endpoint/v1/endpoint.proto"; enum Operation { @@ -18,17 +21,6 @@ enum Operation { OPERATION_CREATE_ACCOUNT = 9; } -enum PaymentMethodType { - PM_UNSPECIFIED = 0; - PM_CARD = 1; - PM_SEPA = 2; - PM_ACH = 3; - PM_PIX = 4; - PM_WALLET = 5; - PM_CRYPTO = 6; - PM_LOCAL_BANK = 7; // generic local rails, refine later if needed -} - // Rail identifiers for orchestration. Extend with new rails as needed. enum Rail { RAIL_UNSPECIFIED = 0; @@ -70,7 +62,7 @@ message OperationCapabilities { // Per-method matrix entry message MethodCapability { - PaymentMethodType method = 1; + payments.endpoint.v1.PaymentMethod method = 1; // ISO 4217 currency codes, e.g. "EUR", "USD" repeated string currencies = 2; @@ -187,4 +179,4 @@ message OperationExecutionStatus { common.money.v1.Money executed_money = 3; OperationResult status = 4; OperationError error = 5; -} \ No newline at end of file +} diff --git a/api/proto/common/organization_bound/v1/obound.proto b/api/proto/common/organization_bound/v1/obound.proto new file mode 100644 index 00000000..93670c12 --- /dev/null +++ b/api/proto/common/organization_bound/v1/obound.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +package common.obound.v1; + +option go_package = "github.com/tech/sendico/pkg/proto/common/organization_bound/v1;oboundv1"; + + +message OrganizationBound { + string organization_ref = 1; +} \ No newline at end of file diff --git a/api/proto/common/payment/v1/card.proto b/api/proto/common/payment/v1/card.proto new file mode 100644 index 00000000..6b55b168 --- /dev/null +++ b/api/proto/common/payment/v1/card.proto @@ -0,0 +1,73 @@ +syntax = "proto3"; + +package common.payment.v1; + +option go_package = "github.com/tech/sendico/pkg/proto/common/payment/v1;paymentv1"; + +import "google/protobuf/wrappers.proto"; + + +// ------------------------- +// Card network (payment system) +// ------------------------- +enum CardNetwork { + CARD_NETWORK_UNSPECIFIED = 0; + CARD_NETWORK_VISA = 1; + CARD_NETWORK_MASTERCARD = 2; + CARD_NETWORK_MIR = 3; + CARD_NETWORK_AMEX = 4; + CARD_NETWORK_UNIONPAY = 5; + CARD_NETWORK_JCB = 6; + CARD_NETWORK_DISCOVER = 7; +} + +enum CardFundingType { + CARD_FUNDING_UNSPECIFIED = 0; + CARD_FUNDING_DEBIT = 1; + CARD_FUNDING_CREDIT = 2; + CARD_FUNDING_PREPAID = 3; +} + +// ------------------------- +// PCI scope: raw card details +// ------------------------- + +message RawCardData { + string pan = 1; + uint32 exp_month = 2; // 1–12 + uint32 exp_year = 3; // YYYY + string cvv = 4; // optional; often omitted for payouts +} + + +// ------------------------- +// Safe metadata (display / routing hints) +// ------------------------- +message CardMetadata { + string masked_pan = 1; // e.g. 411111******1111 + CardNetwork network = 2; // Visa/Mastercard/Mir/... + CardFundingType funding = 3; // debit/credit/prepaid (if known) + string issuing_country = 4; // ISO 3166-1 alpha-2 (if known) + string issuer_name = 5; // display only (if known) +} + + +// ------------------------- +// Card details +// Either inline credentials OR reference to stored payment method +// ------------------------- +message CardDetails { + string id = 1; + + oneof source { + RawCardData raw = 2; + string payment_method_id = 3; + } + + string cardholder_name = 4; + string cardholder_surname = 5; + + string billing_country = 6; // ISO 3166-1 alpha-2, if you need it per operation +} + + diff --git a/api/proto/common/payment/v1/custom.proto b/api/proto/common/payment/v1/custom.proto new file mode 100644 index 00000000..69278d77 --- /dev/null +++ b/api/proto/common/payment/v1/custom.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package common.payment.v1; + +option go_package = "github.com/tech/sendico/pkg/proto/common/payment/v1;paymentv1"; + + +message CustomPaymentDetails { + string id = 1; + bytes payment_method_json = 2; +} \ No newline at end of file diff --git a/api/proto/common/payment/v1/external_chain.proto b/api/proto/common/payment/v1/external_chain.proto new file mode 100644 index 00000000..e5a72084 --- /dev/null +++ b/api/proto/common/payment/v1/external_chain.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package common.payment.v1; + +option go_package = "github.com/tech/sendico/pkg/proto/common/payment/v1;paymentv1"; + +import "api/proto/gateway/chain/v1/chain.proto"; + + +message ExternalChainDetails { + string id = 1; + chain.gateway.v1.Asset asset = 2; + string address = 3; + string memo = 4; +} + diff --git a/api/proto/common/payment/v1/ledger.proto b/api/proto/common/payment/v1/ledger.proto new file mode 100644 index 00000000..e536d642 --- /dev/null +++ b/api/proto/common/payment/v1/ledger.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package common.payment.v1; + +option go_package = "github.com/tech/sendico/pkg/proto/common/payment/v1;paymentv1"; + + +message LedgerDetails { + string id = 1; + oneof source { + string ledger_account_ref = 2; + string account_code = 3; + } +} diff --git a/api/proto/common/payment/v1/managed_wallet.proto b/api/proto/common/payment/v1/managed_wallet.proto new file mode 100644 index 00000000..608cd780 --- /dev/null +++ b/api/proto/common/payment/v1/managed_wallet.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package common.payment.v1; + +option go_package = "github.com/tech/sendico/pkg/proto/common/payment/v1;paymentv1"; + + +message ManagedWalletDetails { + string id = 1; + string managed_wallet_ref = 2; +} \ No newline at end of file diff --git a/api/proto/common/payment/v1/rba.proto b/api/proto/common/payment/v1/rba.proto new file mode 100644 index 00000000..e1e0af14 --- /dev/null +++ b/api/proto/common/payment/v1/rba.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package common.payment.v1; + +option go_package = "github.com/tech/sendico/pkg/proto/common/payment/v1;paymentv1"; + +// ------------------------- +// Russian bank account details +// ------------------------- + +message RussianBankDetails { + string id = 1; + string account_number = 2; // 20 digits + string bik = 3; // 9 digits + string account_holder_name = 4; +} diff --git a/api/proto/common/payment/v1/sepa.proto b/api/proto/common/payment/v1/sepa.proto new file mode 100644 index 00000000..1293876c --- /dev/null +++ b/api/proto/common/payment/v1/sepa.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +package common.payment.v1; + +option go_package = "github.com/tech/sendico/pkg/proto/common/payment/v1;paymentv1"; + +// ------------------------- +// SEPA bank account details +// ------------------------- + +message SepaBankDetails { + string id = 1; + string iban = 2; // IBAN + string bic = 3; // optional (BIC/SWIFT) + string account_holder_name = 4; +} + diff --git a/api/proto/common/payment/v1/settlement.proto b/api/proto/common/payment/v1/settlement.proto new file mode 100644 index 00000000..02174885 --- /dev/null +++ b/api/proto/common/payment/v1/settlement.proto @@ -0,0 +1,13 @@ + +syntax = "proto3"; + +package common.payment.v1; + +option go_package = "github.com/tech/sendico/pkg/proto/common/payment/v1;paymentv1"; + +// SettlementMode defines how to treat fees/FX variance for payouts. +enum SettlementMode { + SETTLEMENT_UNSPECIFIED = 0; + SETTLEMENT_FIX_SOURCE = 1; // customer pays fees; sent amount fixed + SETTLEMENT_FIX_RECEIVED = 2; // receiver gets fixed amount; source flexes +} diff --git a/api/proto/common/permission_bound/v1/pbound.proto b/api/proto/common/permission_bound/v1/pbound.proto new file mode 100644 index 00000000..b991fa52 --- /dev/null +++ b/api/proto/common/permission_bound/v1/pbound.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +package common.pbound.v1; + +option go_package = "github.com/tech/sendico/pkg/proto/common/permission_bound/v1;pboundv1"; + +import "api/proto/common/storable/v1/storable.proto"; +import "api/proto/common/archivable/v1/archivable.proto"; +import "api/proto/common/organization_bound/v1/obound.proto"; + + +message PermissionBound { + common.storable.v1.Storable storable = 1; + common.archivable.v1.Archivable archivable = 2; + common.obound.v1.OrganizationBound organization_bound = 3; + string permission_ref = 4; +} \ No newline at end of file diff --git a/api/proto/common/storable/v1/storable.proto b/api/proto/common/storable/v1/storable.proto new file mode 100644 index 00000000..3fee7e5b --- /dev/null +++ b/api/proto/common/storable/v1/storable.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package common.storable.v1; + +option go_package = "github.com/tech/sendico/pkg/proto/common/storable/v1;storablev1"; + +import "google/protobuf/timestamp.proto"; + + +message Storable { + string id = 1; + google.protobuf.Timestamp created_at = 10; + google.protobuf.Timestamp updated_at = 11; +} diff --git a/api/proto/connector/v1/connector.proto b/api/proto/connector/v1/connector.proto index fb252e59..4735dda1 100644 --- a/api/proto/connector/v1/connector.proto +++ b/api/proto/connector/v1/connector.proto @@ -7,10 +7,10 @@ option go_package = "github.com/tech/sendico/pkg/proto/connector/v1;connectorv1" import "google/protobuf/struct.proto"; import "google/protobuf/timestamp.proto"; import "google/protobuf/wrappers.proto"; -import "common/account_role/v1/account_role.proto"; -import "common/describable/v1/describable.proto"; -import "common/money/v1/money.proto"; -import "common/pagination/v1/cursor.proto"; +import "api/proto/common/account_role/v1/account_role.proto"; +import "api/proto/common/describable/v1/describable.proto"; +import "api/proto/common/money/v1/money.proto"; +import "api/proto/common/pagination/v1/cursor.proto"; // ConnectorService exposes capability-driven account and operation primitives. service ConnectorService { diff --git a/api/proto/gateway/chain/v1/chain.proto b/api/proto/gateway/chain/v1/chain.proto index b28b484f..96e63c0a 100644 --- a/api/proto/gateway/chain/v1/chain.proto +++ b/api/proto/gateway/chain/v1/chain.proto @@ -6,9 +6,9 @@ option go_package = "github.com/tech/sendico/pkg/proto/gateway/chain/v1;chainv1" import "google/protobuf/timestamp.proto"; import "google/protobuf/wrappers.proto"; -import "common/money/v1/money.proto"; -import "common/pagination/v1/cursor.proto"; -import "common/describable/v1/describable.proto"; +import "api/proto/common/money/v1/money.proto"; +import "api/proto/common/pagination/v1/cursor.proto"; +import "api/proto/common/describable/v1/describable.proto"; // Supported blockchain networks for the managed wallets. enum ChainNetwork { diff --git a/api/proto/gateway/mntx/v1/mntx.proto b/api/proto/gateway/mntx/v1/mntx.proto index 834628c9..23e8fb88 100644 --- a/api/proto/gateway/mntx/v1/mntx.proto +++ b/api/proto/gateway/mntx/v1/mntx.proto @@ -5,7 +5,7 @@ package mntx.gateway.v1; option go_package = "github.com/tech/sendico/pkg/proto/gateway/mntx/v1;mntxv1"; import "google/protobuf/timestamp.proto"; -import "common/gateway/v1/gateway.proto"; +import "api/proto/common/gateway/v1/gateway.proto"; // Lifecycle status of a payout handled by Monetix. enum PayoutStatus { diff --git a/api/proto/ledger/v1/ledger.proto b/api/proto/ledger/v1/ledger.proto index 957b12bd..486bb084 100644 --- a/api/proto/ledger/v1/ledger.proto +++ b/api/proto/ledger/v1/ledger.proto @@ -6,8 +6,8 @@ option go_package = "github.com/tech/sendico/pkg/proto/ledger/v1;ledgerv1"; import "google/protobuf/timestamp.proto"; import "google/protobuf/wrappers.proto"; -import "common/describable/v1/describable.proto"; -import "common/money/v1/money.proto"; +import "api/proto/common/describable/v1/describable.proto"; +import "api/proto/common/money/v1/money.proto"; // ===== Enums ===== diff --git a/api/proto/oracle/v1/oracle.proto b/api/proto/oracle/v1/oracle.proto index eeff33cd..4e8675a3 100644 --- a/api/proto/oracle/v1/oracle.proto +++ b/api/proto/oracle/v1/oracle.proto @@ -4,9 +4,11 @@ package oracle.v1; option go_package = "github.com/tech/sendico/pkg/proto/oracle/v1;oraclev1"; -import "common/money/v1/money.proto"; -import "common/fx/v1/fx.proto"; -import "common/trace/v1/trace.proto"; +import "google/protobuf/timestamp.proto"; +import "api/proto/common/money/v1/money.proto"; +import "api/proto/common/fx/v1/fx.proto"; +import "api/proto/common/trace/v1/trace.proto"; + message RateSnapshot { common.fx.v1.CurrencyPair pair = 1; @@ -46,6 +48,7 @@ message Quote { string provider = 8; string rate_ref = 9; bool firm = 10; + google.protobuf.Timestamp priced_at = 11; } message GetQuoteRequest { diff --git a/api/proto/payments/endpoint/v1/endpoint.proto b/api/proto/payments/endpoint/v1/endpoint.proto new file mode 100644 index 00000000..de1bd114 --- /dev/null +++ b/api/proto/payments/endpoint/v1/endpoint.proto @@ -0,0 +1,40 @@ +syntax = "proto3"; + +package payments.endpoint.v1; + +option go_package = "github.com/tech/sendico/pkg/proto/payments/endpoint/v1;endpointv1"; + +import "api/proto/common/describable/v1/describable.proto"; +import "api/proto/common/permission_bound/v1/pbound.proto"; + +enum PaymentMethodType { + PAYMENT_METHOD_TYPE_UNSPECIFIED = 0; + PAYMENT_METHOD_TYPE_IBAN = 1; + PAYMENT_METHOD_TYPE_CARD = 2; + PAYMENT_METHOD_TYPE_CARD_TOKEN = 3; + PAYMENT_METHOD_TYPE_BANK_ACCOUNT = 4; + PAYMENT_METHOD_TYPE_WALLET = 5; + PAYMENT_METHOD_TYPE_CRYPTO_ADDRESS = 6; + PAYMENT_METHOD_TYPE_LEDGER = 7; +} + +message PaymentMethod { + common.describable.v1.Describable describable = 1; + string recipient_ref = 2; + PaymentMethodType type = 3; + bytes data = 4; + bool is_main = 5; +} + +message PaymentEndpoint { + oneof source { + string payment_method_ref = 1; + PaymentMethod payment_method = 2; + string payee_ref = 3; + } +} + +message PaymentMethodRecord { + common.pbound.v1.PermissionBound permission_bound = 1; + PaymentMethod payment_method = 2; +} diff --git a/api/proto/payments/methods/v1/methods.proto b/api/proto/payments/methods/v1/methods.proto index 37def79f..b2a4c964 100644 --- a/api/proto/payments/methods/v1/methods.proto +++ b/api/proto/payments/methods/v1/methods.proto @@ -4,18 +4,17 @@ package payments.methods.v1; option go_package = "github.com/tech/sendico/pkg/proto/payments/methods/v1;methodsv1"; -import "google/protobuf/wrappers.proto"; - -import "common/pagination/v2/cursor.proto"; +import "api/proto/common/pagination/v2/cursor.proto"; +import "api/proto/payments/endpoint/v1/endpoint.proto"; message CreatePaymentMethodRequest { string account_ref = 1; string organization_ref = 2; - bytes payment_method_json = 3; + payments.endpoint.v1.PaymentMethod payment_method = 3; } message CreatePaymentMethodResponse { - bytes payment_method_json = 1; + payments.endpoint.v1.PaymentMethodRecord payment_method_record = 1; } message GetPaymentMethodRequest { @@ -24,16 +23,16 @@ message GetPaymentMethodRequest { } message GetPaymentMethodResponse { - bytes payment_method_json = 1; + payments.endpoint.v1.PaymentMethodRecord payment_method_record = 1; } message UpdatePaymentMethodRequest { string account_ref = 1; - bytes payment_method_json = 2; + payments.endpoint.v1.PaymentMethodRecord payment_method_record = 2; } message UpdatePaymentMethodResponse { - bytes payment_method_json = 1; + payments.endpoint.v1.PaymentMethodRecord payment_method_record = 1; } message DeletePaymentMethodRequest { @@ -62,14 +61,21 @@ message ListPaymentMethodsRequest { } message ListPaymentMethodsResponse { - repeated bytes payment_methods_json = 1; + repeated payments.endpoint.v1.PaymentMethodRecord payment_methods = 1; } +// PaymentMethodsService provides operations for managing payment methods. service PaymentMethodsService { + // CreatePaymentMethod creates a new payment method. rpc CreatePaymentMethod(CreatePaymentMethodRequest) returns (CreatePaymentMethodResponse); + // GetPaymentMethod retrieves a payment method by reference. rpc GetPaymentMethod(GetPaymentMethodRequest) returns (GetPaymentMethodResponse); + // UpdatePaymentMethod updates an existing payment method. rpc UpdatePaymentMethod(UpdatePaymentMethodRequest) returns (UpdatePaymentMethodResponse); + // Delete exising payment method rpc DeletePaymentMethod(DeletePaymentMethodRequest) returns (DeletePaymentMethodResponse); + // SetPaymentMethodArchived sets the archived status of a payment method. rpc SetPaymentMethodArchived(SetPaymentMethodArchivedRequest) returns (SetPaymentMethodArchivedResponse); + // ListPaymentMethods retrieves a list of payment methods. rpc ListPaymentMethods(ListPaymentMethodsRequest) returns (ListPaymentMethodsResponse); } diff --git a/api/proto/payments/orchestration/v1/orchestration.proto b/api/proto/payments/orchestration/v1/orchestration.proto index 3aba9ceb..59da61d8 100644 --- a/api/proto/payments/orchestration/v1/orchestration.proto +++ b/api/proto/payments/orchestration/v1/orchestration.proto @@ -4,11 +4,11 @@ package payments.orchestration.v1; option go_package = "github.com/tech/sendico/pkg/proto/payments/orchestration/v1;orchestrationv1"; -import "common/pagination/v1/cursor.proto"; -import "billing/fees/v1/fees.proto"; -import "gateway/chain/v1/chain.proto"; -import "gateway/mntx/v1/mntx.proto"; -import "payments/shared/v1/shared.proto"; +import "api/proto/common/pagination/v1/cursor.proto"; +import "api/proto/billing/fees/v1/fees.proto"; +import "api/proto/gateway/chain/v1/chain.proto"; +import "api/proto/gateway/mntx/v1/mntx.proto"; +import "api/proto/payments/shared/v1/shared.proto"; message InitiatePaymentsRequest { payments.shared.v1.RequestMeta meta = 1; diff --git a/api/proto/payments/payment/v1/payment.proto b/api/proto/payments/payment/v1/payment.proto new file mode 100644 index 00000000..01c08216 --- /dev/null +++ b/api/proto/payments/payment/v1/payment.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +package payments.payment.v1; + +option go_package = "github.com/tech/sendico/pkg/proto/payments/payment/v1;paymentv1"; + +import "api/proto/payments/transfer/v1/transfer.proto"; + + +// ------------------------- +// External payment semantics +// ------------------------- +message PaymentIntent { + payments.transfer.v1.TransferIntent transfer = 1; + + string payer_ref = 2; + string payee_ref = 3; + + string purpose = 4; +} diff --git a/api/proto/payments/quotation/v1/quotation.proto b/api/proto/payments/quotation/v1/quotation.proto index 093eebc0..bc53f351 100644 --- a/api/proto/payments/quotation/v1/quotation.proto +++ b/api/proto/payments/quotation/v1/quotation.proto @@ -4,7 +4,8 @@ package payments.quotation.v1; option go_package = "github.com/tech/sendico/pkg/proto/payments/quotation/v1;quotationv1"; -import "payments/shared/v1/shared.proto"; +import "api/proto/payments/shared/v1/shared.proto"; + message QuotePaymentRequest { payments.shared.v1.RequestMeta meta = 1; @@ -35,6 +36,8 @@ message QuotePaymentsResponse { } service QuotationService { + // QuotePayment returns a quote for a single payment request. rpc QuotePayment(QuotePaymentRequest) returns (QuotePaymentResponse); + // QuotePayments returns quotes for multiple payment requests. rpc QuotePayments(QuotePaymentsRequest) returns (QuotePaymentsResponse); } diff --git a/api/proto/payments/quotation/v2/interface.proto b/api/proto/payments/quotation/v2/interface.proto new file mode 100644 index 00000000..d93f9499 --- /dev/null +++ b/api/proto/payments/quotation/v2/interface.proto @@ -0,0 +1,59 @@ +syntax = "proto3"; + +package payments.quotation.v2; + +option go_package = "github.com/tech/sendico/pkg/proto/payments/quotation/v2;quotationv2"; + +import "google/protobuf/timestamp.proto"; +import "api/proto/common/storable/v1/storable.proto"; +import "api/proto/common/money/v1/money.proto"; +import "api/proto/common/fx/v1/fx.proto"; +import "api/proto/billing/fees/v1/fees.proto"; +import "api/proto/oracle/v1/oracle.proto"; + +enum QuoteKind { + QUOTE_KIND_UNSPECIFIED = 0; + + QUOTE_KIND_EXECUTABLE = 1; // can be executed now (subject to execution-time checks) + QUOTE_KIND_INDICATIVE = 2; // informational only +} + +enum QuoteState { + QUOTE_STATE_UNSPECIFIED = 0; + + QUOTE_STATE_ACTIVE = 1; + QUOTE_STATE_EXPIRED = 2; +} + +enum QuoteBlockReason { + QUOTE_BLOCK_REASON_UNSPECIFIED = 0; + + QUOTE_BLOCK_REASON_ROUTE_UNAVAILABLE = 1; + QUOTE_BLOCK_REASON_LIMIT_BLOCKED = 2; + QUOTE_BLOCK_REASON_RISK_BLOCKED = 3; + QUOTE_BLOCK_REASON_INSUFFICIENT_LIQUIDITY = 4; + QUOTE_BLOCK_REASON_PRICE_STALE = 5; + QUOTE_BLOCK_REASON_AMOUNT_TOO_SMALL = 6; + QUOTE_BLOCK_REASON_AMOUNT_TOO_LARGE = 7; +} + + +message PaymentQuote { + common.storable.v1.Storable storable = 1; + QuoteKind kind = 2; + QuoteState state = 3; + optional QuoteBlockReason block_reason = 4; + + common.money.v1.Money debit_amount = 5; + common.money.v1.Money credit_amount = 6; + + repeated fees.v1.DerivedPostingLine fee_lines = 7; + repeated fees.v1.AppliedRule fee_rules = 8; + + oracle.v1.Quote fx_quote = 9; + + string quote_ref = 10; + + google.protobuf.Timestamp expires_at = 11; + google.protobuf.Timestamp priced_at = 12; +} diff --git a/api/proto/payments/quotation/v2/quotation.proto b/api/proto/payments/quotation/v2/quotation.proto new file mode 100644 index 00000000..d3580429 --- /dev/null +++ b/api/proto/payments/quotation/v2/quotation.proto @@ -0,0 +1,37 @@ +syntax = "proto3"; + +package payments.quotation.v2; + +option go_package = "github.com/tech/sendico/pkg/proto/payments/quotation/v2;quotationv2"; + +import "api/proto/payments/shared/v1/shared.proto"; +import "api/proto/payments/transfer/v1/transfer.proto"; +import "api/proto/payments/quotation/v2/interface.proto"; + + +message QuotePaymentRequest { + payments.shared.v1.RequestMeta meta = 1; + string idempotency_key = 2; + payments.transfer.v1.TransferIntent intent = 3; + bool preview_only = 4; + string initiator_ref = 5; +} + +message QuotePaymentResponse { + payments.quotation.v2.PaymentQuote quote = 1; + string idempotency_key = 2; +} + +message QuotePaymentsRequest { + payments.shared.v1.RequestMeta meta = 1; + string idempotency_key = 2; + repeated payments.transfer.v1.TransferIntent intents = 3; + bool preview_only = 4; + string initiator_ref = 5; +} + +message QuotePaymentsResponse { + string quote_ref = 1; + repeated payments.quotation.v2.PaymentQuote quotes = 3; + string idempotency_key = 4; +} diff --git a/api/proto/payments/shared/v1/shared.proto b/api/proto/payments/shared/v1/shared.proto index e14d0e4f..cc1a5a77 100644 --- a/api/proto/payments/shared/v1/shared.proto +++ b/api/proto/payments/shared/v1/shared.proto @@ -5,13 +5,14 @@ package payments.shared.v1; option go_package = "github.com/tech/sendico/pkg/proto/payments/shared/v1;sharedv1"; import "google/protobuf/timestamp.proto"; -import "common/money/v1/money.proto"; -import "common/fx/v1/fx.proto"; -import "common/gateway/v1/gateway.proto"; -import "common/trace/v1/trace.proto"; -import "billing/fees/v1/fees.proto"; -import "gateway/chain/v1/chain.proto"; -import "oracle/v1/oracle.proto"; +import "api/proto/common/money/v1/money.proto"; +import "api/proto/common/fx/v1/fx.proto"; +import "api/proto/common/gateway/v1/gateway.proto"; +import "api/proto/common/payment/v1/settlement.proto"; +import "api/proto/common/trace/v1/trace.proto"; +import "api/proto/billing/fees/v1/fees.proto"; +import "api/proto/gateway/chain/v1/chain.proto"; +import "api/proto/oracle/v1/oracle.proto"; enum PaymentKind { PAYMENT_KIND_UNSPECIFIED = 0; @@ -20,13 +21,6 @@ enum PaymentKind { PAYMENT_KIND_FX_CONVERSION = 3; } -// SettlementMode defines how to treat fees/FX variance for payouts. -enum SettlementMode { - SETTLEMENT_UNSPECIFIED = 0; - SETTLEMENT_FIX_SOURCE = 1; // customer pays fees; sent amount fixed - SETTLEMENT_FIX_RECEIVED = 2; // receiver gets fixed amount; source flexes -} - enum PaymentState { PAYMENT_STATE_UNSPECIFIED = 0; PAYMENT_STATE_ACCEPTED = 1; @@ -111,7 +105,7 @@ message PaymentIntent { FXIntent fx = 6; fees.v1.PolicyOverrides fee_policy = 7; map attributes = 8; - SettlementMode settlement_mode = 9; + common.payment.v1.SettlementMode settlement_mode = 9; Customer customer = 10; string settlement_currency = 11; string ref = 12; diff --git a/api/proto/payments/transfer/v1/transfer.proto b/api/proto/payments/transfer/v1/transfer.proto new file mode 100644 index 00000000..d22314ea --- /dev/null +++ b/api/proto/payments/transfer/v1/transfer.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +package payments.transfer.v1; + +option go_package = "github.com/tech/sendico/pkg/proto/payments/transfer/v1;transferv1"; + +import "api/proto/common/money/v1/money.proto"; +import "api/proto/common/storable/v1/storable.proto"; +import "api/proto/payments/endpoint/v1/endpoint.proto"; + + +// ------------------------- +// Base value movement +// ------------------------- +message TransferIntent { + payments.endpoint.v1.PaymentEndpoint source = 1; + payments.endpoint.v1.PaymentEndpoint destination = 2; + common.money.v1.Money amount = 3; + + string comment = 4; +} diff --git a/api/server/go.mod b/api/server/go.mod index 4a5ab6bd..2c0df783 100644 --- a/api/server/go.mod +++ b/api/server/go.mod @@ -37,7 +37,7 @@ require ( go.mongodb.org/mongo-driver/v2 v2.5.0 go.uber.org/zap v1.27.1 golang.org/x/net v0.50.0 - google.golang.org/grpc v1.79.0 + google.golang.org/grpc v1.79.1 google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 moul.io/chizap v1.0.3 diff --git a/api/server/go.sum b/api/server/go.sum index eb538f72..309e0e39 100644 --- a/api/server/go.sum +++ b/api/server/go.sum @@ -365,8 +365,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1: google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= -google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA= -google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= +google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/api/server/interface/api/sresponse/payment.go b/api/server/interface/api/sresponse/payment.go index 6c0ed8a6..31f36674 100644 --- a/api/server/interface/api/sresponse/payment.go +++ b/api/server/interface/api/sresponse/payment.go @@ -32,6 +32,7 @@ type FxQuote struct { BaseAmount *paymenttypes.Money `json:"baseAmount,omitempty"` QuoteAmount *paymenttypes.Money `json:"quoteAmount,omitempty"` ExpiresAtUnixMs int64 `json:"expiresAtUnixMs,omitempty"` + PricedAtUnixMs int64 `json:"pricedAtUnixMs,omitempty"` Provider string `json:"provider,omitempty"` RateRef string `json:"rateRef,omitempty"` Firm bool `json:"firm,omitempty"` @@ -163,6 +164,10 @@ func toFxQuote(q *oraclev1.Quote) *FxQuote { return nil } pair := q.GetPair() + pricedAtUnixMs := int64(0) + if ts := q.GetPricedAt(); ts != nil { + pricedAtUnixMs = ts.AsTime().UnixMilli() + } base := "" quote := "" if pair != nil { @@ -178,6 +183,7 @@ func toFxQuote(q *oraclev1.Quote) *FxQuote { BaseAmount: toMoney(q.GetBaseAmount()), QuoteAmount: toMoney(q.GetQuoteAmount()), ExpiresAtUnixMs: q.GetExpiresAtUnixMs(), + PricedAtUnixMs: pricedAtUnixMs, Provider: q.GetProvider(), RateRef: q.GetRateRef(), Firm: q.GetFirm(), diff --git a/api/server/internal/server/paymethodsimp/service.go b/api/server/internal/server/paymethodsimp/service.go index c7763ea4..1a4abd12 100644 --- a/api/server/internal/server/paymethodsimp/service.go +++ b/api/server/internal/server/paymethodsimp/service.go @@ -3,6 +3,7 @@ package paymethodsimp import ( "context" "encoding/json" + "fmt" "io" "net/http" "os" @@ -16,13 +17,21 @@ import ( "github.com/tech/sendico/pkg/mlogger" "github.com/tech/sendico/pkg/model" "github.com/tech/sendico/pkg/mservice" + archivablev1 "github.com/tech/sendico/pkg/proto/common/archivable/v1" + describablev1 "github.com/tech/sendico/pkg/proto/common/describable/v1" + oboundv1 "github.com/tech/sendico/pkg/proto/common/organization_bound/v1" paginationv2 "github.com/tech/sendico/pkg/proto/common/pagination/v2" + pboundv1 "github.com/tech/sendico/pkg/proto/common/permission_bound/v1" + storablev1 "github.com/tech/sendico/pkg/proto/common/storable/v1" + endpointv1 "github.com/tech/sendico/pkg/proto/payments/endpoint/v1" methodsv1 "github.com/tech/sendico/pkg/proto/payments/methods/v1" eapi "github.com/tech/sendico/server/interface/api" "github.com/tech/sendico/server/interface/api/sresponse" mutil "github.com/tech/sendico/server/internal/mutil/param" + "go.mongodb.org/mongo-driver/v2/bson" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/wrapperspb" ) @@ -98,17 +107,25 @@ func (a *PaymentMethodsAPI) create(r *http.Request, account *model.Account, toke if err != nil { return response.BadPayload(a.logger, a.Name(), err) } + pm, err := decodePaymentMethodJSON(payload) + if err != nil { + return response.BadPayload(a.logger, a.Name(), err) + } + method, err := encodePaymentMethodProto(pm) + if err != nil { + return response.Internal(a.logger, a.Name(), err) + } resp, err := a.client.CreatePaymentMethod(r.Context(), &methodsv1.CreatePaymentMethodRequest{ - AccountRef: account.ID.Hex(), - OrganizationRef: orgRef.Hex(), - PaymentMethodJson: payload, + AccountRef: account.ID.Hex(), + OrganizationRef: orgRef.Hex(), + PaymentMethod: method, }) if err != nil { return grpcErrorResponse(a.logger, a.Name(), err) } - pm, err := decodePaymentMethod(resp.GetPaymentMethodJson()) + pm, err = decodePaymentMethodRecord(resp.GetPaymentMethodRecord()) if err != nil { return response.Internal(a.logger, a.Name(), err) } @@ -140,7 +157,7 @@ func (a *PaymentMethodsAPI) list(r *http.Request, account *model.Account, token return grpcErrorResponse(a.logger, a.Name(), err) } - items, err := decodePaymentMethods(resp.GetPaymentMethodsJson()) + items, err := decodePaymentMethods(resp.GetPaymentMethods()) if err != nil { return response.Internal(a.logger, a.Name(), err) } @@ -161,7 +178,7 @@ func (a *PaymentMethodsAPI) get(r *http.Request, account *model.Account, token * return grpcErrorResponse(a.logger, a.Name(), err) } - pm, err := decodePaymentMethod(resp.GetPaymentMethodJson()) + pm, err := decodePaymentMethodRecord(resp.GetPaymentMethodRecord()) if err != nil { return response.Internal(a.logger, a.Name(), err) } @@ -173,16 +190,24 @@ func (a *PaymentMethodsAPI) update(r *http.Request, account *model.Account, toke if err != nil { return response.BadPayload(a.logger, a.Name(), err) } + pm, err := decodePaymentMethodJSON(payload) + if err != nil { + return response.BadPayload(a.logger, a.Name(), err) + } + record, err := encodePaymentMethodRecord(pm) + if err != nil { + return response.Internal(a.logger, a.Name(), err) + } resp, err := a.client.UpdatePaymentMethod(r.Context(), &methodsv1.UpdatePaymentMethodRequest{ - AccountRef: account.ID.Hex(), - PaymentMethodJson: payload, + AccountRef: account.ID.Hex(), + PaymentMethodRecord: record, }) if err != nil { return grpcErrorResponse(a.logger, a.Name(), err) } - pm, err := decodePaymentMethod(resp.GetPaymentMethodJson()) + pm, err = decodePaymentMethodRecord(resp.GetPaymentMethodRecord()) if err != nil { return response.Internal(a.logger, a.Name(), err) } @@ -300,7 +325,7 @@ func toProtoCursor(cursor *model.ViewCursor) *paginationv2.ViewCursor { return res } -func decodePaymentMethod(payload []byte) (*model.PaymentMethod, error) { +func decodePaymentMethodJSON(payload []byte) (*model.PaymentMethod, error) { var pm model.PaymentMethod if err := json.Unmarshal(payload, &pm); err != nil { return nil, err @@ -308,13 +333,78 @@ func decodePaymentMethod(payload []byte) (*model.PaymentMethod, error) { return &pm, nil } -func decodePaymentMethods(items [][]byte) ([]model.PaymentMethod, error) { +func decodePaymentMethodRecord(record *endpointv1.PaymentMethodRecord) (*model.PaymentMethod, error) { + if record == nil { + return nil, merrors.InvalidArgument("payment_method_record is required") + } + pm, err := decodePaymentMethodProto(record.GetPaymentMethod()) + if err != nil { + return nil, err + } + if err := applyPermissionBound(pm, record.GetPermissionBound()); err != nil { + return nil, err + } + return pm, nil +} + +func decodePaymentMethodProto(method *endpointv1.PaymentMethod) (*model.PaymentMethod, error) { + if method == nil { + return nil, merrors.InvalidArgument("payment_method is required") + } + + recipientRef, err := parseRequiredObjectID(method.GetRecipientRef(), "payment_method.recipient_ref") + if err != nil { + return nil, err + } + pt, err := paymentTypeFromProto(method.GetType(), "payment_method.type") + if err != nil { + return nil, err + } + + return &model.PaymentMethod{ + Describable: describableFromProto(method.GetDescribable()), + RecipientRef: recipientRef, + Type: pt, + Data: cloneBytes(method.GetData()), + IsMain: method.GetIsMain(), + }, nil +} + +func encodePaymentMethodProto(pm *model.PaymentMethod) (*endpointv1.PaymentMethod, error) { + if pm == nil { + return nil, merrors.InvalidArgument("payment method is required") + } + pt, err := paymentTypeToProto(pm.Type) + if err != nil { + return nil, err + } + return &endpointv1.PaymentMethod{ + Describable: describableToProto(pm.Describable), + RecipientRef: toObjectHex(pm.RecipientRef), + Type: pt, + Data: cloneBytes(pm.Data), + IsMain: pm.IsMain, + }, nil +} + +func encodePaymentMethodRecord(pm *model.PaymentMethod) (*endpointv1.PaymentMethodRecord, error) { + method, err := encodePaymentMethodProto(pm) + if err != nil { + return nil, err + } + return &endpointv1.PaymentMethodRecord{ + PermissionBound: permissionBoundFromModel(pm), + PaymentMethod: method, + }, nil +} + +func decodePaymentMethods(items []*endpointv1.PaymentMethodRecord) ([]model.PaymentMethod, error) { if len(items) == 0 { return nil, nil } res := make([]model.PaymentMethod, 0, len(items)) for i := range items { - pm, err := decodePaymentMethod(items[i]) + pm, err := decodePaymentMethodRecord(items[i]) if err != nil { return nil, err } @@ -323,6 +413,183 @@ func decodePaymentMethods(items [][]byte) ([]model.PaymentMethod, error) { return res, nil } +func paymentTypeFromProto(value endpointv1.PaymentMethodType, field string) (model.PaymentType, error) { + switch value { + case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_IBAN: + return model.PaymentTypeIban, nil + case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_CARD: + return model.PaymentTypeCard, nil + case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_CARD_TOKEN: + return model.PaymentTypeCardToken, nil + case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_BANK_ACCOUNT: + return model.PaymentTypeBankAccount, nil + case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_WALLET: + return model.PaymentTypeWallet, nil + case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_CRYPTO_ADDRESS: + return model.PaymentTypeCryptoAddress, nil + case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_LEDGER: + return model.PaymentTypeLedger, nil + default: + return model.PaymentTypeIban, merrors.InvalidArgument(fmt.Sprintf("%s has unsupported value: %s", field, value.String()), field) + } +} + +func paymentTypeToProto(value model.PaymentType) (endpointv1.PaymentMethodType, error) { + switch value { + case model.PaymentTypeIban: + return endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_IBAN, nil + case model.PaymentTypeCard: + return endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_CARD, nil + case model.PaymentTypeCardToken: + return endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_CARD_TOKEN, nil + case model.PaymentTypeBankAccount: + return endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_BANK_ACCOUNT, nil + case model.PaymentTypeWallet: + return endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_WALLET, nil + case model.PaymentTypeCryptoAddress: + return endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_CRYPTO_ADDRESS, nil + case model.PaymentTypeLedger: + return endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_LEDGER, nil + default: + return endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_UNSPECIFIED, merrors.InvalidArgument(fmt.Sprintf("unsupported payment method type: %s", value.String()), "type") + } +} + +func describableFromProto(src *describablev1.Describable) model.Describable { + if src == nil { + return model.Describable{} + } + res := model.Describable{Name: src.GetName()} + if src.Description != nil { + v := src.GetDescription() + res.Description = &v + } + return res +} + +func describableToProto(src model.Describable) *describablev1.Describable { + if strings.TrimSpace(src.Name) == "" && src.Description == nil { + return nil + } + res := &describablev1.Describable{ + Name: src.Name, + } + if src.Description != nil { + v := *src.Description + res.Description = &v + } + return res +} + +func cloneBytes(src []byte) []byte { + if len(src) == 0 { + return nil + } + dst := make([]byte, len(src)) + copy(dst, src) + return dst +} + +func permissionBoundFromModel(pm *model.PaymentMethod) *pboundv1.PermissionBound { + if pm == nil { + return nil + } + return &pboundv1.PermissionBound{ + Storable: &storablev1.Storable{ + Id: toObjectHex(pm.ID), + CreatedAt: toProtoTime(pm.CreatedAt), + UpdatedAt: toProtoTime(pm.UpdatedAt), + }, + Archivable: &archivablev1.Archivable{ + IsArchived: pm.Archived, + }, + OrganizationBound: &oboundv1.OrganizationBound{ + OrganizationRef: toObjectHex(pm.GetOrganizationRef()), + }, + PermissionRef: toObjectHex(pm.GetPermissionRef()), + } +} + +func applyPermissionBound(pm *model.PaymentMethod, src *pboundv1.PermissionBound) error { + if pm == nil || src == nil { + return nil + } + + if storable := src.GetStorable(); storable != nil { + if methodRef, err := parseOptionalObjectID(storable.GetId(), "payment_method_record.permission_bound.storable.id"); err != nil { + return err + } else if methodRef != bson.NilObjectID { + pm.ID = methodRef + } + pm.CreatedAt = fromProtoTime(storable.GetCreatedAt()) + pm.UpdatedAt = fromProtoTime(storable.GetUpdatedAt()) + } + + if archivable := src.GetArchivable(); archivable != nil { + pm.Archived = archivable.GetIsArchived() + } + + if orgBound := src.GetOrganizationBound(); orgBound != nil { + if orgRef, err := parseOptionalObjectID(orgBound.GetOrganizationRef(), "payment_method_record.permission_bound.organization_bound.organization_ref"); err != nil { + return err + } else if orgRef != bson.NilObjectID { + pm.SetOrganizationRef(orgRef) + } + } + + if permissionRef, err := parseOptionalObjectID(src.GetPermissionRef(), "payment_method_record.permission_bound.permission_ref"); err != nil { + return err + } else if permissionRef != bson.NilObjectID { + pm.SetPermissionRef(permissionRef) + } + + return nil +} + +func parseOptionalObjectID(value, field string) (bson.ObjectID, error) { + trimmed := strings.TrimSpace(value) + if trimmed == "" { + return bson.NilObjectID, nil + } + ref, err := bson.ObjectIDFromHex(trimmed) + if err != nil { + return bson.NilObjectID, merrors.InvalidArgument(fmt.Sprintf("%s must be a valid object id", field), field) + } + return ref, nil +} + +func parseRequiredObjectID(value, field string) (bson.ObjectID, error) { + ref, err := parseOptionalObjectID(value, field) + if err != nil { + return bson.NilObjectID, err + } + if ref == bson.NilObjectID { + return bson.NilObjectID, merrors.InvalidArgument(field+" is required", field) + } + return ref, nil +} + +func toObjectHex(value bson.ObjectID) string { + if value == bson.NilObjectID { + return "" + } + return value.Hex() +} + +func toProtoTime(value time.Time) *timestamppb.Timestamp { + if value.IsZero() { + return nil + } + return timestamppb.New(value) +} + +func fromProtoTime(value *timestamppb.Timestamp) time.Time { + if value == nil { + return time.Time{} + } + return value.AsTime() +} + func grpcErrorResponse(logger mlogger.Logger, source mservice.Type, err error) http.HandlerFunc { statusErr, ok := status.FromError(err) if !ok { diff --git a/ci/scripts/proto/generate.sh b/ci/scripts/proto/generate.sh index 3c7aa9b4..0228756a 100755 --- a/ci/scripts/proto/generate.sh +++ b/ci/scripts/proto/generate.sh @@ -36,7 +36,13 @@ PROTOC_ARGS="" if [ -n "${PROTOC_INCLUDE}" ]; then PROTOC_ARGS="-I=${PROTOC_INCLUDE}" fi -PROTOC_ARGS="${PROTOC_ARGS} -I=${PROTO_DIR}" +PROTOC_ARGS="${PROTOC_ARGS} -I=.. -I=${PROTO_DIR}" + +PROTOC_ARGS_MESSAGING="" +if [ -n "${PROTOC_INCLUDE}" ]; then + PROTOC_ARGS_MESSAGING="-I=${PROTOC_INCLUDE}" +fi +PROTOC_ARGS_MESSAGING="${PROTOC_ARGS_MESSAGING} -I=${PROTO_DIR}" generate_go() { # shellcheck disable=SC2086 @@ -47,6 +53,15 @@ generate_go() { "$@" } +generate_go_messaging() { + # shellcheck disable=SC2086 + "${PROTOC_BIN}" \ + ${PROTOC_ARGS_MESSAGING} \ + --go_out="${GO_OUT_DIR}" \ + --go_opt=module="${GO_MODULE}" \ + "$@" +} + generate_go_with_grpc() { # shellcheck disable=SC2086 "${PROTOC_BIN}" \ @@ -76,15 +91,15 @@ list_protos() { cd "${API_DIR}" -messaging_files="$(list_protos "${PROTO_DIR}" 1)" +messaging_files="$(find "${PROTO_DIR}" -maxdepth 1 -type f -name '*.proto' -exec basename {} \; | sort)" if [ -n "${messaging_files}" ]; then info "Compiling messaging protos" rm -rf ./pkg/generated/gmessaging ./pkg/messaging/internal/generated set -- $messaging_files - generate_go "$@" + generate_go_messaging "$@" fi -common_files="$(list_protos "${PROTO_DIR}/common")" +common_files="$(list_protos "${PROTO_DIR}/common" | sed 's#^\./##' | sed 's#^proto/#api/proto/#')" if [ -n "${common_files}" ]; then info "Compiling common shared protos" clean_pb_files "./pkg/proto" @@ -140,6 +155,12 @@ if [ -f "${PROTO_DIR}/payments/quotation/v1/quotation.proto" ]; then generate_go_with_grpc "${PROTO_DIR}/payments/quotation/v1/quotation.proto" fi +if [ -f "${PROTO_DIR}/payments/endpoint/v1/endpoint.proto" ]; then + info "Compiling payments endpoint protos" + clean_pb_files "./pkg/proto/payments/endpoint" + generate_go "${PROTO_DIR}/payments/endpoint/v1/endpoint.proto" +fi + if [ -f "${PROTO_DIR}/payments/methods/v1/methods.proto" ]; then info "Compiling payments methods protos" clean_pb_files "./pkg/proto/payments/methods"