neq quotation definition + priced_at field
This commit is contained in:
@@ -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
|
||||
)
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user