package mongo import ( "context" "time" "github.com/tech/sendico/payments/storage" "github.com/tech/sendico/payments/storage/model" "github.com/tech/sendico/payments/storage/mongo/store" quotestorage "github.com/tech/sendico/payments/storage/quote" quotemongo "github.com/tech/sendico/payments/storage/quote/mongo" "github.com/tech/sendico/pkg/auth" "github.com/tech/sendico/pkg/db" "github.com/tech/sendico/pkg/db/repository" "github.com/tech/sendico/pkg/merrors" "github.com/tech/sendico/pkg/mlogger" "github.com/tech/sendico/pkg/mservice" "go.mongodb.org/mongo-driver/v2/bson" ) // Store implements storage.Repository backed by MongoDB. type Store struct { logger mlogger.Logger ping func(context.Context) error payments storage.PaymentsStore methods storage.PaymentMethodsStore quotes quotestorage.QuotesStore routes storage.RoutesStore plans storage.PlanTemplatesStore } type paymentMethodsConfig struct { enforcer auth.Enforcer permissionRef bson.ObjectID } type options struct { quoteRetention time.Duration paymentMethodsAuth *paymentMethodsConfig } // 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 } } // WithPaymentMethodsAuth enables the payment-methods store and permission checks. func WithPaymentMethodsAuth(enforcer auth.Enforcer, permissionRef bson.ObjectID) Option { return func(opts *options) { opts.paymentMethodsAuth = &paymentMethodsConfig{ enforcer: enforcer, permissionRef: permissionRef, } } } // New constructs a Mongo-backed payments repository from a Mongo connection. 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") } paymentsRepo := repository.CreateMongoRepository(conn.Database(), (&model.Payment{}).Collection()) quotesRepo := repository.CreateMongoRepository(conn.Database(), (&model.PaymentQuoteRecord{}).Collection()) routesRepo := repository.CreateMongoRepository(conn.Database(), (&model.PaymentRoute{}).Collection()) plansRepo := repository.CreateMongoRepository(conn.Database(), (&model.PaymentPlanTemplate{}).Collection()) methodsRepo := repository.CreateMongoRepository(conn.Database(), mservice.PaymentMethods) return newWithRepository(logger, conn.Ping, paymentsRepo, methodsRepo, 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, opts ...Option) (*Store, error) { return newWithRepository(logger, ping, paymentsRepo, nil, quotesRepo, routesRepo, plansRepo, opts...) } func newWithRepository( logger mlogger.Logger, ping func(context.Context) error, paymentsRepo, methodsRepo, quotesRepo, routesRepo, plansRepo repository.Repository, opts ...Option, ) (*Store, error) { if ping == nil { return nil, merrors.InvalidArgument("payments.storage.mongo: ping func is nil") } if paymentsRepo == nil { return nil, merrors.InvalidArgument("payments.storage.mongo: payments repository is nil") } if quotesRepo == nil { return nil, merrors.InvalidArgument("payments.storage.mongo: quotes repository is nil") } if routesRepo == nil { return nil, merrors.InvalidArgument("payments.storage.mongo: routes repository is nil") } if plansRepo == nil { 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 } quotesRepoStore, err := quotemongo.NewWithRepository(childLogger, ping, quotesRepo, quotemongo.WithQuoteRetention(cfg.quoteRetention)) if err != nil { return nil, err } routesStore, err := store.NewRoutes(childLogger, routesRepo) if err != nil { return nil, err } plansStore, err := store.NewPlanTemplates(childLogger, plansRepo) if err != nil { return nil, err } var methodsStore storage.PaymentMethodsStore if cfg.paymentMethodsAuth != nil { if methodsRepo == nil { return nil, merrors.InvalidArgument("payments.storage.mongo: payment methods repository is nil") } if cfg.paymentMethodsAuth.enforcer == nil { return nil, merrors.InvalidArgument("payments.storage.mongo: payment methods enforcer is nil") } if cfg.paymentMethodsAuth.permissionRef == bson.NilObjectID { return nil, merrors.InvalidArgument("payments.storage.mongo: payment methods permission reference is required") } methodsStore, err = store.NewPaymentMethods(childLogger, methodsRepo, cfg.paymentMethodsAuth.enforcer, cfg.paymentMethodsAuth.permissionRef) if err != nil { return nil, err } } result := &Store{ logger: childLogger, ping: ping, payments: paymentsStore, methods: methodsStore, quotes: quotesRepoStore.Quotes(), routes: routesStore, plans: plansStore, } return result, nil } // Ping verifies connectivity with the backing database. func (s *Store) Ping(ctx context.Context) error { if s.ping == nil { return merrors.InvalidArgument("payments.storage.mongo: ping func is nil") } return s.ping(ctx) } // Payments returns the payments store. func (s *Store) Payments() storage.PaymentsStore { return s.payments } // PaymentMethods returns the payment-methods store. func (s *Store) PaymentMethods() storage.PaymentMethodsStore { return s.methods } // Quotes returns the quotes store. func (s *Store) Quotes() quotestorage.QuotesStore { return s.quotes } // Routes returns the routing store. func (s *Store) Routes() storage.RoutesStore { return s.routes } // PlanTemplates returns the plan templates store. func (s *Store) PlanTemplates() storage.PlanTemplatesStore { return s.plans } var _ storage.Repository = (*Store)(nil)