improved ledger account discovery
This commit is contained in:
@@ -19,6 +19,7 @@ type PaymentQuoteRecord struct {
|
||||
Quote *PaymentQuoteSnapshot `bson:"quote,omitempty" json:"quote,omitempty"`
|
||||
Quotes []*PaymentQuoteSnapshot `bson:"quotes,omitempty" json:"quotes,omitempty"`
|
||||
ExpiresAt time.Time `bson:"expiresAt" json:"expiresAt"`
|
||||
PurgeAt time.Time `bson:"purgeAt,omitempty" json:"purgeAt,omitempty"`
|
||||
Hash string `bson:"hash" json:"hash"`
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package mongo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/tech/sendico/payments/orchestrator/storage"
|
||||
"github.com/tech/sendico/payments/orchestrator/storage/model"
|
||||
@@ -23,8 +24,22 @@ type Store struct {
|
||||
plans storage.PlanTemplatesStore
|
||||
}
|
||||
|
||||
type options struct {
|
||||
quoteRetention time.Duration
|
||||
}
|
||||
|
||||
// Option configures the Mongo-backed payments repository.
|
||||
type Option func(*options)
|
||||
|
||||
// WithQuoteRetention sets how long payment quote records are retained after expiry.
|
||||
func WithQuoteRetention(retention time.Duration) Option {
|
||||
return func(opts *options) {
|
||||
opts.quoteRetention = retention
|
||||
}
|
||||
}
|
||||
|
||||
// New constructs a Mongo-backed payments repository from a Mongo connection.
|
||||
func New(logger mlogger.Logger, conn *db.MongoConnection) (*Store, error) {
|
||||
func New(logger mlogger.Logger, conn *db.MongoConnection, opts ...Option) (*Store, error) {
|
||||
if conn == nil {
|
||||
return nil, merrors.InvalidArgument("payments.storage.mongo: connection is nil")
|
||||
}
|
||||
@@ -32,11 +47,11 @@ func New(logger mlogger.Logger, conn *db.MongoConnection) (*Store, error) {
|
||||
quotesRepo := repository.CreateMongoRepository(conn.Database(), (&model.PaymentQuoteRecord{}).Collection())
|
||||
routesRepo := repository.CreateMongoRepository(conn.Database(), (&model.PaymentRoute{}).Collection())
|
||||
plansRepo := repository.CreateMongoRepository(conn.Database(), (&model.PaymentPlanTemplate{}).Collection())
|
||||
return NewWithRepository(logger, conn.Ping, paymentsRepo, quotesRepo, routesRepo, plansRepo)
|
||||
return NewWithRepository(logger, conn.Ping, paymentsRepo, quotesRepo, routesRepo, plansRepo, opts...)
|
||||
}
|
||||
|
||||
// NewWithRepository constructs a payments repository using the provided primitives.
|
||||
func NewWithRepository(logger mlogger.Logger, ping func(context.Context) error, paymentsRepo repository.Repository, quotesRepo repository.Repository, routesRepo repository.Repository, plansRepo repository.Repository) (*Store, error) {
|
||||
func NewWithRepository(logger mlogger.Logger, ping func(context.Context) error, paymentsRepo repository.Repository, quotesRepo repository.Repository, routesRepo repository.Repository, plansRepo repository.Repository, opts ...Option) (*Store, error) {
|
||||
if ping == nil {
|
||||
return nil, merrors.InvalidArgument("payments.storage.mongo: ping func is nil")
|
||||
}
|
||||
@@ -53,12 +68,19 @@ func NewWithRepository(logger mlogger.Logger, ping func(context.Context) error,
|
||||
return nil, merrors.InvalidArgument("payments.storage.mongo: plan templates repository is nil")
|
||||
}
|
||||
|
||||
cfg := options{}
|
||||
for _, opt := range opts {
|
||||
if opt != nil {
|
||||
opt(&cfg)
|
||||
}
|
||||
}
|
||||
|
||||
childLogger := logger.Named("storage").Named("mongo")
|
||||
paymentsStore, err := store.NewPayments(childLogger, paymentsRepo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
quotesStore, err := store.NewQuotes(childLogger, quotesRepo)
|
||||
quotesStore, err := store.NewQuotes(childLogger, quotesRepo, cfg.quoteRetention)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/tech/sendico/payments/orchestrator/storage"
|
||||
"github.com/tech/sendico/payments/orchestrator/storage/model"
|
||||
"github.com/tech/sendico/pkg/db/repository"
|
||||
"github.com/tech/sendico/pkg/db/repository/builder"
|
||||
ri "github.com/tech/sendico/pkg/db/repository/index"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
@@ -17,27 +18,45 @@ import (
|
||||
)
|
||||
|
||||
type Quotes struct {
|
||||
logger mlogger.Logger
|
||||
repo repository.Repository
|
||||
logger mlogger.Logger
|
||||
repo repository.Repository
|
||||
retention time.Duration
|
||||
}
|
||||
|
||||
const defaultPaymentQuoteRetention = 72 * time.Hour
|
||||
|
||||
// NewQuotes constructs a Mongo-backed quotes store.
|
||||
func NewQuotes(logger mlogger.Logger, repo repository.Repository) (*Quotes, error) {
|
||||
func NewQuotes(logger mlogger.Logger, repo repository.Repository, retention time.Duration) (*Quotes, error) {
|
||||
if repo == nil {
|
||||
return nil, merrors.InvalidArgument("quotesStore: repository is nil")
|
||||
}
|
||||
if retention <= 0 {
|
||||
logger.Info("Using default retention duration", zap.Duration("default_retention", defaultPaymentQuoteRetention))
|
||||
retention = defaultPaymentQuoteRetention
|
||||
}
|
||||
logger.Info("Using retention duration", zap.Duration("retention", retention))
|
||||
|
||||
indexes := []*ri.Definition{
|
||||
{
|
||||
Keys: []ri.Key{{Field: "quoteRef", Sort: ri.Asc}},
|
||||
Unique: true,
|
||||
},
|
||||
{
|
||||
Keys: []ri.Key{
|
||||
{Field: "organizationRef", Sort: ri.Asc},
|
||||
{Field: "idempotencyKey", Sort: ri.Asc},
|
||||
},
|
||||
Unique: true,
|
||||
Name: "payment_quotes_org_idempotency_key",
|
||||
PartialFilter: repository.Query().Comparison(repository.Field("idempotencyKey"), builder.Ne, ""),
|
||||
},
|
||||
{
|
||||
Keys: []ri.Key{{Field: "organizationRef", Sort: ri.Asc}},
|
||||
},
|
||||
{
|
||||
Keys: []ri.Key{{Field: "expiresAt", Sort: ri.Asc}},
|
||||
Keys: []ri.Key{{Field: "purgeAt", Sort: ri.Asc}},
|
||||
TTL: int32Ptr(0),
|
||||
Name: "payment_quotes_purge_at_ttl",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -49,8 +68,9 @@ func NewQuotes(logger mlogger.Logger, repo repository.Repository) (*Quotes, erro
|
||||
}
|
||||
|
||||
return &Quotes{
|
||||
logger: logger.Named("quotes"),
|
||||
repo: repo,
|
||||
logger: logger.Named("quotes"),
|
||||
repo: repo,
|
||||
retention: retention,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -65,12 +85,16 @@ func (q *Quotes) Create(ctx context.Context, quote *model.PaymentQuoteRecord) er
|
||||
if quote.OrganizationRef == primitive.NilObjectID {
|
||||
return merrors.InvalidArgument("quotesStore: organization_ref is required")
|
||||
}
|
||||
quote.IdempotencyKey = strings.TrimSpace(quote.IdempotencyKey)
|
||||
if quote.IdempotencyKey == "" {
|
||||
return merrors.InvalidArgument("quotesStore: idempotency key is required")
|
||||
}
|
||||
if quote.ExpiresAt.IsZero() {
|
||||
return merrors.InvalidArgument("quotesStore: expires_at is required")
|
||||
}
|
||||
if quote.PurgeAt.IsZero() || quote.PurgeAt.Before(quote.ExpiresAt) {
|
||||
quote.PurgeAt = quote.ExpiresAt.Add(q.retention)
|
||||
}
|
||||
if quote.Intent.Attributes != nil {
|
||||
for k, v := range quote.Intent.Attributes {
|
||||
quote.Intent.Attributes[k] = strings.TrimSpace(v)
|
||||
@@ -123,13 +147,16 @@ func (q *Quotes) GetByRef(ctx context.Context, orgRef primitive.ObjectID, quoteR
|
||||
return entity, nil
|
||||
}
|
||||
|
||||
func (q *Quotes) GetByIdempotencyKey(ctx context.Context, idempotencyKey string) (*model.PaymentQuoteRecord, error) {
|
||||
func (q *Quotes) GetByIdempotencyKey(ctx context.Context, orgRef primitive.ObjectID, idempotencyKey string) (*model.PaymentQuoteRecord, error) {
|
||||
idempotencyKey = strings.TrimSpace(idempotencyKey)
|
||||
if idempotencyKey == "" {
|
||||
return nil, merrors.InvalidArgument("quotesStore: empty idempotency key")
|
||||
}
|
||||
if orgRef == primitive.NilObjectID {
|
||||
return nil, merrors.InvalidArgument("quotesStore: organization_ref is required")
|
||||
}
|
||||
entity := &model.PaymentQuoteRecord{}
|
||||
query := repository.Filter("idempotencyKey", idempotencyKey)
|
||||
query := repository.OrgFilter(orgRef).And(repository.Filter("idempotencyKey", idempotencyKey))
|
||||
if err := q.repo.FindOneByFilter(ctx, query, entity); err != nil {
|
||||
if errors.Is(err, merrors.ErrNoData) {
|
||||
return nil, storage.ErrQuoteNotFound
|
||||
|
||||
@@ -55,7 +55,7 @@ type PaymentsStore interface {
|
||||
type QuotesStore interface {
|
||||
Create(ctx context.Context, quote *model.PaymentQuoteRecord) error
|
||||
GetByRef(ctx context.Context, orgRef primitive.ObjectID, quoteRef string) (*model.PaymentQuoteRecord, error)
|
||||
GetByIdempotencyKey(ctx context.Context, idempotencyKey string) (*model.PaymentQuoteRecord, error)
|
||||
GetByIdempotencyKey(ctx context.Context, orgRef primitive.ObjectID, idempotencyKey string) (*model.PaymentQuoteRecord, error)
|
||||
}
|
||||
|
||||
// RoutesStore manages allowed routing transitions.
|
||||
|
||||
Reference in New Issue
Block a user