package store import ( "context" "errors" "strings" "time" "github.com/tech/sendico/gateway/tgsettle/storage" "github.com/tech/sendico/gateway/tgsettle/storage/model" "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" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.uber.org/zap" ) const ( paymentsCollection = "payments" fieldIdempotencyKey = "idempotencyKey" ) type Payments struct { logger mlogger.Logger coll *mongo.Collection } func NewPayments(logger mlogger.Logger, db *mongo.Database) (*Payments, error) { if db == nil { return nil, merrors.InvalidArgument("mongo database is nil") } if logger == nil { logger = zap.NewNop() } logger = logger.Named("payments").With(zap.String("collection", paymentsCollection)) repo := repository.CreateMongoRepository(db, paymentsCollection) if err := repo.CreateIndex(&ri.Definition{ Keys: []ri.Key{{Field: fieldIdempotencyKey, Sort: ri.Asc}}, Unique: true, }); err != nil { logger.Error("Failed to create payments idempotency index", zap.Error(err), zap.String("index_field", fieldIdempotencyKey)) return nil, err } p := &Payments{ logger: logger, coll: db.Collection(paymentsCollection), } p.logger.Debug("Payments store initialised") return p, nil } func (p *Payments) FindByIdempotencyKey(ctx context.Context, key string) (*model.PaymentExecution, error) { key = strings.TrimSpace(key) if key == "" { return nil, merrors.InvalidArgument("idempotency key is required", "idempotency_key") } var result model.PaymentExecution err := p.coll.FindOne(ctx, bson.M{fieldIdempotencyKey: key}).Decode(&result) if err == mongo.ErrNoDocuments { return nil, nil } if err != nil { if !errors.Is(err, context.Canceled) && !errors.Is(err, context.DeadlineExceeded) { p.logger.Warn("Payment execution lookup failed", zap.String("idempotency_key", key), zap.Error(err)) } return nil, err } return &result, nil } func (p *Payments) InsertExecution(ctx context.Context, exec *model.PaymentExecution) error { if exec == nil { return merrors.InvalidArgument("payment execution is nil", "execution") } exec.IdempotencyKey = strings.TrimSpace(exec.IdempotencyKey) exec.PaymentIntentID = strings.TrimSpace(exec.PaymentIntentID) exec.QuoteRef = strings.TrimSpace(exec.QuoteRef) if exec.ExecutedAt.IsZero() { exec.ExecutedAt = time.Now() } if _, err := p.coll.InsertOne(ctx, exec); err != nil { if mongo.IsDuplicateKeyError(err) { return storage.ErrDuplicate } if !errors.Is(err, context.Canceled) && !errors.Is(err, context.DeadlineExceeded) { p.logger.Warn("Failed to insert payment execution", zap.String("idempotency_key", exec.IdempotencyKey), zap.String("payment_intent_id", exec.PaymentIntentID), zap.String("quote_ref", exec.QuoteRef), zap.Error(err)) } return err } return nil } var _ storage.PaymentsStore = (*Payments)(nil)