outbox for gateways
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
package gateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
gatewayoutbox "github.com/tech/sendico/gateway/common/outbox"
|
||||
"github.com/tech/sendico/pkg/db/transaction"
|
||||
me "github.com/tech/sendico/pkg/messaging/envelope"
|
||||
)
|
||||
|
||||
type tgOutboxProvider interface {
|
||||
Outbox() gatewayoutbox.Store
|
||||
}
|
||||
|
||||
type tgTransactionProvider interface {
|
||||
TransactionFactory() transaction.Factory
|
||||
}
|
||||
|
||||
func (s *Service) outboxStore() gatewayoutbox.Store {
|
||||
provider, ok := s.repo.(tgOutboxProvider)
|
||||
if !ok || provider == nil {
|
||||
return nil
|
||||
}
|
||||
return provider.Outbox()
|
||||
}
|
||||
|
||||
func (s *Service) startOutboxReliableProducer() error {
|
||||
if s == nil || s.repo == nil {
|
||||
return nil
|
||||
}
|
||||
return s.outbox.Start(s.logger, s.producer, s.outboxStore(), s.msgCfg)
|
||||
}
|
||||
|
||||
func (s *Service) sendWithOutbox(ctx context.Context, env me.Envelope) error {
|
||||
if err := s.startOutboxReliableProducer(); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.outbox.Send(ctx, env)
|
||||
}
|
||||
|
||||
func (s *Service) executeTransaction(ctx context.Context, cb transaction.Callback) (any, error) {
|
||||
provider, ok := s.repo.(tgTransactionProvider)
|
||||
if !ok || provider == nil || provider.TransactionFactory() == nil {
|
||||
return cb(ctx)
|
||||
}
|
||||
return provider.TransactionFactory().CreateTransaction().Execute(ctx, cb)
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
gatewayoutbox "github.com/tech/sendico/gateway/common/outbox"
|
||||
"github.com/tech/sendico/gateway/tgsettle/storage"
|
||||
storagemodel "github.com/tech/sendico/gateway/tgsettle/storage/model"
|
||||
"github.com/tech/sendico/pkg/api/routers"
|
||||
@@ -20,6 +21,7 @@ import (
|
||||
tnotifications "github.com/tech/sendico/pkg/messaging/notifications/telegram"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
pmodel "github.com/tech/sendico/pkg/model"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
paymenttypes "github.com/tech/sendico/pkg/payments/types"
|
||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||
@@ -48,12 +50,13 @@ const (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Rail string
|
||||
TargetChatIDEnv string
|
||||
TimeoutSeconds int32
|
||||
AcceptedUserIDs []string
|
||||
SuccessReaction string
|
||||
InvokeURI string
|
||||
Rail string
|
||||
TargetChatIDEnv string
|
||||
TimeoutSeconds int32
|
||||
AcceptedUserIDs []string
|
||||
SuccessReaction string
|
||||
InvokeURI string
|
||||
MessagingSettings pmodel.SettingsT
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
@@ -62,11 +65,13 @@ type Service struct {
|
||||
producer msg.Producer
|
||||
broker mb.Broker
|
||||
cfg Config
|
||||
msgCfg pmodel.SettingsT
|
||||
rail string
|
||||
chatID string
|
||||
announcer *discovery.Announcer
|
||||
invokeURI string
|
||||
successReaction string
|
||||
outbox gatewayoutbox.ReliableRuntime
|
||||
|
||||
consumers []msg.Consumer
|
||||
|
||||
@@ -84,6 +89,7 @@ func NewService(logger mlogger.Logger, repo storage.Repository, producer msg.Pro
|
||||
producer: producer,
|
||||
broker: broker,
|
||||
cfg: cfg,
|
||||
msgCfg: cfg.MessagingSettings,
|
||||
rail: strings.TrimSpace(cfg.Rail),
|
||||
invokeURI: strings.TrimSpace(cfg.InvokeURI),
|
||||
}
|
||||
@@ -92,6 +98,9 @@ func NewService(logger mlogger.Logger, repo storage.Repository, producer msg.Pro
|
||||
if svc.successReaction == "" {
|
||||
svc.successReaction = defaultTelegramSuccessReaction
|
||||
}
|
||||
if err := svc.startOutboxReliableProducer(); err != nil {
|
||||
svc.logger.Warn("Failed to initialise outbox reliable producer", zap.Error(err))
|
||||
}
|
||||
svc.startConsumers()
|
||||
svc.startAnnouncer()
|
||||
return svc
|
||||
@@ -107,6 +116,7 @@ func (s *Service) Shutdown() {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
s.outbox.Stop()
|
||||
if s.announcer != nil {
|
||||
s.announcer.Stop()
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/tech/sendico/gateway/tgsettle/storage/model"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
paymentgateway "github.com/tech/sendico/pkg/messaging/notifications/paymentgateway"
|
||||
pmodel "github.com/tech/sendico/pkg/model"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
@@ -21,33 +22,57 @@ func isFinalStatus(t *model.PaymentRecord) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func toOpStatus(t *model.PaymentRecord) rail.OperationResult {
|
||||
func toOpStatus(t *model.PaymentRecord) (rail.OperationResult, error) {
|
||||
switch t.Status {
|
||||
case model.PaymentStatusFailed:
|
||||
return rail.OperationResultFailed
|
||||
return rail.OperationResultFailed, nil
|
||||
case model.PaymentStatusSuccess:
|
||||
return rail.OperationResultSuccess
|
||||
return rail.OperationResultSuccess, nil
|
||||
case model.PaymentStatusCancelled:
|
||||
return rail.OperationResultCancelled
|
||||
return rail.OperationResultCancelled, nil
|
||||
default:
|
||||
panic("unexpected transfer status")
|
||||
return rail.OperationResultFailed, merrors.InvalidArgument("unexpected transfer status", "payment.status")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) updateTransferStatus(ctx context.Context, record *model.PaymentRecord) error {
|
||||
if err := s.repo.Payments().Upsert(ctx, record); err != nil {
|
||||
if !isFinalStatus(record) {
|
||||
if err := s.repo.Payments().Upsert(ctx, record); err != nil {
|
||||
s.logger.Warn("Failed to update transfer status", zap.String("payment_ref", record.PaymentIntentID), zap.String("status", string(record.Status)), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := s.executeTransaction(ctx, func(txCtx context.Context) (any, error) {
|
||||
if upsertErr := s.repo.Payments().Upsert(txCtx, record); upsertErr != nil {
|
||||
return nil, upsertErr
|
||||
}
|
||||
if isFinalStatus(record) {
|
||||
if emitErr := s.emitTransferStatusEvent(txCtx, record); emitErr != nil {
|
||||
return nil, emitErr
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
if err != nil {
|
||||
s.logger.Warn("Failed to update transfer status", zap.String("payment_ref", record.PaymentIntentID), zap.String("status", string(record.Status)), zap.Error(err))
|
||||
return err
|
||||
}
|
||||
if isFinalStatus(record) {
|
||||
s.emitTransferStatusEvent(ctx, record)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) emitTransferStatusEvent(_ context.Context, record *model.PaymentRecord) {
|
||||
if s == nil || s.producer == nil || record == nil {
|
||||
return
|
||||
func (s *Service) emitTransferStatusEvent(ctx context.Context, record *model.PaymentRecord) error {
|
||||
if s == nil || record == nil {
|
||||
return nil
|
||||
}
|
||||
if s.producer == nil || s.outboxStore() == nil {
|
||||
return nil
|
||||
}
|
||||
status, err := toOpStatus(record)
|
||||
if err != nil {
|
||||
s.logger.Warn("Failed to map transfer status for transfer status event", zap.Error(err), mzap.ObjRef("transfer_ref", record.ID))
|
||||
return err
|
||||
}
|
||||
|
||||
exec := pmodel.PaymentGatewayExecution{
|
||||
@@ -55,13 +80,15 @@ func (s *Service) emitTransferStatusEvent(_ context.Context, record *model.Payme
|
||||
IdempotencyKey: record.IdempotencyKey,
|
||||
ExecutedMoney: record.ExecutedMoney,
|
||||
PaymentRef: record.PaymentRef,
|
||||
Status: toOpStatus(record),
|
||||
Status: status,
|
||||
OperationRef: record.OperationRef,
|
||||
Error: record.FailureReason,
|
||||
TransferRef: record.ID.Hex(),
|
||||
}
|
||||
env := paymentgateway.PaymentGatewayExecution(mservice.MntxGateway, &exec)
|
||||
if err := s.producer.SendMessage(env); err != nil {
|
||||
s.logger.Warn("Failed to publish transfer status event", zap.Error(err), mzap.ObjRef("transfer_ref", record.ID))
|
||||
if sendErr := s.sendWithOutbox(ctx, env); sendErr != nil {
|
||||
s.logger.Warn("Failed to publish transfer status event", zap.Error(sendErr), mzap.ObjRef("transfer_ref", record.ID))
|
||||
return sendErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user