Files
sendico/api/payments/storage/mongo/store/payment_methods.go
2026-02-12 21:10:33 +01:00

236 lines
7.8 KiB
Go

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