TG settlement service

This commit is contained in:
Stephan D
2026-01-02 14:54:18 +01:00
parent ea1c69f14a
commit 743f683d92
82 changed files with 4693 additions and 503 deletions

View File

@@ -0,0 +1,68 @@
package mongo
import (
"context"
"time"
"github.com/tech/sendico/gateway/tgsettle/storage"
"github.com/tech/sendico/gateway/tgsettle/storage/mongo/store"
"github.com/tech/sendico/pkg/db"
"github.com/tech/sendico/pkg/merrors"
"github.com/tech/sendico/pkg/mlogger"
"go.mongodb.org/mongo-driver/mongo"
"go.uber.org/zap"
)
type Repository struct {
logger mlogger.Logger
conn *db.MongoConnection
db *mongo.Database
payments storage.PaymentsStore
tg storage.TelegramConfirmationsStore
}
func New(logger mlogger.Logger, conn *db.MongoConnection) (*Repository, error) {
if conn == nil {
return nil, merrors.InvalidArgument("mongo connection is nil")
}
client := conn.Client()
if client == nil {
return nil, merrors.Internal("mongo client is not initialised")
}
result := &Repository{
logger: logger.Named("storage").Named("mongo"),
conn: conn,
db: conn.Database(),
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := result.conn.Ping(ctx); err != nil {
result.logger.Error("Mongo ping failed during repository initialisation", zap.Error(err))
return nil, err
}
paymentsStore, err := store.NewPayments(result.logger, result.db)
if err != nil {
result.logger.Error("Failed to initialise payments store", zap.Error(err))
return nil, err
}
tgStore, err := store.NewTelegramConfirmations(result.logger, result.db)
if err != nil {
result.logger.Error("Failed to initialise telegram confirmations store", zap.Error(err))
return nil, err
}
result.payments = paymentsStore
result.tg = tgStore
result.logger.Info("Payment gateway MongoDB storage initialised")
return result, nil
}
func (r *Repository) Payments() storage.PaymentsStore {
return r.payments
}
func (r *Repository) TelegramConfirmations() storage.TelegramConfirmationsStore {
return r.tg
}
var _ storage.Repository = (*Repository)(nil)

View File

@@ -0,0 +1,82 @@
package store
import (
"context"
"strings"
"time"
"github.com/tech/sendico/gateway/tgsettle/storage"
"github.com/tech/sendico/gateway/tgsettle/storage/model"
"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.mongodb.org/mongo-driver/mongo/options"
"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")
}
p := &Payments{
logger: logger.Named("payments"),
coll: db.Collection(paymentsCollection),
}
_, err := p.coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
Keys: bson.D{{Key: fieldIdempotencyKey, Value: 1}},
Options: options.Index().SetUnique(true),
})
if err != nil {
p.logger.Error("Failed to create payments idempotency index", zap.Error(err))
return nil, err
}
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 {
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
}
return err
}
return nil
}
var _ storage.PaymentsStore = (*Payments)(nil)

View File

@@ -0,0 +1,67 @@
package store
import (
"context"
"strings"
"time"
"github.com/tech/sendico/gateway/tgsettle/storage"
"github.com/tech/sendico/gateway/tgsettle/storage/model"
"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.mongodb.org/mongo-driver/mongo/options"
"go.uber.org/zap"
)
const (
telegramCollection = "telegram_confirmations"
fieldRequestID = "requestId"
)
type TelegramConfirmations struct {
logger mlogger.Logger
coll *mongo.Collection
}
func NewTelegramConfirmations(logger mlogger.Logger, db *mongo.Database) (*TelegramConfirmations, error) {
if db == nil {
return nil, merrors.InvalidArgument("mongo database is nil")
}
t := &TelegramConfirmations{
logger: logger.Named("telegram_confirmations"),
coll: db.Collection(telegramCollection),
}
_, err := t.coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
Keys: bson.D{{Key: fieldRequestID, Value: 1}},
Options: options.Index().SetUnique(true),
})
if err != nil {
t.logger.Error("Failed to create telegram confirmations request_id index", zap.Error(err))
return nil, err
}
return t, nil
}
func (t *TelegramConfirmations) Upsert(ctx context.Context, record *model.TelegramConfirmation) error {
if record == nil {
return merrors.InvalidArgument("telegram confirmation is nil", "record")
}
record.RequestID = strings.TrimSpace(record.RequestID)
record.PaymentIntentID = strings.TrimSpace(record.PaymentIntentID)
record.QuoteRef = strings.TrimSpace(record.QuoteRef)
if record.RequestID == "" {
return merrors.InvalidArgument("request_id is required", "request_id")
}
if record.ReceivedAt.IsZero() {
record.ReceivedAt = time.Now()
}
update := bson.M{
"$set": record,
}
_, err := t.coll.UpdateOne(ctx, bson.M{fieldRequestID: record.RequestID}, update, options.Update().SetUpsert(true))
return err
}
var _ storage.TelegramConfirmationsStore = (*TelegramConfirmations)(nil)