improved logging in callbacks

This commit is contained in:
Stephan D
2026-03-03 01:07:35 +01:00
parent b10ec79fe0
commit bae4cd6e35
45 changed files with 226 additions and 146 deletions

View File

@@ -21,6 +21,7 @@ import (
msg "github.com/tech/sendico/pkg/messaging"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/mservice"
"github.com/tech/sendico/pkg/mutil/mzap"
feesv1 "github.com/tech/sendico/pkg/proto/billing/fees/v1"
tracev1 "github.com/tech/sendico/pkg/proto/common/trace/v1"
"go.mongodb.org/mongo-driver/v2/bson"
@@ -385,7 +386,7 @@ func (s *Service) computeQuoteWithTime(ctx context.Context, orgRef bson.ObjectID
logFields := []zap.Field{zap.Time("booked_at_used", bookedAt)}
if !orgRef.IsZero() {
logFields = append(logFields, zap.String("organization_ref", orgRef.Hex()))
logFields = append(logFields, mzap.ObjRef("organization_ref", orgRef))
}
logFields = append(logFields, logFieldsFromIntent(intent)...)

View File

@@ -76,6 +76,7 @@ type InitiatePayment struct {
PaymentBase `json:",inline"`
Intent *PaymentIntent `json:"intent,omitempty"`
QuoteRef string `json:"quoteRef,omitempty"`
ClientPaymentRef string `json:"clientPaymentRef,omitempty"`
}
func (r InitiatePayment) Validate() error {
@@ -108,6 +109,7 @@ func (r InitiatePayment) Validate() error {
type InitiatePayments struct {
PaymentBase `json:",inline"`
QuoteRef string `json:"quoteRef,omitempty"`
ClientPaymentRef string `json:"clientPaymentRef,omitempty"`
}
func (r *InitiatePayments) Validate() error {

View File

@@ -120,11 +120,11 @@ func (a *AccountAPI) resetPassword(r *http.Request) http.HandlerFunc {
var user model.Account
err = a.db.Get(ctx, accountRef, &user)
if errors.Is(err, merrors.ErrNoData) {
a.logger.Info("User not found for password reset", zap.String("account_ref", accountRef.Hex()))
a.logger.Info("User not found for password reset", mzap.ObjRef("account_ref", accountRef))
return response.NotFound(a.logger, a.Name(), "User not found")
}
if err != nil {
a.logger.Warn("Failed to get user for password reset", zap.Error(err), zap.String("account_ref", accountRef.Hex()))
a.logger.Warn("Failed to get user for password reset", zap.Error(err), mzap.ObjRef("account_ref", accountRef))
return response.Auto(a.logger, a.Name(), err)
}
@@ -140,7 +140,7 @@ func (a *AccountAPI) resetPassword(r *http.Request) http.HandlerFunc {
}
if t.AccountRef != accountRef {
a.logger.Warn("Token account reference does not match request account reference", zap.String("token_account_ref", t.AccountRef.Hex()), zap.String("request_account_ref", accountRef.Hex()))
a.logger.Warn("Token account reference does not match request account reference", mzap.ObjRef("token_account_ref", t.AccountRef), mzap.ObjRef("request_account_ref", accountRef))
return response.DataConflict(a.logger, a.Name(), "Token does not match account")
}

View File

@@ -13,6 +13,7 @@ import (
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/tech/sendico/pkg/merrors"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/mutil/mzap"
"github.com/tech/sendico/pkg/vault/kv"
"go.mongodb.org/mongo-driver/v2/bson"
"go.uber.org/zap"
@@ -152,7 +153,7 @@ func (m *vaultSigningSecretManager) Provision(
}
secretRef := "vault:" + secretPath + "#" + m.field
m.logger.Info("Callback signing secret stored", zap.String("secret_ref", secretRef), zap.String("callback_ref", callbackRef.Hex()))
m.logger.Info("Callback signing secret stored", zap.String("secret_ref", secretRef), mzap.ObjRef("callback_ref", callbackRef))
return secretRef, secret, nil
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/tech/sendico/pkg/model"
"github.com/tech/sendico/pkg/model/account_role"
"github.com/tech/sendico/pkg/mservice"
"github.com/tech/sendico/pkg/mutil/mzap"
describablev1 "github.com/tech/sendico/pkg/proto/common/describable/v1"
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
"github.com/tech/sendico/server/interface/api/srequest"
@@ -88,7 +89,7 @@ func (a *LedgerAPI) createAccount(r *http.Request, account *model.Account, token
Describable: describable,
})
if err != nil {
a.logger.Warn("Failed to create ledger account", zap.Error(err), zap.String("organization_ref", orgRef.Hex()))
a.logger.Warn("Failed to create ledger account", zap.Error(err), mzap.ObjRef("organization_ref", orgRef))
return response.Auto(a.logger, mservice.Ledger, err)
}

View File

@@ -47,7 +47,7 @@ func (a *LedgerAPI) listAccounts(r *http.Request, account *model.Account, token
resp, err := a.client.ListAccounts(ctx, req)
if err != nil {
a.logger.Warn("Failed to list ledger accounts", zap.Error(err), zap.String("organization_ref", orgRef.Hex()))
a.logger.Warn("Failed to list ledger accounts", zap.Error(err), mzap.ObjRef("organization_ref", orgRef))
return response.Auto(a.logger, mservice.Ledger, err)
}

View File

@@ -89,7 +89,7 @@ func (a *PaymentAPI) initiatePayment(r *http.Request, account *model.Account, to
req := &orchestrationv2.ExecutePaymentRequest{
Meta: requestMeta(orgRef.Hex(), payload.IdempotencyKey),
QuotationRef: quotationRef,
ClientPaymentRef: metadataValue(payload.Metadata, "client_payment_ref"),
ClientPaymentRef: strings.TrimSpace(payload.ClientPaymentRef),
}
resp, err := a.execution.ExecutePayment(ctx, req)
@@ -110,6 +110,7 @@ func decodeInitiatePayload(r *http.Request) (*srequest.InitiatePayment, error) {
}
payload.IdempotencyKey = strings.TrimSpace(payload.IdempotencyKey)
payload.QuoteRef = strings.TrimSpace(payload.QuoteRef)
payload.ClientPaymentRef = strings.TrimSpace(payload.ClientPaymentRef)
if err := payload.Validate(); err != nil {
return nil, err

View File

@@ -14,12 +14,12 @@ import (
"go.mongodb.org/mongo-driver/v2/bson"
)
func TestInitiateByQuote_DoesNotUseIntentRef(t *testing.T) {
func TestInitiateByQuote_ForwardsClientPaymentRef(t *testing.T) {
orgRef := bson.NewObjectID()
exec := &fakeExecutionClientForBatch{}
api := newBatchAPI(exec)
body := `{"idempotencyKey":"idem-by-quote","quoteRef":"quote-1","metadata":{"client_payment_ref":"client-ref-1"}}`
body := `{"idempotencyKey":"idem-by-quote","quoteRef":"quote-1","clientPaymentRef":"client-ref-1"}`
rr := invokeInitiateByQuote(t, api, orgRef, body)
if got, want := rr.Code, http.StatusOK; got != want {
t.Fatalf("status mismatch: got=%d want=%d body=%s", got, want, rr.Body.String())
@@ -32,6 +32,24 @@ func TestInitiateByQuote_DoesNotUseIntentRef(t *testing.T) {
}
}
func TestInitiateByQuote_DoesNotForwardLegacyClientPaymentRefFromMetadata(t *testing.T) {
orgRef := bson.NewObjectID()
exec := &fakeExecutionClientForBatch{}
api := newBatchAPI(exec)
body := `{"idempotencyKey":"idem-by-quote","quoteRef":"quote-1","metadata":{"client_payment_ref":"legacy-client-ref"}}`
rr := invokeInitiateByQuote(t, api, orgRef, body)
if got, want := rr.Code, http.StatusOK; got != want {
t.Fatalf("status mismatch: got=%d want=%d body=%s", got, want, rr.Body.String())
}
if got, want := len(exec.executeReqs), 1; got != want {
t.Fatalf("execute calls mismatch: got=%d want=%d", got, want)
}
if got := exec.executeReqs[0].GetClientPaymentRef(); got != "" {
t.Fatalf("expected empty client_payment_ref, got=%q", got)
}
}
func TestInitiateByQuote_RejectsMetadataIntentRef(t *testing.T) {
orgRef := bson.NewObjectID()
exec := &fakeExecutionClientForBatch{}

View File

@@ -8,6 +8,7 @@ import (
"github.com/tech/sendico/pkg/api/http/response"
"github.com/tech/sendico/pkg/merrors"
"github.com/tech/sendico/pkg/model"
"github.com/tech/sendico/pkg/mutil/mzap"
orchestrationv2 "github.com/tech/sendico/pkg/proto/payments/orchestration/v2"
"github.com/tech/sendico/server/interface/api/srequest"
"github.com/tech/sendico/server/interface/api/sresponse"
@@ -39,7 +40,7 @@ func (a *PaymentAPI) initiatePaymentsByQuote(r *http.Request, account *model.Acc
return response.BadPayload(a.logger, a.Name(), err)
}
clientPaymentRef := metadataValue(payload.Metadata, "client_payment_ref")
clientPaymentRef := strings.TrimSpace(payload.ClientPaymentRef)
idempotencyKey := strings.TrimSpace(payload.IdempotencyKey)
quotationRef := strings.TrimSpace(payload.QuoteRef)
@@ -50,7 +51,7 @@ func (a *PaymentAPI) initiatePaymentsByQuote(r *http.Request, account *model.Acc
}
resp, err := a.execution.ExecuteBatchPayment(ctx, req)
if err != nil {
a.logger.Warn("Failed to initiate batch payments", zap.Error(err), zap.String("organization_ref", orgRef.Hex()))
a.logger.Warn("Failed to initiate batch payments", zap.Error(err), mzap.ObjRef("organization_ref", orgRef))
return grpcErrorResponse(a.logger, a.Name(), err)
}
@@ -72,6 +73,7 @@ func decodeInitiatePaymentsPayload(r *http.Request) (*srequest.InitiatePayments,
}
payload.IdempotencyKey = strings.TrimSpace(payload.IdempotencyKey)
payload.QuoteRef = strings.TrimSpace(payload.QuoteRef)
payload.ClientPaymentRef = strings.TrimSpace(payload.ClientPaymentRef)
if err := payload.Validate(); err != nil {
return nil, err

View File

@@ -50,7 +50,7 @@ func TestInitiatePaymentsByQuote_ForwardsClientPaymentRef(t *testing.T) {
exec := &fakeExecutionClientForBatch{}
api := newBatchAPI(exec)
body := `{"idempotencyKey":"idem-batch","quoteRef":"quote-1","metadata":{"client_payment_ref":"client-ref-1"}}`
body := `{"idempotencyKey":"idem-batch","quoteRef":"quote-1","clientPaymentRef":"client-ref-1"}`
rr := invokeInitiatePaymentsByQuote(t, api, orgRef, body)
if got, want := rr.Code, http.StatusOK; got != want {
t.Fatalf("status mismatch: got=%d want=%d body=%s", got, want, rr.Body.String())
@@ -67,6 +67,25 @@ func TestInitiatePaymentsByQuote_ForwardsClientPaymentRef(t *testing.T) {
}
}
func TestInitiatePaymentsByQuote_DoesNotForwardLegacyClientPaymentRefFromMetadata(t *testing.T) {
orgRef := bson.NewObjectID()
exec := &fakeExecutionClientForBatch{}
api := newBatchAPI(exec)
body := `{"idempotencyKey":"idem-batch","quoteRef":"quote-1","metadata":{"client_payment_ref":"legacy-client-ref"}}`
rr := invokeInitiatePaymentsByQuote(t, api, orgRef, body)
if got, want := rr.Code, http.StatusOK; got != want {
t.Fatalf("status mismatch: got=%d want=%d body=%s", got, want, rr.Body.String())
}
if got, want := len(exec.executeBatchReqs), 1; got != want {
t.Fatalf("execute batch calls mismatch: got=%d want=%d", got, want)
}
if got := exec.executeBatchReqs[0].GetClientPaymentRef(); got != "" {
t.Fatalf("expected empty client_payment_ref, got=%q", got)
}
}
func TestInitiatePaymentsByQuote_RejectsDeprecatedIntentRefField(t *testing.T) {
orgRef := bson.NewObjectID()
exec := &fakeExecutionClientForBatch{}

View File

@@ -8,6 +8,7 @@ import (
"github.com/tech/sendico/pkg/api/http/response"
"github.com/tech/sendico/pkg/merrors"
"github.com/tech/sendico/pkg/model"
"github.com/tech/sendico/pkg/mutil/mzap"
quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
"github.com/tech/sendico/server/interface/api/srequest"
"github.com/tech/sendico/server/interface/api/sresponse"
@@ -61,7 +62,7 @@ func (a *PaymentAPI) quotePayment(r *http.Request, account *model.Account, token
resp, err := a.quotation.QuotePayment(ctx, req)
if err != nil {
a.logger.Warn("Failed to quote payment", zap.Error(err), zap.String("organization_ref", orgRef.Hex()))
a.logger.Warn("Failed to quote payment", zap.Error(err), mzap.ObjRef("organization_ref", orgRef))
return grpcErrorResponse(a.logger, a.Name(), err)
}
@@ -117,7 +118,7 @@ func (a *PaymentAPI) quotePayments(r *http.Request, account *model.Account, toke
resp, err := a.quotation.QuotePayments(ctx, req)
if err != nil {
a.logger.Warn("Failed to quote payments", zap.Error(err), zap.String("organization_ref", orgRef.Hex()))
a.logger.Warn("Failed to quote payments", zap.Error(err), mzap.ObjRef("organization_ref", orgRef))
return grpcErrorResponse(a.logger, a.Name(), err)
}

View File

@@ -12,6 +12,7 @@ import (
"github.com/tech/sendico/pkg/merrors"
"github.com/tech/sendico/pkg/model"
"github.com/tech/sendico/pkg/mservice"
"github.com/tech/sendico/pkg/mutil/mzap"
connectorv1 "github.com/tech/sendico/pkg/proto/connector/v1"
"github.com/tech/sendico/server/interface/api/sresponse"
mutil "github.com/tech/sendico/server/internal/mutil/param"
@@ -65,24 +66,24 @@ func (a *WalletAPI) getWalletBalance(r *http.Request, account *model.Account, to
return response.Auto(a.logger, a.Name(), merrors.NoData("no crypto gateways available"))
}
a.logger.Debug("Resolved CRYPTO gateways for wallet balance lookup",
zap.String("organization_ref", orgRef.Hex()),
mzap.ObjRef("organization_ref", orgRef),
zap.String("wallet_ref", walletRef),
zap.Int("gateway_count", len(cryptoGateways)))
route, routeErr := a.walletRoute(ctx, orgRef.Hex(), walletRef)
if routeErr != nil {
a.logger.Warn("Failed to resolve wallet route", zap.Error(routeErr), zap.String("wallet_ref", walletRef), zap.String("organization_ref", orgRef.Hex()))
a.logger.Warn("Failed to resolve wallet route", zap.Error(routeErr), zap.String("wallet_ref", walletRef), mzap.ObjRef("organization_ref", orgRef))
}
if route != nil {
a.logger.Debug("Resolved stored wallet route",
zap.String("organization_ref", orgRef.Hex()),
mzap.ObjRef("organization_ref", orgRef),
zap.String("wallet_ref", walletRef),
zap.String("route_network", route.Network),
zap.String("route_gateway_id", route.GatewayID))
preferred := findGatewayForRoute(cryptoGateways, route)
if preferred != nil {
a.logger.Debug("Using preferred gateway from stored wallet route",
zap.String("organization_ref", orgRef.Hex()),
mzap.ObjRef("organization_ref", orgRef),
zap.String("wallet_ref", walletRef),
zap.String("gateway_id", preferred.ID),
zap.String("network", preferred.Network),
@@ -91,7 +92,7 @@ func (a *WalletAPI) getWalletBalance(r *http.Request, account *model.Account, to
if preferredErr == nil && bal != nil {
a.rememberWalletRoute(ctx, orgRef.Hex(), walletRef, preferred.Network, preferred.ID)
a.logger.Debug("Wallet balance resolved via preferred gateway",
zap.String("organization_ref", orgRef.Hex()),
mzap.ObjRef("organization_ref", orgRef),
zap.String("wallet_ref", walletRef),
zap.String("gateway_id", preferred.ID),
zap.String("network", preferred.Network))
@@ -124,20 +125,20 @@ func (a *WalletAPI) getWalletBalance(r *http.Request, account *model.Account, to
}
} else {
a.logger.Warn("Stored wallet route did not match any healthy discovery gateway",
zap.String("organization_ref", orgRef.Hex()),
mzap.ObjRef("organization_ref", orgRef),
zap.String("wallet_ref", walletRef),
zap.String("route_network", route.Network),
zap.String("route_gateway_id", route.GatewayID))
}
} else {
a.logger.Debug("Stored wallet route not found; using gateway fallback",
zap.String("organization_ref", orgRef.Hex()),
mzap.ObjRef("organization_ref", orgRef),
zap.String("wallet_ref", walletRef))
}
// Fall back to querying remaining gateways in parallel.
a.logger.Debug("Starting fallback wallet balance fan-out",
zap.String("organization_ref", orgRef.Hex()),
mzap.ObjRef("organization_ref", orgRef),
zap.String("wallet_ref", walletRef),
zap.Int("gateway_count", len(cryptoGateways)))
bal, err := a.queryBalanceFromGateways(ctx, cryptoGateways, orgRef.Hex(), walletRef)

View File

@@ -81,7 +81,7 @@ func (a *WalletAPI) create(r *http.Request, account *model.Account, token *sresp
return response.Auto(a.logger, a.Name(), merrors.InvalidArgument("no gateway available for network: "+networkName))
}
a.logger.Debug("Selected gateway for wallet creation",
zap.String("organization_ref", orgRef.Hex()),
mzap.ObjRef("organization_ref", orgRef),
zap.String("network", networkName),
zap.String("gateway_id", gateway.ID),
zap.String("gateway_network", gateway.Network),
@@ -134,7 +134,7 @@ func (a *WalletAPI) create(r *http.Request, account *model.Account, token *sresp
a.rememberWalletRoute(ctx, orgRef.Hex(), walletRef, networkName, gateway.ID)
a.rememberWalletRoute(ctx, orgRef.Hex(), walletRef, gateway.Network, gateway.ID)
a.logger.Debug("Persisted wallet route after wallet creation",
zap.String("organization_ref", orgRef.Hex()),
mzap.ObjRef("organization_ref", orgRef),
zap.String("wallet_ref", walletRef),
zap.String("network", networkName),
zap.String("gateway_id", gateway.ID))

View File

@@ -59,7 +59,7 @@ func (a *WalletAPI) listWallets(r *http.Request, account *model.Account, token *
return sresponse.Wallets(a.logger, nil, token)
}
a.logger.Debug("Resolved CRYPTO gateways for wallet list",
zap.String("organization_ref", orgRef.Hex()),
mzap.ObjRef("organization_ref", orgRef),
zap.Int("gateway_count", len(cryptoGateways)))
// Build request
@@ -80,7 +80,7 @@ func (a *WalletAPI) listWallets(r *http.Request, account *model.Account, token *
allAccounts := a.queryAllGateways(ctx, cryptoGateways, req)
dedupedAccounts := dedupeAccountsByWalletRef(allAccounts)
a.logger.Debug("Wallet list fan-out completed",
zap.String("organization_ref", orgRef.Hex()),
mzap.ObjRef("organization_ref", orgRef),
zap.Int("accounts_raw", len(allAccounts)),
zap.Int("accounts_deduped", len(dedupedAccounts)),
zap.Int("gateway_count", len(cryptoGateways)))

View File

@@ -15,6 +15,7 @@ import (
"github.com/tech/sendico/edge/callbacks/internal/storage"
"github.com/tech/sendico/pkg/merrors"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/mutil/mzap"
"go.uber.org/zap"
)
@@ -171,14 +172,14 @@ func (s *service) handleTask(ctx context.Context, workerID string, task *model.T
result = "blocked"
s.logger.Warn("Blocked task delivery due to URL validation failure",
zap.String("worker_id", workerID),
zap.String("task_id", task.ID.Hex()),
mzap.ObjRef("task_ref", task.ID),
zap.String("event_id", task.EventID),
zap.Error(err),
)
if markErr := s.tasks.MarkFailed(ctx, task.ID, attempt, err.Error(), statusCode, time.Now().UTC()); markErr != nil {
s.logger.Warn("Failed to mark blocked task as failed",
zap.String("worker_id", workerID),
zap.String("task_id", task.ID.Hex()),
mzap.ObjRef("task_ref", task.ID),
zap.Error(markErr),
)
}
@@ -195,7 +196,7 @@ func (s *service) handleTask(ctx context.Context, workerID string, task *model.T
result = "sign_error"
s.logger.Warn("Failed to sign task payload",
zap.String("worker_id", workerID),
zap.String("task_id", task.ID.Hex()),
mzap.ObjRef("task_ref", task.ID),
zap.String("event_id", task.EventID),
zap.String("signing_mode", task.SigningMode),
zap.Error(err),
@@ -203,7 +204,7 @@ func (s *service) handleTask(ctx context.Context, workerID string, task *model.T
if markErr := s.tasks.MarkFailed(ctx, task.ID, attempt, err.Error(), statusCode, time.Now().UTC()); markErr != nil {
s.logger.Warn("Failed to mark signing-error task as failed",
zap.String("worker_id", workerID),
zap.String("task_id", task.ID.Hex()),
mzap.ObjRef("task_ref", task.ID),
zap.Error(markErr),
)
}
@@ -218,7 +219,7 @@ func (s *service) handleTask(ctx context.Context, workerID string, task *model.T
result = "request_error"
s.logger.Warn("Failed to build callback request",
zap.String("worker_id", workerID),
zap.String("task_id", task.ID.Hex()),
mzap.ObjRef("task_ref", task.ID),
zap.String("event_id", task.EventID),
zap.String("endpoint_url", task.EndpointURL),
zap.Error(err),
@@ -226,7 +227,7 @@ func (s *service) handleTask(ctx context.Context, workerID string, task *model.T
if markErr := s.tasks.MarkFailed(ctx, task.ID, attempt, err.Error(), statusCode, time.Now().UTC()); markErr != nil {
s.logger.Warn("Failed to mark request-error task as failed",
zap.String("worker_id", workerID),
zap.String("task_id", task.ID.Hex()),
mzap.ObjRef("task_ref", task.ID),
zap.Error(markErr),
)
}
@@ -253,7 +254,7 @@ func (s *service) handleTask(ctx context.Context, workerID string, task *model.T
case outcomeDelivered:
result = string(outcomeDelivered)
if err := s.tasks.MarkDelivered(ctx, task.ID, statusCode, time.Since(started), now); err != nil {
s.logger.Warn("Failed to mark task delivered", zap.String("worker_id", workerID), zap.String("task_id", task.ID.Hex()), zap.Error(err))
s.logger.Warn("Failed to mark task delivered", zap.String("worker_id", workerID), mzap.ObjRef("task_ref", task.ID), zap.Error(err))
}
case outcomeRetry:
if attempt < task.MaxAttempts {
@@ -265,7 +266,7 @@ func (s *service) handleTask(ctx context.Context, workerID string, task *model.T
}
s.logger.Warn("Task delivery retry scheduled",
zap.String("worker_id", workerID),
zap.String("task_id", task.ID.Hex()),
mzap.ObjRef("task_ref", task.ID),
zap.String("event_id", task.EventID),
zap.Int("attempt", attempt),
zap.Int("status_code", statusCode),
@@ -273,7 +274,7 @@ func (s *service) handleTask(ctx context.Context, workerID string, task *model.T
zap.Time("next_attempt_at", next),
)
if err := s.tasks.MarkRetry(ctx, task.ID, attempt, next, lastErr, statusCode, now); err != nil {
s.logger.Warn("Failed to mark task retry", zap.String("worker_id", workerID), zap.String("task_id", task.ID.Hex()), zap.Error(err))
s.logger.Warn("Failed to mark task retry", zap.String("worker_id", workerID), mzap.ObjRef("task_ref", task.ID), zap.Error(err))
}
} else {
result = string(outcomeFailed)
@@ -283,7 +284,7 @@ func (s *service) handleTask(ctx context.Context, workerID string, task *model.T
}
s.logger.Warn("Task delivery failed after reaching max attempts",
zap.String("worker_id", workerID),
zap.String("task_id", task.ID.Hex()),
mzap.ObjRef("task_ref", task.ID),
zap.String("event_id", task.EventID),
zap.Int("attempt", attempt),
zap.Int("max_attempts", task.MaxAttempts),
@@ -291,7 +292,7 @@ func (s *service) handleTask(ctx context.Context, workerID string, task *model.T
zap.String("reason", lastErr),
)
if err := s.tasks.MarkFailed(ctx, task.ID, attempt, lastErr, statusCode, now); err != nil {
s.logger.Warn("Failed to mark task failed", zap.String("worker_id", workerID), zap.String("task_id", task.ID.Hex()), zap.Error(err))
s.logger.Warn("Failed to mark task failed", zap.String("worker_id", workerID), mzap.ObjRef("task_ref", task.ID), zap.Error(err))
}
}
default:
@@ -302,14 +303,14 @@ func (s *service) handleTask(ctx context.Context, workerID string, task *model.T
}
s.logger.Warn("Task delivery failed",
zap.String("worker_id", workerID),
zap.String("task_id", task.ID.Hex()),
mzap.ObjRef("task_ref", task.ID),
zap.String("event_id", task.EventID),
zap.Int("attempt", attempt),
zap.Int("status_code", statusCode),
zap.String("reason", lastErr),
)
if err := s.tasks.MarkFailed(ctx, task.ID, attempt, lastErr, statusCode, now); err != nil {
s.logger.Warn("Failed to mark task failed", zap.String("worker_id", workerID), zap.String("task_id", task.ID.Hex()), zap.Error(err))
s.logger.Warn("Failed to mark task failed", zap.String("worker_id", workerID), mzap.ObjRef("task_ref", task.ID), zap.Error(err))
}
}
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/tech/sendico/edge/callbacks/internal/storage"
"github.com/tech/sendico/pkg/merrors"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/mutil/mzap"
"go.mongodb.org/mongo-driver/v2/bson"
"go.uber.org/zap"
)
@@ -45,7 +46,7 @@ func (s *service) Resolve(ctx context.Context, eventType string, organizationRef
if err != nil {
s.logger.Warn("Failed to resolve active endpoints",
zap.String("event_type", eventType),
zap.String("organization_ref", organizationRef.Hex()),
mzap.ObjRef("organization_ref", organizationRef),
zap.Error(err),
)
return nil, err
@@ -53,7 +54,7 @@ func (s *service) Resolve(ctx context.Context, eventType string, organizationRef
s.logger.Debug("Resolved active endpoints",
zap.String("event_type", eventType),
zap.String("organization_ref", organizationRef.Hex()),
mzap.ObjRef("organization_ref", organizationRef),
zap.Int("endpoints", len(endpoints)),
)

View File

@@ -66,8 +66,7 @@ func (s *Service) postCreditResponder(_ context.Context, req *ledgerv1.PostCredi
existingEntry, err := s.storage.JournalEntries().GetByIdempotencyKey(ctx, orgRef, req.IdempotencyKey)
if err == nil && existingEntry != nil {
recordDuplicateRequest(journalEntryTypeCredit)
logger.Info("Duplicate credit request (idempotency)",
zap.String("existingEntryID", existingEntry.GetID().Hex()))
logger.Info("Duplicate credit request (idempotency)", mzap.StorableRef(existingEntry))
return &ledgerv1.PostResponse{
JournalEntryRef: existingEntry.GetID().Hex(),
Version: existingEntry.Version,

View File

@@ -64,8 +64,7 @@ func (s *Service) postDebitResponder(_ context.Context, req *ledgerv1.PostDebitR
existingEntry, err := s.storage.JournalEntries().GetByIdempotencyKey(ctx, orgRef, req.IdempotencyKey)
if err == nil && existingEntry != nil {
recordDuplicateRequest(journalEntryTypeDebit)
logger.Info("Duplicate debit request (idempotency)",
zap.String("existingEntryID", existingEntry.GetID().Hex()))
logger.Info("Duplicate debit request (idempotency)", mzap.StorableRef(existingEntry))
return &ledgerv1.PostResponse{
JournalEntryRef: existingEntry.GetID().Hex(),
Version: existingEntry.Version,
@@ -123,7 +122,7 @@ func (s *Service) postDebitResponder(_ context.Context, req *ledgerv1.PostDebitR
if err == storage.ErrAccountNotFound {
return nil, merrors.NoData(fmt.Sprintf("charges[%d]: account not found", i))
}
logger.Warn("Failed to get charge account", zap.Error(err), zap.String("chargeAccountRef", chargeAccountRef.Hex()))
logger.Warn("Failed to get charge account", zap.Error(err), mzap.ObjRef("charge_account_ref", chargeAccountRef))
return nil, merrors.Internal("failed to get charge account")
}
if err := validateAccountForOrg(chargeAccount, orgRef, charge.Money.Currency); err != nil {

View File

@@ -62,7 +62,7 @@ func (s *Service) postExternalCreditResponder(_ context.Context, req *ledgerv1.P
if err == nil && existingEntry != nil {
recordDuplicateRequest(journalEntryTypeCredit)
logger.Info("Duplicate external credit request (idempotency)",
zap.String("existingEntryID", existingEntry.GetID().Hex()))
mzap.StorableRef(existingEntry))
return &ledgerv1.PostResponse{
JournalEntryRef: existingEntry.GetID().Hex(),
Version: existingEntry.Version,
@@ -140,7 +140,7 @@ func (s *Service) postExternalCreditResponder(_ context.Context, req *ledgerv1.P
if err == storage.ErrAccountNotFound {
return nil, merrors.NoData(fmt.Sprintf("charges[%d]: account not found", i))
}
logger.Warn("Failed to get charge account", zap.Error(err), zap.String("chargeAccountRef", chargeAccountRef.Hex()))
logger.Warn("Failed to get charge account", zap.Error(err), mzap.ObjRef("charge_account_ref", chargeAccountRef))
return nil, merrors.Internal("failed to get charge account")
}
if err := validateAccountForOrg(chargeAccount, orgRef, charge.Money.Currency); err != nil {
@@ -287,8 +287,7 @@ func (s *Service) postExternalDebitResponder(_ context.Context, req *ledgerv1.Po
existingEntry, err := s.storage.JournalEntries().GetByIdempotencyKey(ctx, orgRef, req.IdempotencyKey)
if err == nil && existingEntry != nil {
recordDuplicateRequest(journalEntryTypeDebit)
logger.Info("Duplicate external debit request (idempotency)",
zap.String("existingEntryID", existingEntry.GetID().Hex()))
logger.Info("Duplicate external debit request (idempotency)", mzap.StorableRef(existingEntry))
return &ledgerv1.PostResponse{
JournalEntryRef: existingEntry.GetID().Hex(),
Version: existingEntry.Version,
@@ -366,7 +365,7 @@ func (s *Service) postExternalDebitResponder(_ context.Context, req *ledgerv1.Po
if err == storage.ErrAccountNotFound {
return nil, merrors.NoData(fmt.Sprintf("charges[%d]: account not found", i))
}
logger.Warn("Failed to get charge account", zap.Error(err), zap.String("chargeAccountRef", chargeAccountRef.Hex()))
logger.Warn("Failed to get charge account", zap.Error(err), mzap.ObjRef("charge_account_ref", chargeAccountRef))
return nil, merrors.Internal("failed to get charge account")
}
if err := validateAccountForOrg(chargeAccount, orgRef, charge.Money.Currency); err != nil {

View File

@@ -77,8 +77,7 @@ func (s *Service) fxResponder(_ context.Context, req *ledgerv1.FXRequest) gsresp
existingEntry, err := s.storage.JournalEntries().GetByIdempotencyKey(ctx, orgRef, req.IdempotencyKey)
if err == nil && existingEntry != nil {
recordDuplicateRequest(journalEntryTypeFX)
logger.Info("Duplicate FX request (idempotency)",
zap.String("existingEntryID", existingEntry.GetID().Hex()))
logger.Info("Duplicate FX request (idempotency)", mzap.StorableRef(existingEntry))
return &ledgerv1.PostResponse{
JournalEntryRef: existingEntry.GetID().Hex(),
Version: existingEntry.Version,
@@ -162,7 +161,7 @@ func (s *Service) fxResponder(_ context.Context, req *ledgerv1.FXRequest) gsresp
if err == storage.ErrAccountNotFound {
return nil, merrors.NoData(fmt.Sprintf("charges[%d]: account not found", i))
}
logger.Warn("Failed to get FX charge account", zap.Error(err), zap.String("chargeAccountRef", chargeAccountRef.Hex()))
logger.Warn("Failed to get FX charge account", zap.Error(err), mzap.ObjRef("charge_account_ref", chargeAccountRef))
return nil, merrors.Internal("failed to get charge account")
}
if err := validateAccountForOrg(chargeAccount, orgRef, charge.Money.Currency); err != nil {

View File

@@ -139,7 +139,7 @@ func (s *Service) resolveSettlementAccount(ctx context.Context, orgRef bson.Obje
if errors.Is(err, storage.ErrAccountNotFound) {
return nil, merrors.NoData("contra account not found")
}
s.logger.Warn("Failed to load override contra account", zap.Error(err), zap.String("accountRef", overrideRef.Hex()))
s.logger.Warn("Failed to load override contra account", zap.Error(err), mzap.ObjRef("account_ref", overrideRef))
return nil, merrors.Internal("failed to load contra account")
}
if err := validateAccountForOrg(account, orgRef, currency); err != nil {

View File

@@ -87,8 +87,7 @@ func (s *Service) transferResponder(_ context.Context, req *ledgerv1.TransferReq
existingEntry, err := s.storage.JournalEntries().GetByIdempotencyKey(ctx, orgRef, req.IdempotencyKey)
if err == nil && existingEntry != nil {
recordDuplicateRequest(journalEntryTypeTransfer)
logger.Info("Duplicate transfer request (idempotency)",
zap.String("existingEntryID", existingEntry.GetID().Hex()))
logger.Info("Duplicate transfer request (idempotency)", mzap.StorableRef(existingEntry))
return &ledgerv1.PostResponse{
JournalEntryRef: existingEntry.GetID().Hex(),
Version: existingEntry.Version,
@@ -172,7 +171,7 @@ func (s *Service) transferResponder(_ context.Context, req *ledgerv1.TransferReq
if err == storage.ErrAccountNotFound {
return nil, merrors.NoData(fmt.Sprintf("charges[%d]: account not found", i))
}
logger.Warn("Failed to get charge account", zap.Error(err), zap.String("chargeAccountRef", chargeAccountRef.Hex()))
logger.Warn("Failed to get charge account", zap.Error(err), mzap.ObjRef("charge_account_ref", chargeAccountRef))
return nil, merrors.Internal("failed to get charge account")
}
if err := validateAccountForOrg(chargeAccount, orgRef, charge.Money.Currency); err != nil {

View File

@@ -83,17 +83,17 @@ func (b *balancesStore) Upsert(ctx context.Context, balance *model.AccountBalanc
if err := b.repo.FindOneByFilter(ctx, filter, existing); err != nil {
if errors.Is(err, merrors.ErrNoData) {
b.logger.Debug("Inserting new balance", zap.String("accountRef", balance.AccountRef.Hex()))
b.logger.Debug("Inserting new balance", mzap.ObjRef("account_ref", balance.AccountRef))
return b.repo.Insert(ctx, balance, filter)
}
b.logger.Warn("Failed to fetch balance", zap.Error(err), zap.String("accountRef", balance.AccountRef.Hex()))
b.logger.Warn("Failed to fetch balance", zap.Error(err), mzap.ObjRef("account_ref", balance.AccountRef))
return err
}
if existing.GetID() != nil {
balance.SetID(*existing.GetID())
}
b.logger.Debug("Updating balance", zap.String("accountRef", balance.AccountRef.Hex()),
b.logger.Debug("Updating balance", mzap.ObjRef("account_ref", balance.AccountRef),
zap.String("balance", balance.Balance))
return b.repo.Update(ctx, balance)
}

View File

@@ -11,7 +11,7 @@ import (
const (
paymentTypeAccount pkgmodel.PaymentType = 8
maxPrivateMethodResolutionDepth = 8
maxPrivateMethodResolutionDepth int = 8
)
func (s *Service) GetPaymentMethodPrivate(ctx context.Context, req *methodsv1.GetPaymentMethodPrivateRequest) (*methodsv1.GetPaymentMethodPrivateResponse, error) {

View File

@@ -8,6 +8,7 @@ import (
np "github.com/tech/sendico/pkg/messaging/notifications/processor"
nm "github.com/tech/sendico/pkg/model/notification"
"github.com/tech/sendico/pkg/mservice"
"github.com/tech/sendico/pkg/mutil/mzap"
"go.mongodb.org/mongo-driver/v2/bson"
"go.uber.org/zap"
)
@@ -62,25 +63,25 @@ func (s *Service) onRecipientNotification(
if err != nil {
s.logger.Warn("Failed to cascade archive payment methods by recipient",
zap.Error(err),
zap.String("recipient_ref", recipientRef.Hex()),
zap.String("actor_account_ref", actorAccountRef.Hex()))
mzap.ObjRef("recipient_ref", recipientRef),
mzap.ObjRef("actor_account_ref", actorAccountRef))
return err
}
s.logger.Info("Recipient archive cascade applied to payment methods",
zap.String("recipient_ref", recipientRef.Hex()),
zap.String("actor_account_ref", actorAccountRef.Hex()),
mzap.ObjRef("recipient_ref", recipientRef),
mzap.ObjRef("actor_account_ref", actorAccountRef),
zap.Int("updated_count", updated))
case nm.NADeleted:
if err := s.pmstore.DeleteByRecipient(ctx, recipientRef); err != nil {
s.logger.Warn("Failed to cascade delete payment methods by recipient",
zap.Error(err),
zap.String("recipient_ref", recipientRef.Hex()),
zap.String("actor_account_ref", actorAccountRef.Hex()))
mzap.ObjRef("recipient_ref", recipientRef),
mzap.ObjRef("actor_account_ref", actorAccountRef))
return err
}
s.logger.Info("Recipient delete cascade applied to payment methods",
zap.String("recipient_ref", recipientRef.Hex()),
zap.String("actor_account_ref", actorAccountRef.Hex()))
mzap.ObjRef("recipient_ref", recipientRef),
mzap.ObjRef("actor_account_ref", actorAccountRef))
}
return nil

View File

@@ -9,6 +9,7 @@ import (
"github.com/tech/sendico/pkg/merrors"
"github.com/tech/sendico/pkg/mlogger"
pm "github.com/tech/sendico/pkg/model"
"github.com/tech/sendico/pkg/mutil/mzap"
"go.mongodb.org/mongo-driver/v2/bson"
"go.uber.org/zap"
)
@@ -24,7 +25,7 @@ type svc struct {
func (s *svc) Create(in Input) (payment *Payment, err error) {
logger := s.logger
logger.Debug("Starting Create",
zap.String("organization_ref", in.OrganizationRef.Hex()),
mzap.ObjRef("organization_ref", in.OrganizationRef),
zap.String("quotation_ref", strings.TrimSpace(in.QuotationRef)),
zap.Int("steps_count", len(in.Steps)),
)

View File

@@ -10,6 +10,7 @@ import (
"github.com/tech/sendico/payments/storage/model"
"github.com/tech/sendico/pkg/merrors"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/mutil/mzap"
"go.uber.org/zap"
)
@@ -26,7 +27,7 @@ func (s *svc) TryReuse(
) (payment *model.Payment, reused bool, err error) {
logger := s.logger
logger.Debug("Starting Try reuse",
zap.String("organization_ref", in.OrganizationID.Hex()),
mzap.ObjRef("organization_ref", payment.OrganizationRef),
zap.Bool("has_idempotency_key", strings.TrimSpace(in.IdempotencyKey) != ""),
)
defer func(start time.Time) {

View File

@@ -12,6 +12,7 @@ import (
"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/prepo"
"github.com/tech/sendico/pkg/merrors"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/mutil/mzap"
"go.mongodb.org/mongo-driver/v2/bson"
"go.uber.org/zap"
)
@@ -61,7 +62,7 @@ func newService(deps Dependencies) (Service, error) {
func (s *svc) GetPayment(ctx context.Context, in GetPaymentInput) (payment *agg.Payment, err error) {
logger := s.logger
logger.Debug("Starting Get payment",
zap.String("organization_ref", in.OrganizationRef.Hex()),
mzap.ObjRef("organization_ref", in.OrganizationRef),
zap.String("payment_ref", strings.TrimSpace(in.PaymentRef)),
)
defer func(start time.Time) {
@@ -93,7 +94,7 @@ func (s *svc) GetPayment(ctx context.Context, in GetPaymentInput) (payment *agg.
func (s *svc) ListPayments(ctx context.Context, in ListPaymentsInput) (out *ListPaymentsOutput, err error) {
logger := s.logger
logger.Debug("Starting List payments",
zap.String("organization_ref", in.OrganizationRef.Hex()),
mzap.ObjRef("organization_ref", in.OrganizationRef),
zap.String("quotation_ref", strings.TrimSpace(in.QuotationRef)),
zap.Int("states_count", len(in.States)),
zap.Int32("limit", in.Limit),

View File

@@ -175,13 +175,13 @@ func (s *svc) GetByPaymentRef(ctx context.Context, orgRef bson.ObjectID, payment
logger := s.logger
requestPaymentRef := strings.TrimSpace(paymentRef)
logger.Debug("Starting Get by payment ref",
zap.String("organization_ref", orgRef.Hex()),
mzap.ObjRef("organization_ref", orgRef),
zap.String("payment_ref", requestPaymentRef),
)
defer func(start time.Time) {
fields := []zap.Field{
zap.Int64("duration_ms", time.Since(start).Milliseconds()),
zap.String("organization_ref", orgRef.Hex()),
mzap.ObjRef("organization_ref", orgRef),
zap.String("payment_ref", requestPaymentRef),
}
if payment != nil {
@@ -225,7 +225,7 @@ func (s *svc) GetByPaymentRefGlobal(ctx context.Context, paymentRef string) (pay
}
if payment != nil {
fields = append(fields,
zap.String("organization_ref", payment.OrganizationRef.Hex()),
mzap.ObjRef("organization_ref", payment.OrganizationRef),
zap.String("state", string(payment.State)),
zap.Uint64("version", payment.Version),
)
@@ -253,7 +253,7 @@ func (s *svc) GetByIdempotencyKey(ctx context.Context, orgRef bson.ObjectID, ide
logger := s.logger
hasKey := strings.TrimSpace(idempotencyKey) != ""
logger.Debug("Starting Get by idempotency key",
zap.String("organization_ref", orgRef.Hex()),
mzap.ObjRef("organization_ref", orgRef),
zap.Bool("has_idempotency_key", hasKey),
)
defer func(start time.Time) {
@@ -298,7 +298,7 @@ func (s *svc) GetByIdempotencyKey(ctx context.Context, orgRef bson.ObjectID, ide
func (s *svc) ListByQuotationRef(ctx context.Context, in ListByQuotationRefInput) (out *ListOutput, err error) {
logger := s.logger
logger.Debug("Starting List by quotation ref",
zap.String("organization_ref", in.OrganizationRef.Hex()),
mzap.ObjRef("organization_ref", in.OrganizationRef),
zap.String("quotation_ref", strings.TrimSpace(in.QuotationRef)),
zap.Int32("limit", in.Limit),
)
@@ -337,7 +337,7 @@ func (s *svc) ListByQuotationRef(ctx context.Context, in ListByQuotationRefInput
func (s *svc) ListByState(ctx context.Context, in ListByStateInput) (out *ListOutput, err error) {
logger := s.logger
logger.Debug("Starting List by state",
zap.String("organization_ref", in.OrganizationRef.Hex()),
mzap.ObjRef("organization_ref", in.OrganizationRef),
zap.String("state", string(in.State)),
zap.Int32("limit", in.Limit),
)

View File

@@ -5,12 +5,13 @@ import (
"crypto/sha256"
"encoding/hex"
"errors"
"github.com/tech/sendico/pkg/discovery"
"sort"
"strconv"
"strings"
"time"
"github.com/tech/sendico/pkg/discovery"
"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/agg"
"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/batchmeta"
"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/idem"
@@ -60,7 +61,7 @@ func (s *svc) ExecuteBatchPayment(ctx context.Context, req *orchestrationv2.Exec
}
resolved, err := s.quote.ResolveAll(ctx, s.quoteStore, qsnap.ResolveAllInput{
OrganizationID: requestCtx.OrganizationID,
OrganizationRef: requestCtx.OrganizationID,
QuotationRef: requestCtx.QuotationRef,
})
if err != nil {

View File

@@ -11,6 +11,7 @@ import (
pon "github.com/tech/sendico/pkg/messaging/notifications/paymentorchestrator"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/model"
"github.com/tech/sendico/pkg/mutil/mzap"
"go.uber.org/zap"
)
@@ -64,7 +65,7 @@ func (p *brokerPaymentStatusPublisher) Publish(_ context.Context, in paymentStat
if paymentRef == "" || payment.OrganizationRef.IsZero() {
p.logger.Warn("Skipping payment status publish due to missing identifiers",
zap.String("payment_ref", paymentRef),
zap.String("organization_ref", payment.OrganizationRef.Hex()),
mzap.ObjRef("organization_ref", payment.OrganizationRef),
)
return nil
}

View File

@@ -36,7 +36,7 @@ type Output struct {
// ResolveAllInput defines lookup scope for resolving all items in a batch quotation.
type ResolveAllInput struct {
OrganizationID bson.ObjectID
OrganizationRef bson.ObjectID
QuotationRef string
}

View File

@@ -41,7 +41,7 @@ func TestResolveAll_BatchReturnsAllItems(t *testing.T) {
return record, nil
},
}, ResolveAllInput{
OrganizationID: orgID,
OrganizationRef: orgID,
QuotationRef: "batch-quote-ref",
})
if err != nil {
@@ -94,7 +94,7 @@ func TestResolveAll_SingleShapeReturnsOneItem(t *testing.T) {
return record, nil
},
}, ResolveAllInput{
OrganizationID: orgID,
OrganizationRef: orgID,
QuotationRef: "single-quote-ref",
})
if err != nil {
@@ -137,7 +137,7 @@ func TestResolveAll_NonExecutableItemFails(t *testing.T) {
return record, nil
},
}, ResolveAllInput{
OrganizationID: orgID,
OrganizationRef: orgID,
QuotationRef: "batch-mixed",
})
if err == nil {
@@ -172,7 +172,7 @@ func TestResolveAll_ExpiredQuoteFails(t *testing.T) {
return record, nil
},
}, ResolveAllInput{
OrganizationID: orgID,
OrganizationRef: orgID,
QuotationRef: "expired-quote",
})
if err == nil {
@@ -187,7 +187,7 @@ func TestResolveAll_EmptyQuotationRefFails(t *testing.T) {
resolver := New(Dependencies{Logger: zap.NewNop()})
_, err := resolver.ResolveAll(context.Background(), &fakeStore{}, ResolveAllInput{
OrganizationID: bson.NewObjectID(),
OrganizationRef: bson.NewObjectID(),
QuotationRef: "",
})
if err == nil {
@@ -199,7 +199,7 @@ func TestResolveAll_QuoteNotFoundFails(t *testing.T) {
resolver := New(Dependencies{Logger: zap.NewNop()})
_, err := resolver.ResolveAll(context.Background(), &fakeStore{}, ResolveAllInput{
OrganizationID: bson.NewObjectID(),
OrganizationRef: bson.NewObjectID(),
QuotationRef: "nonexistent",
})
if err == nil {
@@ -234,7 +234,7 @@ func TestResolveAll_SetsQuoteRefWhenEmpty(t *testing.T) {
return record, nil
},
}, ResolveAllInput{
OrganizationID: orgID,
OrganizationRef: orgID,
QuotationRef: "batch-ref",
})
if err != nil {

View File

@@ -11,6 +11,7 @@ import (
quotestorage "github.com/tech/sendico/payments/storage/quote"
"github.com/tech/sendico/pkg/merrors"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/mutil/mzap"
"go.mongodb.org/mongo-driver/v2/bson"
"go.uber.org/zap"
)
@@ -33,7 +34,7 @@ func (s *svc) Resolve(
) (out *Output, err error) {
logger := s.logger
logger.Debug("Starting Resolve",
zap.String("organization_ref", in.OrganizationID.Hex()),
mzap.ObjRef("organization_ref", in.OrganizationID),
zap.String("quotation_ref", strings.TrimSpace(in.QuotationRef)),
)
defer func(start time.Time) {
@@ -105,7 +106,7 @@ func (s *svc) ResolveAll(
) (out *ResolveAllOutput, err error) {
logger := s.logger
logger.Debug("Starting ResolveAll",
zap.String("organization_ref", in.OrganizationID.Hex()),
mzap.ObjRef("organization_ref", in.OrganizationRef),
zap.String("quotation_ref", strings.TrimSpace(in.QuotationRef)),
)
defer func(start time.Time) {
@@ -126,7 +127,7 @@ func (s *svc) ResolveAll(
if store == nil {
return nil, merrors.InvalidArgument("quotes store is required")
}
if in.OrganizationID.IsZero() {
if in.OrganizationRef.IsZero() {
return nil, merrors.InvalidArgument("organization_id is required")
}
quoteRef := strings.TrimSpace(in.QuotationRef)
@@ -134,7 +135,7 @@ func (s *svc) ResolveAll(
return nil, merrors.InvalidArgument("quotation_ref is required")
}
record, err := store.GetByRef(ctx, in.OrganizationID, quoteRef)
record, err := store.GetByRef(ctx, in.OrganizationRef, quoteRef)
if err != nil {
if errors.Is(err, quotestorage.ErrQuoteNotFound) || errors.Is(err, merrors.ErrNoData) {
return nil, ErrQuoteNotFound

View File

@@ -3,10 +3,12 @@ package orchestrator
import (
"context"
"errors"
"github.com/tech/sendico/pkg/discovery"
"strings"
"time"
"github.com/tech/sendico/pkg/discovery"
"github.com/tech/sendico/pkg/mutil/mzap"
"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/agg"
"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/erecon"
"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/prepo"
@@ -111,7 +113,7 @@ func (s *Service) onPaymentGatewayExecution(ctx context.Context, msg *pmodel.Pay
s.logger.Debug("Reconciling payment from gateway execution event",
zap.String("payment_ref", strings.TrimSpace(payment.PaymentRef)),
zap.String("organization_ref", payment.OrganizationRef.Hex()),
mzap.ObjRef("organization_ref", payment.OrganizationRef),
zap.String("step_ref", strings.TrimSpace(event.StepRef)),
zap.String("status", strings.TrimSpace(string(event.Status))),
zap.String("transfer_ref", strings.TrimSpace(event.TransferRef)),
@@ -457,7 +459,7 @@ func (s *Service) pollObserveCandidate(ctx context.Context, payment *agg.Payment
s.logger.Debug("Reconciling payment from observe polling result",
zap.String("payment_ref", strings.TrimSpace(payment.PaymentRef)),
zap.String("organization_ref", payment.OrganizationRef.Hex()),
mzap.ObjRef("organization_ref", payment.OrganizationRef),
zap.String("step_ref", candidate.stepRef),
zap.String("status", strings.TrimSpace(string(event.Status))),
zap.String("transfer_ref", candidate.transferRef),

View File

@@ -5,6 +5,7 @@ import (
"strings"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/mutil/mzap"
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
endpointv1 "github.com/tech/sendico/pkg/proto/payments/endpoint/v1"
quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
@@ -33,7 +34,7 @@ type quoteIntentLogSummary struct {
func (s *QuotationServiceV2) quotePaymentLogger(req *quotationv2.QuotePaymentRequest) mlogger.Logger {
return s.logger.With(
zap.String("flow_ref", bson.NewObjectID().Hex()),
mzap.ObjRef("flow_ref", bson.NewObjectID()),
zap.String("rpc_method", "QuotePayment"),
zap.String("organization_ref", strings.TrimSpace(req.GetMeta().GetOrganizationRef())),
zap.String("idempotency_key", strings.TrimSpace(req.GetIdempotencyKey())),
@@ -44,7 +45,7 @@ func (s *QuotationServiceV2) quotePaymentLogger(req *quotationv2.QuotePaymentReq
func (s *QuotationServiceV2) quotePaymentsLogger(req *quotationv2.QuotePaymentsRequest) mlogger.Logger {
return s.logger.With(
zap.String("flow_ref", bson.NewObjectID().Hex()),
mzap.ObjRef("flow_ref", bson.NewObjectID()),
zap.String("rpc_method", "QuotePayments"),
zap.String("organization_ref", strings.TrimSpace(req.GetMeta().GetOrganizationRef())),
zap.String("idempotency_key", strings.TrimSpace(req.GetIdempotencyKey())),

View File

@@ -8,6 +8,7 @@ import (
"github.com/tech/sendico/pkg/db/storable"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/model"
"github.com/tech/sendico/pkg/mutil/mzap"
"go.mongodb.org/mongo-driver/v2/bson"
"go.uber.org/zap"
)
@@ -43,7 +44,7 @@ func (db *IndexableDB[T]) Reorder(ctx context.Context, objectRef bson.ObjectID,
if err != nil {
db.logger.Error("Failed to get object for reordering",
zap.Error(err),
zap.String("object_ref", objectRef.Hex()),
mzap.ObjRef("object_ref", objectRef),
zap.Int("new_index", newIndex))
return err
}
@@ -53,7 +54,7 @@ func (db *IndexableDB[T]) Reorder(ctx context.Context, objectRef bson.ObjectID,
currentIndex := indexable.Index
if currentIndex == newIndex {
db.logger.Debug("No reordering needed - same index",
zap.String("object_ref", objectRef.Hex()),
mzap.ObjRef("object_ref", objectRef),
zap.Int("current_index", currentIndex),
zap.Int("new_index", newIndex))
return nil // No change needed
@@ -71,14 +72,14 @@ func (db *IndexableDB[T]) Reorder(ctx context.Context, objectRef bson.ObjectID,
if err != nil {
db.logger.Error("Failed to shift objects during reordering (moving down)",
zap.Error(err),
zap.String("object_ref", objectRef.Hex()),
mzap.ObjRef("object_ref", objectRef),
zap.Int("current_index", currentIndex),
zap.Int("new_index", newIndex),
zap.Int("updated_count", updatedCount))
return err
}
db.logger.Debug("Successfully shifted objects (moving down)",
zap.String("object_ref", objectRef.Hex()),
mzap.ObjRef("object_ref", objectRef),
zap.Int("updated_count", updatedCount))
} else {
// Moving up: shift items between newIndex and currentIndex-1 down by +1
@@ -91,14 +92,14 @@ func (db *IndexableDB[T]) Reorder(ctx context.Context, objectRef bson.ObjectID,
if err != nil {
db.logger.Error("Failed to shift objects during reordering (moving up)",
zap.Error(err),
zap.String("object_ref", objectRef.Hex()),
mzap.ObjRef("object_ref", objectRef),
zap.Int("current_index", currentIndex),
zap.Int("new_index", newIndex),
zap.Int("updated_count", updatedCount))
return err
}
db.logger.Debug("Successfully shifted objects (moving up)",
zap.String("object_ref", objectRef.Hex()),
mzap.ObjRef("object_ref", objectRef),
zap.Int("updated_count", updatedCount))
}
@@ -108,14 +109,14 @@ func (db *IndexableDB[T]) Reorder(ctx context.Context, objectRef bson.ObjectID,
if err != nil {
db.logger.Error("Failed to update target object index",
zap.Error(err),
zap.String("object_ref", objectRef.Hex()),
mzap.ObjRef("object_ref", objectRef),
zap.Int("current_index", currentIndex),
zap.Int("new_index", newIndex))
return err
}
db.logger.Info("Successfully reordered object",
zap.String("object_ref", objectRef.Hex()),
mzap.ObjRef("object_ref", objectRef),
zap.Int("old_index", currentIndex),
zap.Int("new_index", newIndex))
return nil

View File

@@ -5,20 +5,22 @@ import 'package:pshared/data/dto/payment/intent/payment.dart';
part 'initiate.g.dart';
@JsonSerializable()
class InitiatePaymentRequest extends PaymentBaseRequest {
final PaymentIntentDTO? intent;
final String? quoteRef;
final String? clientPaymentRef;
const InitiatePaymentRequest({
required super.idempotencyKey,
super.metadata,
this.intent,
this.quoteRef,
this.clientPaymentRef,
});
factory InitiatePaymentRequest.fromJson(Map<String, dynamic> json) => _$InitiatePaymentRequestFromJson(json);
factory InitiatePaymentRequest.fromJson(Map<String, dynamic> json) =>
_$InitiatePaymentRequestFromJson(json);
@override
Map<String, dynamic> toJson() => _$InitiatePaymentRequestToJson(this);
}

View File

@@ -3,18 +3,20 @@ import 'package:pshared/api/requests/payment/base.dart';
part 'initiate_payments.g.dart';
@JsonSerializable()
class InitiatePaymentsRequest extends PaymentBaseRequest {
final String quoteRef;
final String? clientPaymentRef;
const InitiatePaymentsRequest({
required super.idempotencyKey,
super.metadata,
required this.quoteRef,
this.clientPaymentRef,
});
factory InitiatePaymentsRequest.fromJson(Map<String, dynamic> json) => _$InitiatePaymentsRequestFromJson(json);
factory InitiatePaymentsRequest.fromJson(Map<String, dynamic> json) =>
_$InitiatePaymentsRequestFromJson(json);
@override
Map<String, dynamic> toJson() => _$InitiatePaymentsRequestToJson(this);
}

View File

@@ -7,7 +7,6 @@ import 'package:pshared/provider/resource.dart';
import 'package:pshared/service/payment/multiple.dart';
import 'package:pshared/utils/exception.dart';
class MultiPaymentProvider extends ChangeNotifier {
late OrganizationsProvider _organization;
late MultiQuotationProvider _quotation;
@@ -30,6 +29,7 @@ class MultiPaymentProvider extends ChangeNotifier {
Future<List<Payment>> pay({
String? idempotencyKey,
String? clientPaymentRef,
Map<String, String>? metadata,
}) async {
if (!_organization.isOrganizationSet) {
@@ -52,6 +52,7 @@ class MultiPaymentProvider extends ChangeNotifier {
_organization.current.id,
quoteRef,
idempotencyKey: idempotencyKey,
clientPaymentRef: clientPaymentRef,
metadata: metadata,
);

View File

@@ -7,12 +7,15 @@ import 'package:pshared/provider/resource.dart';
import 'package:pshared/service/payment/service.dart';
import 'package:pshared/utils/exception.dart';
class PaymentProvider extends ChangeNotifier {
late OrganizationsProvider _organization;
late QuotationProvider _quotation;
Resource<Payment> _payment = Resource(data: null, isLoading: false, error: null);
Resource<Payment> _payment = Resource(
data: null,
isLoading: false,
error: null,
);
bool _isLoaded = false;
void update(OrganizationsProvider organization, QuotationProvider quotation) {
@@ -23,15 +26,21 @@ class PaymentProvider extends ChangeNotifier {
Payment? get payment => _payment.data;
bool get isLoading => _payment.isLoading;
Exception? get error => _payment.error;
bool get isReady => _isLoaded && !_payment.isLoading && _payment.error == null;
bool get isReady =>
_isLoaded && !_payment.isLoading && _payment.error == null;
void _setResource(Resource<Payment> payment) {
_payment = payment;
notifyListeners();
}
Future<Payment?> pay({String? idempotencyKey, Map<String, String>? metadata}) async {
if (!_organization.isOrganizationSet) throw StateError('Organization is not set');
Future<Payment?> pay({
String? idempotencyKey,
String? clientPaymentRef,
Map<String, String>? metadata,
}) async {
if (!_organization.isOrganizationSet)
throw StateError('Organization is not set');
final quoteRef = _quotation.quotation?.quoteRef;
if (quoteRef == null || quoteRef.isEmpty) {
throw StateError('Quotation reference is not set');
@@ -49,12 +58,17 @@ class PaymentProvider extends ChangeNotifier {
_organization.current.id,
quoteRef,
idempotencyKey: resolvedIdempotencyKey,
clientPaymentRef: clientPaymentRef,
metadata: metadata,
);
_isLoaded = true;
_setResource(_payment.copyWith(data: response, isLoading: false, error: null));
_setResource(
_payment.copyWith(data: response, isLoading: false, error: null),
);
} catch (e) {
_setResource(_payment.copyWith(data: null, error: toException(e), isLoading: false));
_setResource(
_payment.copyWith(data: null, error: toException(e), isLoading: false),
);
}
return _payment.data;
}

View File

@@ -13,7 +13,6 @@ import 'package:pshared/models/payment/quote/quotes.dart';
import 'package:pshared/service/authorization/service.dart';
import 'package:pshared/service/services.dart';
class MultiplePaymentsService {
static final _logger = Logger('service.payment.multiple');
static const String _objectType = Services.payments;
@@ -37,6 +36,7 @@ class MultiplePaymentsService {
String organizationRef,
String quoteRef, {
String? idempotencyKey,
String? clientPaymentRef,
Map<String, String>? metadata,
}) async {
_logger.fine(
@@ -45,6 +45,7 @@ class MultiplePaymentsService {
final request = InitiatePaymentsRequest(
idempotencyKey: idempotencyKey ?? const Uuid().v4(),
quoteRef: quoteRef,
clientPaymentRef: clientPaymentRef,
metadata: metadata,
);

View File

@@ -82,6 +82,7 @@ class PaymentService {
String organizationRef,
String quotationRef, {
String? idempotencyKey,
String? clientPaymentRef,
Map<String, String>? metadata,
}) async {
_logger.fine(
@@ -90,6 +91,7 @@ class PaymentService {
final request = InitiatePaymentRequest(
idempotencyKey: idempotencyKey ?? Uuid().v4(),
quoteRef: quotationRef,
clientPaymentRef: clientPaymentRef,
metadata: metadata,
);
final response = await AuthorizationService.getPOSTResponse(

View File

@@ -158,16 +158,13 @@ void main() {
final request = InitiatePaymentRequest(
idempotencyKey: 'idem-2',
quoteRef: 'q-1',
metadata: const {'client_payment_ref': 'cp-1'},
clientPaymentRef: 'cp-1',
);
final json = request.toJson();
expect(json['idempotencyKey'], equals('idem-2'));
expect(json['quoteRef'], equals('q-1'));
expect(
(json['metadata'] as Map<String, dynamic>)['client_payment_ref'],
equals('cp-1'),
);
expect(json['clientPaymentRef'], equals('cp-1'));
expect(json.containsKey('intent'), isTrue);
expect(json['intent'], isNull);
});
@@ -176,16 +173,13 @@ void main() {
final request = InitiatePaymentsRequest(
idempotencyKey: 'idem-3',
quoteRef: 'q-2',
metadata: const {'client_payment_ref': 'cp-1'},
clientPaymentRef: 'cp-1',
);
final json = request.toJson();
expect(json['idempotencyKey'], equals('idem-3'));
expect(json['quoteRef'], equals('q-2'));
expect(
(json['metadata'] as Map<String, dynamic>)['client_payment_ref'],
equals('cp-1'),
);
expect(json['clientPaymentRef'], equals('cp-1'));
expect(json.containsKey('intentRef'), isFalse);
expect(json.containsKey('intentRefs'), isFalse);
});

View File

@@ -107,6 +107,9 @@ components:
quoteRef:
description: Reference to a previously generated quote to execute.
type: string
clientPaymentRef:
description: Optional caller-side payment correlation reference.
type: string
InitiatePaymentsRequest:
description: Request payload to initiate multiple payments from a multi-quote reference.
@@ -120,6 +123,9 @@ components:
quoteRef:
description: Reference to a previously generated multi-quote.
type: string
clientPaymentRef:
description: Optional caller-side payment correlation reference.
type: string
InitiatePaymentByQuoteRequest:
description: Request payload to initiate one payment from an existing quote reference.
@@ -133,3 +139,6 @@ components:
quoteRef:
description: Reference to a previously generated quote to execute.
type: string
clientPaymentRef:
description: Optional caller-side payment correlation reference.
type: string