new settlement flow
This commit is contained in:
@@ -22,7 +22,7 @@ require (
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260119175649-084b3e29eee1 // indirect
|
||||
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260120135015-58f41aaaee16 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.24.4 // indirect
|
||||
github.com/bmatcuk/doublestar/v4 v4.9.2 // indirect
|
||||
@@ -86,5 +86,5 @@ require (
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120174246-409b4a993575 // indirect
|
||||
)
|
||||
|
||||
@@ -6,8 +6,8 @@ github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
|
||||
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260119175649-084b3e29eee1 h1:uq8tHimZusJXFqiZG2EyQhyLYUWXkOumwNRFAyWcyHw=
|
||||
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260119175649-084b3e29eee1/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI=
|
||||
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260120135015-58f41aaaee16 h1:zhGT51SYKfpAlhdhfHqJbNMpjT5Q0E6/31byd8yTnxY=
|
||||
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260120135015-58f41aaaee16/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI=
|
||||
github.com/VictoriaMetrics/fastcache v1.13.0 h1:AW4mheMR5Vd9FkAPUv+NH6Nhw+fmbTMGMsNAoA/+4G0=
|
||||
github.com/VictoriaMetrics/fastcache v1.13.0/go.mod h1:hHXhl4DA2fTL2HTZDJFXWgW0LNjo6B+4aj2Wmng3TjU=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
@@ -362,8 +362,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3 h1:C4WAdL+FbjnGlpp2S+HMVhBeCq2Lcib4xZqfPNF6OoQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120174246-409b4a993575 h1:vzOYHDZEHIsPYYnaSYo60AqHkJronSu0rzTz/s4quL0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120174246-409b4a993575/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
|
||||
@@ -50,5 +50,5 @@ require (
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120174246-409b4a993575 // indirect
|
||||
)
|
||||
|
||||
@@ -214,8 +214,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3 h1:C4WAdL+FbjnGlpp2S+HMVhBeCq2Lcib4xZqfPNF6OoQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120174246-409b4a993575 h1:vzOYHDZEHIsPYYnaSYo60AqHkJronSu0rzTz/s4quL0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120174246-409b4a993575/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
|
||||
@@ -38,5 +38,5 @@ messaging:
|
||||
gateway:
|
||||
rail: "provider_settlement"
|
||||
target_chat_id_env: TGSETTLE_GATEWAY_CHAT_ID
|
||||
timeout_seconds: 120
|
||||
timeout_seconds: 259200
|
||||
accepted_user_ids: []
|
||||
|
||||
@@ -47,5 +47,5 @@ require (
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120174246-409b4a993575 // indirect
|
||||
)
|
||||
|
||||
@@ -212,8 +212,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3 h1:C4WAdL+FbjnGlpp2S+HMVhBeCq2Lcib4xZqfPNF6OoQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120174246-409b4a993575 h1:vzOYHDZEHIsPYYnaSYo60AqHkJronSu0rzTz/s4quL0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120174246-409b4a993575/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
confirmations "github.com/tech/sendico/pkg/messaging/notifications/confirmations"
|
||||
paymentgateway "github.com/tech/sendico/pkg/messaging/notifications/paymentgateway"
|
||||
np "github.com/tech/sendico/pkg/messaging/notifications/processor"
|
||||
tnotifications "github.com/tech/sendico/pkg/messaging/notifications/telegram"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
@@ -34,8 +35,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
defaultConfirmationTimeoutSeconds = 120
|
||||
defaultConfirmationTimeoutSeconds = 259200
|
||||
executedStatus = "executed"
|
||||
telegramSuccessReaction = "\u2705"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -43,6 +45,8 @@ const (
|
||||
metadataQuoteRef = "quote_ref"
|
||||
metadataTargetChatID = "target_chat_id"
|
||||
metadataOutgoingLeg = "outgoing_leg"
|
||||
metadataSourceAmount = "source_amount"
|
||||
metadataSourceCurrency = "source_currency"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
@@ -77,14 +81,14 @@ func NewService(logger mlogger.Logger, repo storage.Repository, producer msg.Pro
|
||||
}
|
||||
logger = logger.Named("service")
|
||||
svc := &Service{
|
||||
logger: logger,
|
||||
repo: repo,
|
||||
producer: producer,
|
||||
broker: broker,
|
||||
cfg: cfg,
|
||||
rail: strings.TrimSpace(cfg.Rail),
|
||||
logger: logger,
|
||||
repo: repo,
|
||||
producer: producer,
|
||||
broker: broker,
|
||||
cfg: cfg,
|
||||
rail: strings.TrimSpace(cfg.Rail),
|
||||
invokeURI: strings.TrimSpace(cfg.InvokeURI),
|
||||
pending: map[string]*model.PaymentGatewayIntent{},
|
||||
pending: map[string]*model.PaymentGatewayIntent{},
|
||||
}
|
||||
svc.chatID = strings.TrimSpace(readEnv(cfg.TargetChatIDEnv))
|
||||
svc.startConsumers()
|
||||
@@ -322,6 +326,7 @@ func (s *Service) onConfirmationResult(ctx context.Context, result *model.Confir
|
||||
}
|
||||
|
||||
s.publishExecution(intent, result)
|
||||
s.publishTelegramReaction(result)
|
||||
s.removeIntent(requestID)
|
||||
return nil
|
||||
}
|
||||
@@ -416,6 +421,39 @@ func (s *Service) publishExecution(intent *model.PaymentGatewayIntent, result *m
|
||||
zap.String("status", string(result.Status)))
|
||||
}
|
||||
|
||||
func (s *Service) publishTelegramReaction(result *model.ConfirmationResult) {
|
||||
if s == nil || s.producer == nil || result == nil || result.RawReply == nil {
|
||||
return
|
||||
}
|
||||
if result.Status != model.ConfirmationStatusConfirmed && result.Status != model.ConfirmationStatusClarified {
|
||||
return
|
||||
}
|
||||
request := &model.TelegramReactionRequest{
|
||||
RequestID: strings.TrimSpace(result.RequestID),
|
||||
ChatID: strings.TrimSpace(result.RawReply.ChatID),
|
||||
MessageID: strings.TrimSpace(result.RawReply.MessageID),
|
||||
Emoji: telegramSuccessReaction,
|
||||
}
|
||||
if request.ChatID == "" || request.MessageID == "" || request.Emoji == "" {
|
||||
return
|
||||
}
|
||||
env := tnotifications.TelegramReaction(string(mservice.PaymentGateway), request)
|
||||
if err := s.producer.SendMessage(env); err != nil {
|
||||
s.logger.Warn("Failed to publish telegram reaction",
|
||||
zap.Error(err),
|
||||
zap.String("request_id", request.RequestID),
|
||||
zap.String("chat_id", request.ChatID),
|
||||
zap.String("message_id", request.MessageID),
|
||||
zap.String("emoji", request.Emoji))
|
||||
return
|
||||
}
|
||||
s.logger.Info("Published telegram reaction",
|
||||
zap.String("request_id", request.RequestID),
|
||||
zap.String("chat_id", request.ChatID),
|
||||
zap.String("message_id", request.MessageID),
|
||||
zap.String("emoji", request.Emoji))
|
||||
}
|
||||
|
||||
func (s *Service) trackIntent(requestID string, intent *model.PaymentGatewayIntent) {
|
||||
if s == nil || intent == nil {
|
||||
return
|
||||
@@ -507,6 +545,7 @@ func intentFromSubmitTransfer(req *chainv1.SubmitTransferRequest, defaultRail, d
|
||||
if amount == nil {
|
||||
return nil, merrors.InvalidArgument("submit_transfer: amount is required")
|
||||
}
|
||||
metadata := req.GetMetadata()
|
||||
requestedMoney := &paymenttypes.Money{
|
||||
Amount: strings.TrimSpace(amount.GetAmount()),
|
||||
Currency: strings.TrimSpace(amount.GetCurrency()),
|
||||
@@ -514,7 +553,14 @@ func intentFromSubmitTransfer(req *chainv1.SubmitTransferRequest, defaultRail, d
|
||||
if requestedMoney.Amount == "" || requestedMoney.Currency == "" {
|
||||
return nil, merrors.InvalidArgument("submit_transfer: amount is required")
|
||||
}
|
||||
metadata := req.GetMetadata()
|
||||
sourceAmount := strings.TrimSpace(metadata[metadataSourceAmount])
|
||||
sourceCurrency := strings.TrimSpace(metadata[metadataSourceCurrency])
|
||||
if sourceAmount != "" && sourceCurrency != "" {
|
||||
requestedMoney = &paymenttypes.Money{
|
||||
Amount: sourceAmount,
|
||||
Currency: sourceCurrency,
|
||||
}
|
||||
}
|
||||
paymentIntentID := strings.TrimSpace(req.GetClientReference())
|
||||
if paymentIntentID == "" {
|
||||
paymentIntentID = strings.TrimSpace(metadata[metadataPaymentIntentID])
|
||||
|
||||
@@ -9,11 +9,13 @@ import (
|
||||
"github.com/tech/sendico/gateway/tgsettle/storage"
|
||||
storagemodel "github.com/tech/sendico/gateway/tgsettle/storage/model"
|
||||
envelope "github.com/tech/sendico/pkg/messaging/envelope"
|
||||
mloggerfactory "github.com/tech/sendico/pkg/mlogger/factory"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
notification "github.com/tech/sendico/pkg/model/notification"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
mloggerfactory "github.com/tech/sendico/pkg/mlogger/factory"
|
||||
paymenttypes "github.com/tech/sendico/pkg/payments/types"
|
||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||
chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
|
||||
)
|
||||
|
||||
type fakePaymentsStore struct {
|
||||
@@ -147,6 +149,25 @@ func TestOnIntentCreatesConfirmationRequest(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntentFromSubmitTransferUsesSourceMoney(t *testing.T) {
|
||||
req := &chainv1.SubmitTransferRequest{
|
||||
IdempotencyKey: "idem-1",
|
||||
ClientReference: "pi-1",
|
||||
Amount: &moneyv1.Money{Amount: "10.00", Currency: "EUR"},
|
||||
Metadata: map[string]string{
|
||||
metadataSourceAmount: "12.34",
|
||||
metadataSourceCurrency: "USD",
|
||||
},
|
||||
}
|
||||
intent, err := intentFromSubmitTransfer(req, "provider_settlement", "")
|
||||
if err != nil {
|
||||
t.Fatalf("intentFromSubmitTransfer error: %v", err)
|
||||
}
|
||||
if intent.RequestedMoney == nil || intent.RequestedMoney.Amount != "12.34" || intent.RequestedMoney.Currency != "USD" {
|
||||
t.Fatalf("expected source money override, got: %#v", intent.RequestedMoney)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfirmationResultPersistsExecutionAndReply(t *testing.T) {
|
||||
logger := mloggerfactory.NewLogger(false)
|
||||
repo := &fakeRepo{payments: &fakePaymentsStore{}, tg: &fakeTelegramStore{}}
|
||||
|
||||
Reference in New Issue
Block a user