Linting #509

Merged
tech merged 46 commits from main into dis-474 2026-02-13 16:14:15 +00:00
85 changed files with 1180 additions and 162 deletions
Showing only changes of commit e6232f9b1d - Show all commits

View File

@@ -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
)

View File

@@ -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=

View File

@@ -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
)

View File

@@ -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=

View File

@@ -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
)

View File

@@ -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=

View File

@@ -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
)

View File

@@ -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=

View File

@@ -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(),

View File

@@ -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)
}
}

View File

@@ -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
)

View File

@@ -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=

View File

@@ -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(),

View File

@@ -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) {

View File

@@ -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
}

View File

@@ -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"`

View File

@@ -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 = ""

View File

@@ -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) {

View File

@@ -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

View File

@@ -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=

View File

@@ -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
)

View File

@@ -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=

View File

@@ -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
)

View File

@@ -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=

View File

@@ -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

View File

@@ -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=

View File

@@ -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
)

View File

@@ -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=

View File

@@ -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
)

View File

@@ -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=

View File

@@ -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
)

View File

@@ -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=

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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
)

View File

@@ -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=

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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,

View File

@@ -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
)

View File

@@ -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=

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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
)

View File

@@ -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=

View File

@@ -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 ""

View File

@@ -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<string,string> 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<string,string> parameters = 7; // thresholds, tiers, etc.
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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; // 112
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
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 =====

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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<string, string> attributes = 8;
SettlementMode settlement_mode = 9;
common.payment.v1.SettlementMode settlement_mode = 9;
Customer customer = 10;
string settlement_currency = 11;
string ref = 12;

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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=

View File

@@ -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(),

View File

@@ -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 {

View File

@@ -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"