package store import ( "context" "errors" "github.com/tech/sendico/payments/storage" "github.com/tech/sendico/pkg/auth" "github.com/tech/sendico/pkg/db/repository" ri "github.com/tech/sendico/pkg/db/repository/index" "github.com/tech/sendico/pkg/merrors" "github.com/tech/sendico/pkg/mlogger" pkgmodel "github.com/tech/sendico/pkg/model" "github.com/tech/sendico/pkg/mservice" mutil "github.com/tech/sendico/pkg/mutil/db" mauth "github.com/tech/sendico/pkg/mutil/db/auth" "go.mongodb.org/mongo-driver/v2/bson" "go.uber.org/zap" ) type PaymentMethods struct { logger mlogger.Logger repo repository.Repository enforcer auth.Enforcer permissionRef bson.ObjectID } // NewPaymentMethods constructs a Mongo-backed payment-methods store. func NewPaymentMethods(logger mlogger.Logger, repo repository.Repository, enforcer auth.Enforcer, permissionRef bson.ObjectID) (*PaymentMethods, error) { if repo == nil { return nil, merrors.InvalidArgument("paymentMethodsStore: repository is nil") } if enforcer == nil { return nil, merrors.InvalidArgument("paymentMethodsStore: enforcer is nil") } if permissionRef == bson.NilObjectID { return nil, merrors.InvalidArgument("paymentMethodsStore: permission reference is required") } indexes := []*ri.Definition{ { Keys: []ri.Key{ {Field: "organizationRef", Sort: ri.Asc}, {Field: "recipientRef", Sort: ri.Asc}, }, }, { Keys: []ri.Key{{Field: "recipientRef", Sort: ri.Asc}}, }, } for _, def := range indexes { if err := repo.CreateIndex(def); err != nil { logger.Error("Failed to ensure payment methods index", zap.Error(err), zap.String("collection", repo.Collection())) return nil, err } } return &PaymentMethods{ logger: logger.Named("payment_methods"), repo: repo, enforcer: enforcer, permissionRef: permissionRef, }, nil } func (p *PaymentMethods) Create(ctx context.Context, accountRef, organizationRef bson.ObjectID, method *pkgmodel.PaymentMethod) error { if method == nil { return merrors.InvalidArgument("paymentMethodsStore: nil payment method") } if accountRef == bson.NilObjectID { return merrors.InvalidArgument("paymentMethodsStore: account_ref is required") } if organizationRef == bson.NilObjectID { return merrors.InvalidArgument("paymentMethodsStore: organization_ref is required") } if method.RecipientRef == bson.NilObjectID { return merrors.InvalidArgument("paymentMethodsStore: recipient_ref is required") } if method.GetPermissionRef() == bson.NilObjectID { method.SetPermissionRef(p.permissionRef) } method.SetOrganizationRef(organizationRef) allowed, err := p.enforcer.Enforce(ctx, method.GetPermissionRef(), accountRef, organizationRef, bson.NilObjectID, pkgmodel.ActionCreate) if err != nil { return err } if !allowed { return merrors.AccessDenied(mservice.PaymentMethods, string(pkgmodel.ActionCreate), bson.NilObjectID) } return p.repo.Insert(ctx, method, nil) } func (p *PaymentMethods) Get(ctx context.Context, accountRef, methodRef bson.ObjectID) (*pkgmodel.PaymentMethod, error) { if accountRef == bson.NilObjectID { return nil, merrors.InvalidArgument("paymentMethodsStore: account_ref is required") } if methodRef == bson.NilObjectID { return nil, merrors.InvalidArgument("paymentMethodsStore: method_ref is required") } if err := p.enforceObject(ctx, accountRef, methodRef, pkgmodel.ActionRead); err != nil { return nil, err } method := &pkgmodel.PaymentMethod{} if err := p.repo.Get(ctx, methodRef, method); err != nil { return nil, err } return method, nil } func (p *PaymentMethods) GetPrivate(ctx context.Context, methodRef bson.ObjectID) (*pkgmodel.PaymentMethod, error) { if methodRef == bson.NilObjectID { return nil, merrors.InvalidArgument("paymentMethodsStore: method_ref is required") } method := &pkgmodel.PaymentMethod{} if err := p.repo.Get(ctx, methodRef, method); err != nil { return nil, err } return method, nil } func (p *PaymentMethods) Update(ctx context.Context, accountRef bson.ObjectID, method *pkgmodel.PaymentMethod) error { if method == nil { return merrors.InvalidArgument("paymentMethodsStore: nil payment method") } if accountRef == bson.NilObjectID { return merrors.InvalidArgument("paymentMethodsStore: account_ref is required") } if method.GetID() == nil || method.GetID().IsZero() { return merrors.InvalidArgument("paymentMethodsStore: method id is required") } if err := p.enforceObject(ctx, accountRef, *method.GetID(), pkgmodel.ActionUpdate); err != nil { return err } return p.repo.Update(ctx, method) } func (p *PaymentMethods) Delete(ctx context.Context, accountRef, methodRef bson.ObjectID) error { if accountRef == bson.NilObjectID { return merrors.InvalidArgument("paymentMethodsStore: account_ref is required") } if methodRef == bson.NilObjectID { return merrors.InvalidArgument("paymentMethodsStore: method_ref is required") } if err := p.enforceObject(ctx, accountRef, methodRef, pkgmodel.ActionDelete); err != nil { return err } return p.repo.Delete(ctx, methodRef) } func (p *PaymentMethods) DeleteCascade(ctx context.Context, accountRef, methodRef bson.ObjectID) error { return p.Delete(ctx, accountRef, methodRef) } func (p *PaymentMethods) SetArchived(ctx context.Context, accountRef, _ bson.ObjectID, methodRef bson.ObjectID, archived, _ bool) error { if accountRef == bson.NilObjectID { return merrors.InvalidArgument("paymentMethodsStore: account_ref is required") } if methodRef == bson.NilObjectID { return merrors.InvalidArgument("paymentMethodsStore: method_ref is required") } if err := p.enforceObject(ctx, accountRef, methodRef, pkgmodel.ActionUpdate); err != nil { return err } patch := repository.Patch().Set(repository.Field("isArchived"), archived) return p.repo.Patch(ctx, methodRef, patch) } func (p *PaymentMethods) List(ctx context.Context, accountRef, organizationRef, recipientRef bson.ObjectID, cursor *pkgmodel.ViewCursor) ([]pkgmodel.PaymentMethod, error) { if accountRef == bson.NilObjectID { return nil, merrors.InvalidArgument("paymentMethodsStore: account_ref is required") } if organizationRef == bson.NilObjectID { return nil, merrors.InvalidArgument("paymentMethodsStore: organization_ref is required") } if recipientRef == bson.NilObjectID { return nil, merrors.InvalidArgument("paymentMethodsStore: recipient_ref is required") } items, err := mauth.GetProtectedObjects[pkgmodel.PaymentMethod]( ctx, p.logger, accountRef, organizationRef, pkgmodel.ActionRead, repository.OrgFilter(organizationRef).And(repository.Filter("recipientRef", recipientRef)), cursor, p.enforcer, p.repo, ) if errors.Is(err, merrors.ErrNoData) { return []pkgmodel.PaymentMethod{}, nil } return items, err } func (p *PaymentMethods) ListPrivate(ctx context.Context, organizationRef, recipientRef bson.ObjectID, cursor *pkgmodel.ViewCursor) ([]pkgmodel.PaymentMethod, error) { if organizationRef == bson.NilObjectID { return nil, merrors.InvalidArgument("paymentMethodsStore: organization_ref is required") } if recipientRef == bson.NilObjectID { return nil, merrors.InvalidArgument("paymentMethodsStore: recipient_ref is required") } items, err := mutil.GetObjects[pkgmodel.PaymentMethod]( ctx, p.logger, repository.OrgFilter(organizationRef).And(repository.Filter("recipientRef", recipientRef)), cursor, p.repo, ) if errors.Is(err, merrors.ErrNoData) { return []pkgmodel.PaymentMethod{}, nil } return items, err } func (p *PaymentMethods) SetArchivedByRecipient(ctx context.Context, recipientRef bson.ObjectID, archived bool) (int, error) { if recipientRef == bson.NilObjectID { return 0, merrors.InvalidArgument("paymentMethodsStore: recipient_ref is required") } filter := repository.Filter("recipientRef", recipientRef) patch := repository.Patch().Set(repository.Field("isArchived"), archived) return p.repo.PatchMany(ctx, filter, patch) } func (p *PaymentMethods) DeleteByRecipient(ctx context.Context, recipientRef bson.ObjectID) error { if recipientRef == bson.NilObjectID { return merrors.InvalidArgument("paymentMethodsStore: recipient_ref is required") } return p.repo.DeleteMany(ctx, repository.Filter("recipientRef", recipientRef)) } func (p *PaymentMethods) enforceObject(ctx context.Context, accountRef, methodRef bson.ObjectID, action pkgmodel.Action) error { refs, err := p.repo.ListPermissionBound(ctx, repository.IDFilter(methodRef)) if err != nil { if errors.Is(err, merrors.ErrNoData) { return merrors.AccessDenied(mservice.PaymentMethods, string(action), methodRef) } return err } if len(refs) == 0 { return merrors.AccessDenied(mservice.PaymentMethods, string(action), methodRef) } allowed, err := p.enforcer.Enforce(ctx, refs[0].GetPermissionRef(), accountRef, refs[0].GetOrganizationRef(), methodRef, action) if err != nil { return err } if !allowed { return merrors.AccessDenied(mservice.PaymentMethods, string(action), methodRef) } return nil } var _ storage.PaymentMethodsStore = (*PaymentMethods)(nil)