Merge pull request 'Callbacks service docs updated' (#598) from callbacks-596 into main
All checks were successful
ci/woodpecker/push/billing_documents Pipeline was successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/callbacks Pipeline was successful
ci/woodpecker/push/discovery Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/gateway_mntx Pipeline was successful
ci/woodpecker/push/gateway_tgsettle Pipeline was successful
ci/woodpecker/push/gateway_tron Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/gateway_chain Pipeline was successful
ci/woodpecker/push/payments_methods Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
ci/woodpecker/push/payments_quotation Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful

Reviewed-on: #598
This commit was merged in pull request #598.
This commit is contained in:
2026-03-02 15:28:26 +00:00
77 changed files with 803 additions and 764 deletions

View File

@@ -0,0 +1,33 @@
package sresponse
import (
"net/http"
"github.com/tech/sendico/pkg/api/http/response"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/model"
)
type callbackWriteResponse struct {
AccessToken TokenData `json:"accessToken"`
Callbacks []model.Callback `json:"callbacks"`
GeneratedSigningSecret string `json:"generatedSigningSecret,omitempty"`
}
func Callback(
logger mlogger.Logger,
callback *model.Callback,
accessToken *TokenData,
generatedSecret string,
created bool,
) http.HandlerFunc {
resp := callbackWriteResponse{
AccessToken: *accessToken,
Callbacks: []model.Callback{*callback},
GeneratedSigningSecret: generatedSecret,
}
if created {
return response.Created(logger, resp)
}
return response.Ok(logger, resp)
}

View File

@@ -42,9 +42,7 @@ func PrepareRefreshToken(
} }
token := &model.RefreshToken{ token := &model.RefreshToken{
AccountBoundBase: model.AccountBoundBase{ AccountRef: account.GetID(),
AccountRef: account.GetID(),
},
ClientRefreshToken: model.ClientRefreshToken{ ClientRefreshToken: model.ClientRefreshToken{
SessionIdentifier: *session, SessionIdentifier: *session,
RefreshToken: refreshToken, RefreshToken: refreshToken,

View File

@@ -0,0 +1,47 @@
package callbacksimp
import (
"context"
"encoding/json"
"net/http"
"github.com/tech/sendico/pkg/api/http/response"
"github.com/tech/sendico/pkg/model"
"github.com/tech/sendico/server/interface/api/sresponse"
mutil "github.com/tech/sendico/server/internal/mutil/param"
"go.uber.org/zap"
)
func (a *CallbacksAPI) create(r *http.Request, account *model.Account, accessToken *sresponse.TokenData) http.HandlerFunc {
organizationRef, err := a.Oph.GetRef(r)
if err != nil {
a.Logger.Warn("Failed to parse organization reference", zap.Error(err), mutil.PLog(a.Oph, r))
return response.BadReference(a.Logger, a.Name(), a.Oph.Name(), a.Oph.GetID(r), err)
}
var callback model.Callback
if err := json.NewDecoder(r.Body).Decode(&callback); err != nil {
a.Logger.Warn("Failed to decode callback payload", zap.Error(err))
return response.BadPayload(a.Logger, a.Name(), err)
}
mutation, err := a.normalizeAndPrepare(r.Context(), &callback, organizationRef, "", true)
if err != nil {
return response.Auto(a.Logger, a.Name(), err)
}
if _, err := a.tf.CreateTransaction().Execute(r.Context(), func(ctx context.Context) (any, error) {
if err := a.DB.Create(ctx, *account.GetID(), organizationRef, &callback); err != nil {
return nil, err
}
if err := a.applySigningSecretMutation(ctx, *account.GetID(), *callback.GetID(), mutation); err != nil {
return nil, err
}
return nil, nil
}); err != nil {
a.Logger.Warn("Failed to create callback transaction", zap.Error(err))
return response.Auto(a.Logger, a.Name(), err)
}
return a.callbackResponse(&callback, accessToken, mutation.Generated, true)
}

View File

@@ -2,7 +2,7 @@ package callbacksimp
import ( import (
"context" "context"
"encoding/json" "errors"
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
@@ -16,68 +16,10 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
type callbackWriteResponse struct { type signingSecretMutation struct {
AccessToken sresponse.TokenData `json:"accessToken"` SetSecretRef string
Callbacks []model.Callback `json:"callbacks"` Clear bool
GeneratedSigningSecret string `json:"generatedSigningSecret,omitempty"` Generated string
}
func (a *CallbacksAPI) create(r *http.Request, account *model.Account, accessToken *sresponse.TokenData) http.HandlerFunc {
organizationRef, err := a.Oph.GetRef(r)
if err != nil {
a.Logger.Warn("Failed to parse organization reference", zap.Error(err), mutil.PLog(a.Oph, r))
return response.BadReference(a.Logger, a.Name(), a.Oph.Name(), a.Oph.GetID(r), err)
}
var callback model.Callback
if err := json.NewDecoder(r.Body).Decode(&callback); err != nil {
a.Logger.Warn("Failed to decode callback payload", zap.Error(err))
return response.BadPayload(a.Logger, a.Name(), err)
}
generatedSecret, err := a.normalizeAndPrepare(r.Context(), &callback, organizationRef, true)
if err != nil {
return response.Auto(a.Logger, a.Name(), err)
}
if err := a.DB.Create(r.Context(), *account.GetID(), organizationRef, &callback); err != nil {
a.Logger.Warn("Failed to create callback", zap.Error(err))
return response.Auto(a.Logger, a.Name(), err)
}
return a.callbackResponse(&callback, accessToken, generatedSecret, true)
}
func (a *CallbacksAPI) update(r *http.Request, account *model.Account, accessToken *sresponse.TokenData) http.HandlerFunc {
var input model.Callback
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
a.Logger.Warn("Failed to decode callback payload", zap.Error(err))
return response.BadPayload(a.Logger, a.Name(), err)
}
callbackRef := *input.GetID()
if callbackRef.IsZero() {
return response.Auto(a.Logger, a.Name(), merrors.InvalidArgument("callback id is required", "id"))
}
var existing model.Callback
if err := a.db.Get(r.Context(), *account.GetID(), callbackRef, &existing); err != nil {
a.Logger.Warn("Failed to fetch callback before update", zap.Error(err))
return response.Auto(a.Logger, a.Name(), err)
}
mergeCallbackMutable(&existing, &input)
generatedSecret, err := a.normalizeAndPrepare(r.Context(), &existing, existing.OrganizationRef, true)
if err != nil {
return response.Auto(a.Logger, a.Name(), err)
}
if err := a.DB.Update(r.Context(), *account.GetID(), &existing); err != nil {
a.Logger.Warn("Failed to update callback", zap.Error(err))
return response.Auto(a.Logger, a.Name(), err)
}
return a.callbackResponse(&existing, accessToken, generatedSecret, false)
} }
func (a *CallbacksAPI) rotateSecret(r *http.Request, account *model.Account, accessToken *sresponse.TokenData) http.HandlerFunc { func (a *CallbacksAPI) rotateSecret(r *http.Request, account *model.Account, accessToken *sresponse.TokenData) http.HandlerFunc {
@@ -102,10 +44,8 @@ func (a *CallbacksAPI) rotateSecret(r *http.Request, account *model.Account, acc
a.Logger.Warn("Failed to rotate callback signing secret", zap.Error(err)) a.Logger.Warn("Failed to rotate callback signing secret", zap.Error(err))
return response.Auto(a.Logger, a.Name(), err) return response.Auto(a.Logger, a.Name(), err)
} }
callback.RetryPolicy.SecretRef = secretRef if err := a.db.SetSigningSecretRef(r.Context(), *account.GetID(), callbackRef, secretRef); err != nil {
a.Logger.Warn("Failed to persist rotated callback signing secret reference", zap.Error(err))
if err := a.DB.Update(r.Context(), *account.GetID(), &callback); err != nil {
a.Logger.Warn("Failed to persist rotated callback secret reference", zap.Error(err))
return response.Auto(a.Logger, a.Name(), err) return response.Auto(a.Logger, a.Name(), err)
} }
@@ -116,29 +56,25 @@ func (a *CallbacksAPI) normalizeAndPrepare(
ctx context.Context, ctx context.Context,
callback *model.Callback, callback *model.Callback,
organizationRef bson.ObjectID, organizationRef bson.ObjectID,
existingSecretRef string,
allowSecretGeneration bool, allowSecretGeneration bool,
) (string, error) { ) (signingSecretMutation, error) {
if callback == nil { if callback == nil {
return "", merrors.InvalidArgument("callback payload is required") return signingSecretMutation{}, merrors.InvalidArgument("callback payload is required")
} }
if organizationRef.IsZero() { if organizationRef.IsZero() {
return "", merrors.InvalidArgument("organization reference is required", "organizationRef") return signingSecretMutation{}, merrors.InvalidArgument("organization reference is required", "organizationRef")
} }
callback.SetOrganizationRef(organizationRef)
callback.Name = strings.TrimSpace(callback.Name) callback.Name = strings.TrimSpace(callback.Name)
callback.Description = trimDescription(callback.Description) callback.Description = trimDescription(callback.Description)
callback.ClientID = strings.TrimSpace(callback.ClientID)
if callback.ClientID == "" {
callback.ClientID = organizationRef.Hex()
}
callback.URL = strings.TrimSpace(callback.URL) callback.URL = strings.TrimSpace(callback.URL)
if callback.URL == "" { if callback.URL == "" {
return "", merrors.InvalidArgument("url is required", "url") return signingSecretMutation{}, merrors.InvalidArgument("url is required", "url")
} }
if err := validateCallbackURL(callback.URL); err != nil { if err := validateCallbackURL(callback.URL); err != nil {
return "", err return signingSecretMutation{}, err
} }
if callback.Name == "" { if callback.Name == "" {
callback.Name = callback.URL callback.Name = callback.URL
@@ -146,15 +82,15 @@ func (a *CallbacksAPI) normalizeAndPrepare(
status, err := normalizeStatus(callback.Status, a.config.DefaultStatus) status, err := normalizeStatus(callback.Status, a.config.DefaultStatus)
if err != nil { if err != nil {
return "", err return signingSecretMutation{}, err
} }
callback.Status = status callback.Status = status
callback.EventTypes = normalizeEventTypes(callback.EventTypes, a.config.DefaultEventTypes) callback.EventTypes = normalizeEventTypes(callback.EventTypes, a.config.DefaultEventTypes)
callback.RetryPolicy.MinDelayMS = defaultInt(callback.RetryPolicy.MinDelayMS, defaultRetryMinDelayMS) callback.RetryPolicy.Backoff.MinDelayMS = defaultInt(callback.RetryPolicy.Backoff.MinDelayMS, defaultRetryMinDelayMS)
callback.RetryPolicy.MaxDelayMS = defaultInt(callback.RetryPolicy.MaxDelayMS, defaultRetryMaxDelayMS) callback.RetryPolicy.Backoff.MaxDelayMS = defaultInt(callback.RetryPolicy.Backoff.MaxDelayMS, defaultRetryMaxDelayMS)
if callback.RetryPolicy.MaxDelayMS < callback.RetryPolicy.MinDelayMS { if callback.RetryPolicy.Backoff.MaxDelayMS < callback.RetryPolicy.Backoff.MinDelayMS {
callback.RetryPolicy.MaxDelayMS = callback.RetryPolicy.MinDelayMS callback.RetryPolicy.Backoff.MaxDelayMS = callback.RetryPolicy.Backoff.MinDelayMS
} }
callback.RetryPolicy.MaxAttempts = defaultInt(callback.RetryPolicy.MaxAttempts, defaultRetryMaxAttempts) callback.RetryPolicy.MaxAttempts = defaultInt(callback.RetryPolicy.MaxAttempts, defaultRetryMaxAttempts)
callback.RetryPolicy.RequestTimeoutMS = defaultInt(callback.RetryPolicy.RequestTimeoutMS, defaultRetryRequestTimeoutMS) callback.RetryPolicy.RequestTimeoutMS = defaultInt(callback.RetryPolicy.RequestTimeoutMS, defaultRetryRequestTimeoutMS)
@@ -162,36 +98,55 @@ func (a *CallbacksAPI) normalizeAndPrepare(
mode, err := normalizeSigningMode(callback.RetryPolicy.SigningMode) mode, err := normalizeSigningMode(callback.RetryPolicy.SigningMode)
if err != nil { if err != nil {
return "", err return signingSecretMutation{}, err
} }
callback.RetryPolicy.SigningMode = mode callback.RetryPolicy.SigningMode = mode
callback.RetryPolicy.SecretRef = strings.TrimSpace(callback.RetryPolicy.SecretRef) existingSecretRef = strings.TrimSpace(existingSecretRef)
switch callback.RetryPolicy.SigningMode { switch callback.RetryPolicy.SigningMode {
case model.CallbackSigningModeNone: case model.CallbackSigningModeNone:
callback.RetryPolicy.SecretRef = "" return signingSecretMutation{Clear: existingSecretRef != ""}, nil
return "", nil
case model.CallbackSigningModeHMACSHA256: case model.CallbackSigningModeHMACSHA256:
if callback.RetryPolicy.SecretRef != "" { if existingSecretRef != "" {
return "", nil return signingSecretMutation{SetSecretRef: existingSecretRef}, nil
} }
if !allowSecretGeneration { if !allowSecretGeneration {
return "", merrors.InvalidArgument("secretRef is required for hmac_sha256 callbacks", "retryPolicy.secretRef") return signingSecretMutation{}, merrors.InvalidArgument("signing secret is required for hmac_sha256 callbacks", "retryPolicy.signingMode")
} }
if callback.GetID().IsZero() { if callback.GetID().IsZero() {
callback.SetID(bson.NewObjectID()) callback.SetID(bson.NewObjectID())
} }
secretRef, generatedSecret, err := a.secrets.Provision(ctx, organizationRef, *callback.GetID()) secretRef, generatedSecret, err := a.secrets.Provision(ctx, organizationRef, *callback.GetID())
if err != nil { if err != nil {
return "", err return signingSecretMutation{}, err
} }
callback.RetryPolicy.SecretRef = secretRef return signingSecretMutation{SetSecretRef: secretRef, Generated: generatedSecret}, nil
return generatedSecret, nil
default: default:
return "", merrors.InvalidArgument("unsupported signing mode", "retryPolicy.signingMode") return signingSecretMutation{}, merrors.InvalidArgument("unsupported signing mode", "retryPolicy.signingMode")
} }
} }
func (a *CallbacksAPI) applySigningSecretMutation(
ctx context.Context,
accountRef,
callbackRef bson.ObjectID,
mutation signingSecretMutation,
) error {
if callbackRef.IsZero() {
return merrors.InvalidArgument("callback reference is required", "callbackRef")
}
if strings.TrimSpace(mutation.SetSecretRef) != "" {
return a.db.SetSigningSecretRef(ctx, accountRef, callbackRef, mutation.SetSecretRef)
}
if mutation.Clear {
err := a.db.ClearSigningSecretRef(ctx, accountRef, callbackRef)
if err != nil && !errors.Is(err, merrors.ErrNoData) {
return err
}
}
return nil
}
func (a *CallbacksAPI) callbackResponse( func (a *CallbacksAPI) callbackResponse(
callback *model.Callback, callback *model.Callback,
accessToken *sresponse.TokenData, accessToken *sresponse.TokenData,
@@ -202,15 +157,7 @@ func (a *CallbacksAPI) callbackResponse(
return response.Internal(a.Logger, a.Name(), merrors.Internal("failed to build callback response")) return response.Internal(a.Logger, a.Name(), merrors.Internal("failed to build callback response"))
} }
resp := callbackWriteResponse{ return sresponse.Callback(a.Logger, callback, accessToken, generatedSecret, created)
AccessToken: *accessToken,
Callbacks: []model.Callback{*callback},
GeneratedSigningSecret: generatedSecret,
}
if created {
return response.Created(a.Logger, resp)
}
return response.Ok(a.Logger, resp)
} }
func normalizeStatus(raw, fallback model.CallbackStatus) (model.CallbackStatus, error) { func normalizeStatus(raw, fallback model.CallbackStatus) (model.CallbackStatus, error) {
@@ -286,16 +233,17 @@ func normalizeHeaders(headers map[string]string) map[string]string {
} }
func mergeCallbackMutable(dst, src *model.Callback) { func mergeCallbackMutable(dst, src *model.Callback) {
dst.OrganizationRef = src.OrganizationRef
dst.Describable = src.Describable dst.Describable = src.Describable
dst.ClientID = src.ClientID
dst.Status = src.Status dst.Status = src.Status
dst.URL = src.URL dst.URL = src.URL
dst.EventTypes = append([]string(nil), src.EventTypes...) dst.EventTypes = append([]string(nil), src.EventTypes...)
dst.RetryPolicy = model.CallbackRetryPolicy{ dst.RetryPolicy = model.CallbackRetryPolicy{
MinDelayMS: src.RetryPolicy.MinDelayMS, Backoff: model.CallbackBackoff{
MaxDelayMS: src.RetryPolicy.MaxDelayMS, MinDelayMS: src.RetryPolicy.Backoff.MinDelayMS,
MaxDelayMS: src.RetryPolicy.Backoff.MaxDelayMS,
},
SigningMode: src.RetryPolicy.SigningMode, SigningMode: src.RetryPolicy.SigningMode,
SecretRef: src.RetryPolicy.SecretRef,
Headers: normalizeHeaders(src.RetryPolicy.Headers), Headers: normalizeHeaders(src.RetryPolicy.Headers),
MaxAttempts: src.RetryPolicy.MaxAttempts, MaxAttempts: src.RetryPolicy.MaxAttempts,
RequestTimeoutMS: src.RetryPolicy.RequestTimeoutMS, RequestTimeoutMS: src.RetryPolicy.RequestTimeoutMS,

View File

@@ -81,7 +81,7 @@ func newSigningSecretManager(logger mlogger.Logger, cfg callbacksConfig) (signin
} }
if isVaultConfigEmpty(cfg.Vault) { if isVaultConfigEmpty(cfg.Vault) {
manager.logger.Warn("Callbacks Vault config is not set; secret generation requires explicit secretRef in payloads") manager.logger.Warn("Callbacks Vault config is not set; hmac signing secret generation is disabled")
ensureSigningSecretMetrics() ensureSigningSecretMetrics()
return manager, nil return manager, nil
} }

View File

@@ -5,6 +5,7 @@ import (
api "github.com/tech/sendico/pkg/api/http" api "github.com/tech/sendico/pkg/api/http"
"github.com/tech/sendico/pkg/db/callbacks" "github.com/tech/sendico/pkg/db/callbacks"
"github.com/tech/sendico/pkg/db/transaction"
"github.com/tech/sendico/pkg/merrors" "github.com/tech/sendico/pkg/merrors"
"github.com/tech/sendico/pkg/model" "github.com/tech/sendico/pkg/model"
"github.com/tech/sendico/pkg/mservice" "github.com/tech/sendico/pkg/mservice"
@@ -16,6 +17,7 @@ import (
type CallbacksAPI struct { type CallbacksAPI struct {
papitemplate.ProtectedAPI[model.Callback] papitemplate.ProtectedAPI[model.Callback]
db callbacks.DB db callbacks.DB
tf transaction.Factory
secrets signingSecretManager secrets signingSecretManager
config callbacksConfig config callbacksConfig
} }
@@ -35,6 +37,7 @@ func CreateAPI(apiCtx eapi.API) (*CallbacksAPI, error) {
res := &CallbacksAPI{ res := &CallbacksAPI{
config: newCallbacksConfig(apiCtx.Config().Callbacks), config: newCallbacksConfig(apiCtx.Config().Callbacks),
tf: apiCtx.DBFactory().TransactionFactory(),
} }
p, err := papitemplate.CreateAPI(apiCtx, dbFactory, mservice.Organizations, mservice.Callbacks) p, err := papitemplate.CreateAPI(apiCtx, dbFactory, mservice.Organizations, mservice.Callbacks)

View File

@@ -0,0 +1,59 @@
package callbacksimp
import (
"context"
"encoding/json"
"errors"
"net/http"
"github.com/tech/sendico/pkg/api/http/response"
"github.com/tech/sendico/pkg/merrors"
"github.com/tech/sendico/pkg/model"
"github.com/tech/sendico/server/interface/api/sresponse"
"go.uber.org/zap"
)
func (a *CallbacksAPI) update(r *http.Request, account *model.Account, accessToken *sresponse.TokenData) http.HandlerFunc {
var input model.Callback
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
a.Logger.Warn("Failed to decode callback payload", zap.Error(err))
return response.BadPayload(a.Logger, a.Name(), err)
}
callbackRef := *input.GetID()
if callbackRef.IsZero() {
return response.Auto(a.Logger, a.Name(), merrors.InvalidArgument("callback ref is required", "id"))
}
var existing model.Callback
if err := a.db.Get(r.Context(), *account.GetID(), callbackRef, &existing); err != nil {
a.Logger.Warn("Failed to fetch callback before update", zap.Error(err))
return response.Auto(a.Logger, a.Name(), err)
}
existingSecretRef, err := a.db.GetSigningSecretRef(r.Context(), *account.GetID(), callbackRef)
if err != nil && !errors.Is(err, merrors.ErrNoData) {
a.Logger.Warn("Failed to fetch callback signing secret metadata", zap.Error(err))
return response.Auto(a.Logger, a.Name(), err)
}
mergeCallbackMutable(&existing, &input)
mutation, err := a.normalizeAndPrepare(r.Context(), &existing, existing.OrganizationRef, existingSecretRef, true)
if err != nil {
return response.Auto(a.Logger, a.Name(), err)
}
if _, err := a.tf.CreateTransaction().Execute(r.Context(), func(ctx context.Context) (any, error) {
if err := a.DB.Update(ctx, *account.GetID(), &existing); err != nil {
return nil, err
}
if err := a.applySigningSecretMutation(ctx, *account.GetID(), callbackRef, mutation); err != nil {
return nil, err
}
return nil, nil
}); err != nil {
a.Logger.Warn("Failed to update callback transaction", zap.Error(err))
return response.Auto(a.Logger, a.Name(), err)
}
return a.callbackResponse(&existing, accessToken, mutation.Generated, false)
}

View File

@@ -10,6 +10,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/tech/sendico/edge/callbacks/internal/model"
"github.com/tech/sendico/edge/callbacks/internal/signing" "github.com/tech/sendico/edge/callbacks/internal/signing"
"github.com/tech/sendico/edge/callbacks/internal/storage" "github.com/tech/sendico/edge/callbacks/internal/storage"
"github.com/tech/sendico/pkg/merrors" "github.com/tech/sendico/pkg/merrors"
@@ -154,7 +155,7 @@ func (s *service) runWorker(ctx context.Context, workerID string) {
} }
} }
func (s *service) handleTask(ctx context.Context, workerID string, task *storage.Task) { func (s *service) handleTask(ctx context.Context, workerID string, task *model.Task) {
started := time.Now() started := time.Now()
statusCode := 0 statusCode := 0
result := "failed" result := "failed"

View File

@@ -4,16 +4,18 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"time" "time"
"go.mongodb.org/mongo-driver/v2/bson"
) )
// Envelope is the canonical incoming event envelope. // Envelope is the canonical incoming event envelope.
type Envelope struct { type Envelope struct {
EventID string `json:"event_id"` EventID string `json:"event_id"`
Type string `json:"type"` Type string `json:"type"`
ClientID string `json:"client_id"` OrganizationRef bson.ObjectID `json:"organization_ref"`
OccurredAt time.Time `json:"occurred_at"` OccurredAt time.Time `json:"occurred_at"`
PublishedAt time.Time `json:"published_at,omitempty"` PublishedAt time.Time `json:"published_at,omitempty"`
Data json.RawMessage `json:"data"` Data json.RawMessage `json:"data"`
} }
// Service parses incoming messages and builds outbound payload bytes. // Service parses incoming messages and builds outbound payload bytes.
@@ -24,10 +26,10 @@ type Service interface {
// Payload is the stable outbound JSON body. // Payload is the stable outbound JSON body.
type Payload struct { type Payload struct {
EventID string `json:"event_id"` EventID string `json:"event_id"`
Type string `json:"type"` Type string `json:"type"`
ClientID string `json:"client_id"` OrganizationRef bson.ObjectID `json:"organization_ref"`
OccurredAt string `json:"occurred_at"` OccurredAt string `json:"occurred_at"`
PublishedAt string `json:"published_at,omitempty"` PublishedAt string `json:"published_at,omitempty"`
Data json.RawMessage `json:"data"` Data json.RawMessage `json:"data"`
} }

View File

@@ -8,6 +8,7 @@ import (
"github.com/tech/sendico/pkg/merrors" "github.com/tech/sendico/pkg/merrors"
"github.com/tech/sendico/pkg/mlogger" "github.com/tech/sendico/pkg/mlogger"
"go.mongodb.org/mongo-driver/v2/bson"
"go.uber.org/zap" "go.uber.org/zap"
) )
@@ -39,8 +40,8 @@ func (s *parserService) Parse(data []byte) (*Envelope, error) {
if strings.TrimSpace(envelope.Type) == "" { if strings.TrimSpace(envelope.Type) == "" {
return nil, merrors.InvalidArgument("type is required", "type") return nil, merrors.InvalidArgument("type is required", "type")
} }
if strings.TrimSpace(envelope.ClientID) == "" { if envelope.OrganizationRef == bson.NilObjectID {
return nil, merrors.InvalidArgument("client_id is required", "client_id") return nil, merrors.InvalidArgument("organization_ref is required", "organization_ref")
} }
if envelope.OccurredAt.IsZero() { if envelope.OccurredAt.IsZero() {
return nil, merrors.InvalidArgument("occurred_at is required", "occurred_at") return nil, merrors.InvalidArgument("occurred_at is required", "occurred_at")
@@ -51,7 +52,6 @@ func (s *parserService) Parse(data []byte) (*Envelope, error) {
envelope.EventID = strings.TrimSpace(envelope.EventID) envelope.EventID = strings.TrimSpace(envelope.EventID)
envelope.Type = strings.TrimSpace(envelope.Type) envelope.Type = strings.TrimSpace(envelope.Type)
envelope.ClientID = strings.TrimSpace(envelope.ClientID)
envelope.OccurredAt = envelope.OccurredAt.UTC() envelope.OccurredAt = envelope.OccurredAt.UTC()
if !envelope.PublishedAt.IsZero() { if !envelope.PublishedAt.IsZero() {
envelope.PublishedAt = envelope.PublishedAt.UTC() envelope.PublishedAt = envelope.PublishedAt.UTC()
@@ -66,11 +66,11 @@ func (s *parserService) BuildPayload(_ context.Context, envelope *Envelope) ([]b
} }
payload := Payload{ payload := Payload{
EventID: envelope.EventID, EventID: envelope.EventID,
Type: envelope.Type, Type: envelope.Type,
ClientID: envelope.ClientID, OrganizationRef: envelope.OrganizationRef,
OccurredAt: envelope.OccurredAt.UTC().Format(time.RFC3339Nano), OccurredAt: envelope.OccurredAt.UTC().Format(time.RFC3339Nano),
Data: envelope.Data, Data: envelope.Data,
} }
if !envelope.PublishedAt.IsZero() { if !envelope.PublishedAt.IsZero() {
payload.PublishedAt = envelope.PublishedAt.UTC().Format(time.RFC3339Nano) payload.PublishedAt = envelope.PublishedAt.UTC().Format(time.RFC3339Nano)

View File

@@ -17,6 +17,7 @@ import (
np "github.com/tech/sendico/pkg/messaging/notifications/processor" np "github.com/tech/sendico/pkg/messaging/notifications/processor"
"github.com/tech/sendico/pkg/mlogger" "github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/model" "github.com/tech/sendico/pkg/model"
"go.mongodb.org/mongo-driver/v2/bson"
"go.uber.org/zap" "go.uber.org/zap"
) )
@@ -178,7 +179,7 @@ func (s *service) handlePaymentStatusUpdated(ctx context.Context, msg *model.Pay
result = ingestResultEmptyPayload result = ingestResultEmptyPayload
return nil return nil
} }
if strings.TrimSpace(msg.EventID) == "" || strings.TrimSpace(msg.ClientID) == "" || msg.OccurredAt.IsZero() { if strings.TrimSpace(msg.EventID) == "" || msg.Data.OrganizationRef == bson.NilObjectID || msg.OccurredAt.IsZero() {
result = ingestResultInvalidEvent result = ingestResultInvalidEvent
return nil return nil
} }
@@ -195,15 +196,15 @@ func (s *service) handlePaymentStatusUpdated(ctx context.Context, msg *model.Pay
} }
parsed := &events.Envelope{ parsed := &events.Envelope{
EventID: strings.TrimSpace(msg.EventID), EventID: strings.TrimSpace(msg.EventID),
Type: eventType, Type: eventType,
ClientID: strings.TrimSpace(msg.ClientID), OrganizationRef: msg.Data.OrganizationRef,
OccurredAt: msg.OccurredAt.UTC(), OccurredAt: msg.OccurredAt.UTC(),
PublishedAt: msg.PublishedAt.UTC(), PublishedAt: msg.PublishedAt.UTC(),
Data: data, Data: data,
} }
inserted, err := s.deps.InboxRepo.TryInsert(ctx, parsed.EventID, parsed.ClientID, parsed.Type, time.Now().UTC()) inserted, err := s.deps.InboxRepo.TryInsert(ctx, parsed.EventID, parsed.Type, parsed.OrganizationRef, time.Now().UTC())
if err != nil { if err != nil {
result = ingestResultInboxError result = ingestResultInboxError
return err return err
@@ -213,7 +214,7 @@ func (s *service) handlePaymentStatusUpdated(ctx context.Context, msg *model.Pay
return nil return nil
} }
endpoints, err := s.deps.Resolver.Resolve(ctx, parsed.ClientID, parsed.Type) endpoints, err := s.deps.Resolver.Resolve(ctx, parsed.Type, parsed.OrganizationRef)
if err != nil { if err != nil {
result = ingestResultResolveError result = ingestResultResolveError
return err return err

View File

@@ -0,0 +1,8 @@
package model
import pmodel "github.com/tech/sendico/pkg/model"
type CallbackInternal struct {
pmodel.Callback `bson:",inline" json:",inline"`
SecretRef string `bson:"secretRef"`
}

View File

@@ -0,0 +1,22 @@
package model
import (
"time"
"github.com/tech/sendico/pkg/db/storable"
pmodel "github.com/tech/sendico/pkg/model"
)
// Endpoint describes one target callback endpoint.
type Endpoint struct {
storable.Base
pmodel.OrganizationBoundBase
URL string
SigningMode string
SecretRef string
Headers map[string]string
MaxAttempts int
MinDelay time.Duration
MaxDelay time.Duration
RequestTimeout time.Duration
}

View File

@@ -0,0 +1,37 @@
package model
import (
"time"
"github.com/tech/sendico/pkg/db/storable"
"go.mongodb.org/mongo-driver/v2/bson"
)
// TaskStatus tracks delivery task lifecycle.
type TaskStatus string
const (
TaskStatusPending TaskStatus = "PENDING"
TaskStatusRetry TaskStatus = "RETRY"
TaskStatusDelivered TaskStatus = "DELIVERED"
TaskStatusFailed TaskStatus = "FAILED"
)
// Task is one callback delivery job.
type Task struct {
storable.Base
EventID string
EndpointRef bson.ObjectID
EndpointURL string
SigningMode string
SecretRef string
Headers map[string]string
Payload []byte
Attempt int
MaxAttempts int
MinDelay time.Duration
MaxDelay time.Duration
RequestTimeout time.Duration
Status TaskStatus
NextAttemptAt time.Time
}

View File

@@ -4,54 +4,12 @@ import (
"context" "context"
"time" "time"
"github.com/tech/sendico/edge/callbacks/internal/model"
"github.com/tech/sendico/pkg/db" "github.com/tech/sendico/pkg/db"
"github.com/tech/sendico/pkg/mlogger" "github.com/tech/sendico/pkg/mlogger"
"go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/bson"
) )
// TaskStatus tracks delivery task lifecycle.
type TaskStatus string
const (
TaskStatusPending TaskStatus = "PENDING"
TaskStatusRetry TaskStatus = "RETRY"
TaskStatusDelivered TaskStatus = "DELIVERED"
TaskStatusFailed TaskStatus = "FAILED"
)
// Endpoint describes one target callback endpoint.
type Endpoint struct {
ID bson.ObjectID
ClientID string
URL string
SigningMode string
SecretRef string
Headers map[string]string
MaxAttempts int
MinDelay time.Duration
MaxDelay time.Duration
RequestTimeout time.Duration
}
// Task is one callback delivery job.
type Task struct {
ID bson.ObjectID
EventID string
EndpointID bson.ObjectID
EndpointURL string
SigningMode string
SecretRef string
Headers map[string]string
Payload []byte
Attempt int
MaxAttempts int
MinDelay time.Duration
MaxDelay time.Duration
RequestTimeout time.Duration
Status TaskStatus
NextAttemptAt time.Time
}
// TaskDefaults are applied when creating tasks. // TaskDefaults are applied when creating tasks.
type TaskDefaults struct { type TaskDefaults struct {
MaxAttempts int MaxAttempts int
@@ -69,18 +27,18 @@ type Options struct {
// InboxRepo controls event dedupe state. // InboxRepo controls event dedupe state.
type InboxRepo interface { type InboxRepo interface {
TryInsert(ctx context.Context, eventID, clientID, eventType string, at time.Time) (bool, error) TryInsert(ctx context.Context, eventID, ceventType string, organizationRef bson.ObjectID, at time.Time) (bool, error)
} }
// EndpointRepo resolves endpoints for events. // EndpointRepo resolves endpoints for events.
type EndpointRepo interface { type EndpointRepo interface {
FindActiveByClientAndType(ctx context.Context, clientID, eventType string) ([]Endpoint, error) FindActive(ctx context.Context, eventType string, organizationRef bson.ObjectID) ([]model.Endpoint, error)
} }
// TaskRepo manages callback tasks. // TaskRepo manages callback tasks.
type TaskRepo interface { type TaskRepo interface {
UpsertTasks(ctx context.Context, eventID string, endpoints []Endpoint, payload []byte, defaults TaskDefaults, at time.Time) error UpsertTasks(ctx context.Context, eventID string, endpoints []model.Endpoint, payload []byte, defaults TaskDefaults, at time.Time) error
LockNextTask(ctx context.Context, now time.Time, workerID string, lockTTL time.Duration) (*Task, error) LockNextTask(ctx context.Context, now time.Time, workerID string, lockTTL time.Duration) (*model.Task, error)
MarkDelivered(ctx context.Context, taskID bson.ObjectID, httpCode int, latency time.Duration, at time.Time) error MarkDelivered(ctx context.Context, taskID bson.ObjectID, httpCode int, latency time.Duration, at time.Time) error
MarkRetry(ctx context.Context, taskID bson.ObjectID, attempt int, nextAttemptAt time.Time, lastError string, httpCode int, at time.Time) error MarkRetry(ctx context.Context, taskID bson.ObjectID, attempt int, nextAttemptAt time.Time, lastError string, httpCode int, at time.Time) error
MarkFailed(ctx context.Context, taskID bson.ObjectID, attempt int, lastError string, httpCode int, at time.Time) error MarkFailed(ctx context.Context, taskID bson.ObjectID, attempt int, lastError string, httpCode int, at time.Time) error

View File

@@ -6,6 +6,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/tech/sendico/edge/callbacks/internal/model"
"github.com/tech/sendico/pkg/db" "github.com/tech/sendico/pkg/db"
"github.com/tech/sendico/pkg/db/repository" "github.com/tech/sendico/pkg/db/repository"
"github.com/tech/sendico/pkg/db/repository/builder" "github.com/tech/sendico/pkg/db/repository/builder"
@@ -13,6 +14,7 @@ import (
"github.com/tech/sendico/pkg/db/storable" "github.com/tech/sendico/pkg/db/storable"
"github.com/tech/sendico/pkg/merrors" "github.com/tech/sendico/pkg/merrors"
"github.com/tech/sendico/pkg/mlogger" "github.com/tech/sendico/pkg/mlogger"
pmodel "github.com/tech/sendico/pkg/model"
"github.com/tech/sendico/pkg/mservice" "github.com/tech/sendico/pkg/mservice"
mutil "github.com/tech/sendico/pkg/mutil/db" mutil "github.com/tech/sendico/pkg/mutil/db"
"go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/bson"
@@ -39,10 +41,10 @@ type mongoRepository struct {
} }
type inboxDoc struct { type inboxDoc struct {
storable.Base `bson:",inline"` storable.Base `bson:",inline"`
EventID string `bson:"event_id"` pmodel.OrganizationBoundBase `bson:",inline"`
ClientID string `bson:"client_id"` EventID string `bson:"event_id"`
EventType string `bson:"event_type"` EventType string `bson:"event_type"`
} }
func (d *inboxDoc) Collection() string { func (d *inboxDoc) Collection() string {
@@ -64,12 +66,12 @@ type deliveryPolicy struct {
} }
type endpointDoc struct { type endpointDoc struct {
storable.Base `bson:",inline"` storable.Base `bson:",inline"`
deliveryPolicy `bson:"retry_policy"` pmodel.OrganizationBoundBase `bson:",inline"`
ClientID string `bson:"client_id"` deliveryPolicy `bson:"retry_policy"`
Status string `bson:"status"` Status string `bson:"status"`
URL string `bson:"url"` URL string `bson:"url"`
EventTypes []string `bson:"event_types"` EventTypes []string `bson:"event_types"`
} }
func (d *endpointDoc) Collection() string { func (d *endpointDoc) Collection() string {
@@ -79,18 +81,18 @@ func (d *endpointDoc) Collection() string {
type taskDoc struct { type taskDoc struct {
storable.Base `bson:",inline"` storable.Base `bson:",inline"`
deliveryPolicy `bson:"retry_policy"` deliveryPolicy `bson:"retry_policy"`
EventID string `bson:"event_id"` EventID string `bson:"event_id"`
EndpointID bson.ObjectID `bson:"endpoint_id"` EndpointRef bson.ObjectID `bson:"endpoint_ref"`
EndpointURL string `bson:"endpoint_url"` EndpointURL string `bson:"endpoint_url"`
Payload []byte `bson:"payload"` Payload []byte `bson:"payload"`
Status TaskStatus `bson:"status"` Status model.TaskStatus `bson:"status"`
Attempt int `bson:"attempt"` Attempt int `bson:"attempt"`
LastError string `bson:"last_error,omitempty"` LastError string `bson:"last_error,omitempty"`
LastHTTPCode int `bson:"last_http_code,omitempty"` LastHTTPCode int `bson:"last_http_code,omitempty"`
NextAttemptAt time.Time `bson:"next_attempt_at"` NextAttemptAt time.Time `bson:"next_attempt_at"`
LockedUntil *time.Time `bson:"locked_until,omitempty"` LockedUntil *time.Time `bson:"locked_until,omitempty"`
WorkerID string `bson:"worker_id,omitempty"` WorkerID string `bson:"worker_id,omitempty"`
DeliveredAt *time.Time `bson:"delivered_at,omitempty"` DeliveredAt *time.Time `bson:"delivered_at,omitempty"`
} }
func (d *taskDoc) Collection() string { func (d *taskDoc) Collection() string {
@@ -152,7 +154,7 @@ func (m *mongoRepository) ensureIndexes() error {
Unique: true, Unique: true,
Keys: []ri.Key{ Keys: []ri.Key{
{Field: "event_id", Sort: ri.Asc}, {Field: "event_id", Sort: ri.Asc},
{Field: "endpoint_id", Sort: ri.Asc}, {Field: "endpoint_ref", Sort: ri.Asc},
}, },
}, },
{ {
@@ -172,7 +174,7 @@ func (m *mongoRepository) ensureIndexes() error {
if err := m.endpointsRepo.CreateIndex(&ri.Definition{ if err := m.endpointsRepo.CreateIndex(&ri.Definition{
Name: "idx_client_event", Name: "idx_client_event",
Keys: []ri.Key{ Keys: []ri.Key{
{Field: "client_id", Sort: ri.Asc}, {Field: "organization_ref", Sort: ri.Asc},
{Field: "status", Sort: ri.Asc}, {Field: "status", Sort: ri.Asc},
{Field: "event_types", Sort: ri.Asc}, {Field: "event_types", Sort: ri.Asc},
}, },
@@ -188,11 +190,11 @@ type inboxStore struct {
repo repository.Repository repo repository.Repository
} }
func (r *inboxStore) TryInsert(ctx context.Context, eventID, clientID, eventType string, at time.Time) (bool, error) { func (r *inboxStore) TryInsert(ctx context.Context, eventID, eventType string, organizationRef bson.ObjectID, at time.Time) (bool, error) {
doc := &inboxDoc{ doc := &inboxDoc{
EventID: strings.TrimSpace(eventID), OrganizationBoundBase: pmodel.OrganizationBoundBase{OrganizationRef: organizationRef},
ClientID: strings.TrimSpace(clientID), EventID: strings.TrimSpace(eventID),
EventType: strings.TrimSpace(eventType), EventType: strings.TrimSpace(eventType),
} }
filter := repository.Filter("event_id", doc.EventID) filter := repository.Filter("event_id", doc.EventID)
@@ -212,21 +214,20 @@ type endpointStore struct {
repo repository.Repository repo repository.Repository
} }
func (r *endpointStore) FindActiveByClientAndType(ctx context.Context, clientID, eventType string) ([]Endpoint, error) { func (r *endpointStore) FindActive(ctx context.Context, eventType string, organizationRef bson.ObjectID) ([]model.Endpoint, error) {
clientID = strings.TrimSpace(clientID)
eventType = strings.TrimSpace(eventType) eventType = strings.TrimSpace(eventType)
if clientID == "" { if organizationRef == bson.NilObjectID {
return nil, merrors.InvalidArgument("client_id is required", "client_id") return nil, merrors.InvalidArgument("organization_ref is required", "organization_ref")
} }
if eventType == "" { if eventType == "" {
return nil, merrors.InvalidArgument("event type is required", "event_type") return nil, merrors.InvalidArgument("event type is required", "event_type")
} }
query := repository.Query(). query := repository.Query().
Filter(repository.Field("client_id"), clientID). Filter(repository.OrgField(), organizationRef).
In(repository.Field("status"), "active", "enabled") In(repository.Field("status"), "active", "enabled")
out := make([]Endpoint, 0) out := make([]model.Endpoint, 0)
err := r.repo.FindManyByFilter(ctx, query, func(cur *mongo.Cursor) error { err := r.repo.FindManyByFilter(ctx, query, func(cur *mongo.Cursor) error {
doc := &endpointDoc{} doc := &endpointDoc{}
if err := cur.Decode(doc); err != nil { if err := cur.Decode(doc); err != nil {
@@ -238,17 +239,17 @@ func (r *endpointStore) FindActiveByClientAndType(ctx context.Context, clientID,
if !supportsEventType(doc.EventTypes, eventType) { if !supportsEventType(doc.EventTypes, eventType) {
return nil return nil
} }
out = append(out, Endpoint{ out = append(out, model.Endpoint{
ID: doc.ID, Base: doc.Base,
ClientID: doc.ClientID, OrganizationBoundBase: doc.OrganizationBoundBase,
URL: strings.TrimSpace(doc.URL), URL: strings.TrimSpace(doc.URL),
SigningMode: strings.TrimSpace(doc.SigningMode), SigningMode: strings.TrimSpace(doc.SigningMode),
SecretRef: strings.TrimSpace(doc.SecretRef), SecretRef: strings.TrimSpace(doc.SecretRef),
Headers: cloneHeaders(doc.Headers), Headers: cloneHeaders(doc.Headers),
MaxAttempts: doc.MaxAttempts, MaxAttempts: doc.MaxAttempts,
MinDelay: time.Duration(doc.MinDelayMS) * time.Millisecond, MinDelay: time.Duration(doc.MinDelayMS) * time.Millisecond,
MaxDelay: time.Duration(doc.MaxDelayMS) * time.Millisecond, MaxDelay: time.Duration(doc.MaxDelayMS) * time.Millisecond,
RequestTimeout: time.Duration(doc.RequestTimeoutMS) * time.Millisecond, RequestTimeout: time.Duration(doc.RequestTimeoutMS) * time.Millisecond,
}) })
return nil return nil
}) })
@@ -281,7 +282,7 @@ type taskStore struct {
repo repository.Repository repo repository.Repository
} }
func (r *taskStore) UpsertTasks(ctx context.Context, eventID string, endpoints []Endpoint, payload []byte, defaults TaskDefaults, at time.Time) error { func (r *taskStore) UpsertTasks(ctx context.Context, eventID string, endpoints []model.Endpoint, payload []byte, defaults TaskDefaults, at time.Time) error {
eventID = strings.TrimSpace(eventID) eventID = strings.TrimSpace(eventID)
if eventID == "" { if eventID == "" {
return merrors.InvalidArgument("event id is required", "event_id") return merrors.InvalidArgument("event id is required", "event_id")
@@ -292,7 +293,7 @@ func (r *taskStore) UpsertTasks(ctx context.Context, eventID string, endpoints [
now := at.UTC() now := at.UTC()
for _, endpoint := range endpoints { for _, endpoint := range endpoints {
if endpoint.ID == bson.NilObjectID { if endpoint.GetID() == nil || *endpoint.GetID() == bson.NilObjectID {
continue continue
} }
@@ -327,13 +328,13 @@ func (r *taskStore) UpsertTasks(ctx context.Context, eventID string, endpoints [
doc := &taskDoc{} doc := &taskDoc{}
doc.EventID = eventID doc.EventID = eventID
doc.EndpointID = endpoint.ID doc.EndpointRef = *endpoint.GetID()
doc.EndpointURL = strings.TrimSpace(endpoint.URL) doc.EndpointURL = strings.TrimSpace(endpoint.URL)
doc.SigningMode = strings.TrimSpace(endpoint.SigningMode) doc.SigningMode = strings.TrimSpace(endpoint.SigningMode)
doc.SecretRef = strings.TrimSpace(endpoint.SecretRef) doc.SecretRef = strings.TrimSpace(endpoint.SecretRef)
doc.Headers = cloneHeaders(endpoint.Headers) doc.Headers = cloneHeaders(endpoint.Headers)
doc.Payload = append([]byte(nil), payload...) doc.Payload = append([]byte(nil), payload...)
doc.Status = TaskStatusPending doc.Status = model.TaskStatusPending
doc.Attempt = 0 doc.Attempt = 0
doc.MaxAttempts = maxAttempts doc.MaxAttempts = maxAttempts
doc.MinDelayMS = int(minDelay / time.Millisecond) doc.MinDelayMS = int(minDelay / time.Millisecond)
@@ -341,7 +342,7 @@ func (r *taskStore) UpsertTasks(ctx context.Context, eventID string, endpoints [
doc.RequestTimeoutMS = int(requestTimeout / time.Millisecond) doc.RequestTimeoutMS = int(requestTimeout / time.Millisecond)
doc.NextAttemptAt = now doc.NextAttemptAt = now
filter := repository.Filter("event_id", eventID).And(repository.Filter("endpoint_id", endpoint.ID)) filter := repository.Filter("event_id", eventID).And(repository.Filter("endpoint_ref", endpoint.ID))
if err := r.repo.Insert(ctx, doc, filter); err != nil { if err := r.repo.Insert(ctx, doc, filter); err != nil {
if errors.Is(err, merrors.ErrDataConflict) { if errors.Is(err, merrors.ErrDataConflict) {
continue continue
@@ -353,7 +354,7 @@ func (r *taskStore) UpsertTasks(ctx context.Context, eventID string, endpoints [
return nil return nil
} }
func (r *taskStore) LockNextTask(ctx context.Context, now time.Time, workerID string, lockTTL time.Duration) (*Task, error) { func (r *taskStore) LockNextTask(ctx context.Context, now time.Time, workerID string, lockTTL time.Duration) (*model.Task, error) {
workerID = strings.TrimSpace(workerID) workerID = strings.TrimSpace(workerID)
if workerID == "" { if workerID == "" {
return nil, merrors.InvalidArgument("worker id is required", "worker_id") return nil, merrors.InvalidArgument("worker id is required", "worker_id")
@@ -368,7 +369,7 @@ func (r *taskStore) LockNextTask(ctx context.Context, now time.Time, workerID st
) )
query := repository.Query(). query := repository.Query().
In(repository.Field("status"), string(TaskStatusPending), string(TaskStatusRetry)). In(repository.Field("status"), string(model.TaskStatusPending), string(model.TaskStatusRetry)).
Comparison(repository.Field("next_attempt_at"), builder.Lte, now). Comparison(repository.Field("next_attempt_at"), builder.Lte, now).
And(lockFilter). And(lockFilter).
Sort(repository.Field("next_attempt_at"), true). Sort(repository.Field("next_attempt_at"), true).
@@ -390,7 +391,7 @@ func (r *taskStore) LockNextTask(ctx context.Context, now time.Time, workerID st
Set(repository.Field("worker_id"), workerID) Set(repository.Field("worker_id"), workerID)
conditional := repository.IDFilter(candidate.ID).And( conditional := repository.IDFilter(candidate.ID).And(
repository.Query().In(repository.Field("status"), string(TaskStatusPending), string(TaskStatusRetry)), repository.Query().In(repository.Field("status"), string(model.TaskStatusPending), string(model.TaskStatusRetry)),
repository.Query().Comparison(repository.Field("next_attempt_at"), builder.Lte, now), repository.Query().Comparison(repository.Field("next_attempt_at"), builder.Lte, now),
lockFilter, lockFilter,
) )
@@ -427,7 +428,7 @@ func (r *taskStore) MarkDelivered(ctx context.Context, taskID bson.ObjectID, htt
} }
patch := repository.Patch(). patch := repository.Patch().
Set(repository.Field("status"), TaskStatusDelivered). Set(repository.Field("status"), model.TaskStatusDelivered).
Set(repository.Field("last_http_code"), httpCode). Set(repository.Field("last_http_code"), httpCode).
Set(repository.Field("delivered_at"), time.Now()). Set(repository.Field("delivered_at"), time.Now()).
Set(repository.Field("locked_until"), nil). Set(repository.Field("locked_until"), nil).
@@ -446,7 +447,7 @@ func (r *taskStore) MarkRetry(ctx context.Context, taskID bson.ObjectID, attempt
} }
patch := repository.Patch(). patch := repository.Patch().
Set(repository.Field("status"), TaskStatusRetry). Set(repository.Field("status"), model.TaskStatusRetry).
Set(repository.Field("attempt"), attempt). Set(repository.Field("attempt"), attempt).
Set(repository.Field("next_attempt_at"), nextAttemptAt.UTC()). Set(repository.Field("next_attempt_at"), nextAttemptAt.UTC()).
Set(repository.Field("last_error"), strings.TrimSpace(lastError)). Set(repository.Field("last_error"), strings.TrimSpace(lastError)).
@@ -466,7 +467,7 @@ func (r *taskStore) MarkFailed(ctx context.Context, taskID bson.ObjectID, attemp
} }
patch := repository.Patch(). patch := repository.Patch().
Set(repository.Field("status"), TaskStatusFailed). Set(repository.Field("status"), model.TaskStatusFailed).
Set(repository.Field("attempt"), attempt). Set(repository.Field("attempt"), attempt).
Set(repository.Field("last_error"), strings.TrimSpace(lastError)). Set(repository.Field("last_error"), strings.TrimSpace(lastError)).
Set(repository.Field("last_http_code"), httpCode). Set(repository.Field("last_http_code"), httpCode).
@@ -479,14 +480,14 @@ func (r *taskStore) MarkFailed(ctx context.Context, taskID bson.ObjectID, attemp
return nil return nil
} }
func mapTaskDoc(doc *taskDoc) *Task { func mapTaskDoc(doc *taskDoc) *model.Task {
if doc == nil { if doc == nil {
return nil return nil
} }
return &Task{ return &model.Task{
ID: doc.ID, Base: doc.Base,
EventID: doc.EventID, EventID: doc.EventID,
EndpointID: doc.EndpointID, EndpointRef: doc.EndpointRef,
EndpointURL: doc.EndpointURL, EndpointURL: doc.EndpointURL,
SigningMode: doc.SigningMode, SigningMode: doc.SigningMode,
SecretRef: doc.SecretRef, SecretRef: doc.SecretRef,

View File

@@ -3,12 +3,14 @@ package subscriptions
import ( import (
"context" "context"
"github.com/tech/sendico/edge/callbacks/internal/model"
"github.com/tech/sendico/edge/callbacks/internal/storage" "github.com/tech/sendico/edge/callbacks/internal/storage"
"go.mongodb.org/mongo-driver/v2/bson"
) )
// Resolver resolves active webhook endpoints for an event. // Resolver resolves active webhook endpoints for an event.
type Resolver interface { type Resolver interface {
Resolve(ctx context.Context, clientID, eventType string) ([]storage.Endpoint, error) Resolve(ctx context.Context, eventType string, organizationRef bson.ObjectID) ([]model.Endpoint, error)
} }
// Dependencies defines subscriptions resolver dependencies. // Dependencies defines subscriptions resolver dependencies.

View File

@@ -4,8 +4,10 @@ import (
"context" "context"
"strings" "strings"
"github.com/tech/sendico/edge/callbacks/internal/model"
"github.com/tech/sendico/edge/callbacks/internal/storage" "github.com/tech/sendico/edge/callbacks/internal/storage"
"github.com/tech/sendico/pkg/merrors" "github.com/tech/sendico/pkg/merrors"
"go.mongodb.org/mongo-driver/v2/bson"
) )
type service struct { type service struct {
@@ -21,15 +23,15 @@ func New(deps Dependencies) (Resolver, error) {
return &service{repo: deps.EndpointRepo}, nil return &service{repo: deps.EndpointRepo}, nil
} }
func (s *service) Resolve(ctx context.Context, clientID, eventType string) ([]storage.Endpoint, error) { func (s *service) Resolve(ctx context.Context, eventType string, organizationRef bson.ObjectID) ([]model.Endpoint, error) {
if strings.TrimSpace(clientID) == "" { if organizationRef == bson.NilObjectID {
return nil, merrors.InvalidArgument("subscriptions: client id is required", "clientID") return nil, merrors.InvalidArgument("subscriptions: client id is required", "clientID")
} }
if strings.TrimSpace(eventType) == "" { if strings.TrimSpace(eventType) == "" {
return nil, merrors.InvalidArgument("subscriptions: event type is required", "eventType") return nil, merrors.InvalidArgument("subscriptions: event type is required", "eventType")
} }
endpoints, err := s.repo.FindActiveByClientAndType(ctx, clientID, eventType) endpoints, err := s.repo.FindActive(ctx, eventType, organizationRef)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -23,7 +23,7 @@ require (
require ( require (
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260225065256-91dd007ecddc // indirect github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260302101851-b43f15d4ea3b // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.24.4 // indirect github.com/bits-and-blooms/bitset v1.24.4 // indirect
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect

View File

@@ -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/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 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260225065256-91dd007ecddc h1:1stW1OipdBj8Me+nj26SzT8yil7OYve0r3cWobzk1JQ= github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260302101851-b43f15d4ea3b h1:zoMPWbn27kscAQflTWzSHhm9fuex5SXSpyMlhCFPfxk=
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260225065256-91dd007ecddc/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI= github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260302101851-b43f15d4ea3b/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI=
github.com/VictoriaMetrics/fastcache v1.13.0 h1:AW4mheMR5Vd9FkAPUv+NH6Nhw+fmbTMGMsNAoA/+4G0= 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/VictoriaMetrics/fastcache v1.13.0/go.mod h1:hHXhl4DA2fTL2HTZDJFXWgW0LNjo6B+4aj2Wmng3TjU=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=

View File

@@ -26,7 +26,7 @@ require (
require ( require (
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260225065256-91dd007ecddc // indirect github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260302101851-b43f15d4ea3b // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.24.4 // indirect github.com/bits-and-blooms/bitset v1.24.4 // indirect
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect

View File

@@ -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/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 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260225065256-91dd007ecddc h1:1stW1OipdBj8Me+nj26SzT8yil7OYve0r3cWobzk1JQ= github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260302101851-b43f15d4ea3b h1:zoMPWbn27kscAQflTWzSHhm9fuex5SXSpyMlhCFPfxk=
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260225065256-91dd007ecddc/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI= github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260302101851-b43f15d4ea3b/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI=
github.com/VictoriaMetrics/fastcache v1.13.0 h1:AW4mheMR5Vd9FkAPUv+NH6Nhw+fmbTMGMsNAoA/+4G0= 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/VictoriaMetrics/fastcache v1.13.0/go.mod h1:hHXhl4DA2fTL2HTZDJFXWgW0LNjo6B+4aj2Wmng3TjU=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=

View File

@@ -81,11 +81,10 @@ func (p *brokerPaymentStatusPublisher) Publish(_ context.Context, in paymentStat
message := &model.PaymentStatusUpdated{ message := &model.PaymentStatusUpdated{
EventID: buildPaymentStatusEventID(paymentRef, payment.Version, in.CurrentState), EventID: buildPaymentStatusEventID(paymentRef, payment.Version, in.CurrentState),
Type: model.PaymentStatusUpdatedType, Type: model.PaymentStatusUpdatedType,
ClientID: payment.OrganizationRef.Hex(),
OccurredAt: occurredAt, OccurredAt: occurredAt,
PublishedAt: time.Now().UTC(), PublishedAt: time.Now().UTC(),
Data: model.PaymentStatusUpdatedData{ Data: model.PaymentStatusUpdatedData{
OrganizationRef: payment.OrganizationRef.Hex(), OrganizationRef: payment.OrganizationRef,
PaymentRef: paymentRef, PaymentRef: paymentRef,
QuotationRef: strings.TrimSpace(payment.QuotationRef), QuotationRef: strings.TrimSpace(payment.QuotationRef),
ClientPaymentRef: strings.TrimSpace(payment.ClientPaymentRef), ClientPaymentRef: strings.TrimSpace(payment.ClientPaymentRef),

View File

@@ -12,4 +12,7 @@ type DB interface {
auth.ProtectedDB[*model.Callback] auth.ProtectedDB[*model.Callback]
SetArchived(ctx context.Context, accountRef, organizationRef, callbackRef bson.ObjectID, archived, cascade bool) error SetArchived(ctx context.Context, accountRef, organizationRef, callbackRef bson.ObjectID, archived, cascade bool) error
List(ctx context.Context, accountRef, organizationRef, _ bson.ObjectID, cursor *model.ViewCursor) ([]model.Callback, error) List(ctx context.Context, accountRef, organizationRef, _ bson.ObjectID, cursor *model.ViewCursor) ([]model.Callback, error)
GetSigningSecretRef(ctx context.Context, accountRef, callbackRef bson.ObjectID) (string, error)
SetSigningSecretRef(ctx context.Context, accountRef, callbackRef bson.ObjectID, secretRef string) error
ClearSigningSecretRef(ctx context.Context, accountRef, callbackRef bson.ObjectID) error
} }

View File

@@ -0,0 +1,8 @@
package callbacksdb
import "github.com/tech/sendico/pkg/model"
type callbackInternal struct {
model.Callback `bson:",inline" json:",inline"`
SecretRef string `bson:"secret_ref" json:"-"`
}

View File

@@ -2,14 +2,12 @@ package callbacksdb
import ( import (
"context" "context"
"errors"
"github.com/tech/sendico/pkg/auth" "github.com/tech/sendico/pkg/auth"
"github.com/tech/sendico/pkg/db/callbacks" "github.com/tech/sendico/pkg/db/callbacks"
"github.com/tech/sendico/pkg/db/policy" "github.com/tech/sendico/pkg/db/policy"
ri "github.com/tech/sendico/pkg/db/repository/index" ri "github.com/tech/sendico/pkg/db/repository/index"
"github.com/tech/sendico/pkg/db/storable" "github.com/tech/sendico/pkg/db/storable"
"github.com/tech/sendico/pkg/merrors"
"github.com/tech/sendico/pkg/mlogger" "github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/model" "github.com/tech/sendico/pkg/model"
"github.com/tech/sendico/pkg/mservice" "github.com/tech/sendico/pkg/mservice"
@@ -29,10 +27,6 @@ func Create(
pdb policy.DB, pdb policy.DB,
db *mongo.Database, db *mongo.Database,
) (*CallbacksDB, error) { ) (*CallbacksDB, error) {
if err := ensureBuiltInPolicy(ctx, logger, pdb); err != nil {
return nil, err
}
p, err := auth.CreateDBImp[*model.Callback](ctx, logger, pdb, enforcer, mservice.Callbacks, db) p, err := auth.CreateDBImp[*model.Callback](ctx, logger, pdb, enforcer, mservice.Callbacks, db)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -43,7 +37,7 @@ func Create(
Name: "uq_callbacks_client_url", Name: "uq_callbacks_client_url",
Keys: []ri.Key{ Keys: []ri.Key{
{Field: storable.OrganizationRefField, Sort: ri.Asc}, {Field: storable.OrganizationRefField, Sort: ri.Asc},
{Field: "client_id", Sort: ri.Asc}, {Field: "organization_ref", Sort: ri.Asc},
{Field: "url", Sort: ri.Asc}, {Field: "url", Sort: ri.Asc},
}, },
Unique: true, Unique: true,
@@ -82,31 +76,4 @@ func Create(
}, nil }, nil
} }
func ensureBuiltInPolicy(ctx context.Context, logger mlogger.Logger, pdb policy.DB) error {
var existing model.PolicyDescription
if err := pdb.GetBuiltInPolicy(ctx, mservice.Callbacks, &existing); err == nil {
return nil
} else if !errors.Is(err, merrors.ErrNoData) {
return err
}
description := "Callbacks subscription management"
resourceTypes := []mservice.Type{mservice.Callbacks}
policyDescription := &model.PolicyDescription{
Describable: model.Describable{
Name: "Callbacks",
Description: &description,
},
ResourceTypes: &resourceTypes,
}
if err := pdb.Create(ctx, policyDescription); err != nil && !errors.Is(err, merrors.ErrDataConflict) {
if logger != nil {
logger.Warn("Failed to create built-in callbacks policy", zap.Error(err))
}
return err
}
return pdb.GetBuiltInPolicy(ctx, mservice.Callbacks, &existing)
}
var _ callbacks.DB = (*CallbacksDB)(nil) var _ callbacks.DB = (*CallbacksDB)(nil)

View File

@@ -0,0 +1,60 @@
package callbacksdb
import (
"context"
"strings"
"github.com/tech/sendico/pkg/db/repository"
"github.com/tech/sendico/pkg/merrors"
"github.com/tech/sendico/pkg/model"
"go.mongodb.org/mongo-driver/v2/bson"
)
func (db *CallbacksDB) GetSigningSecretRef(ctx context.Context, accountRef, callbackRef bson.ObjectID) (string, error) {
if callbackRef.IsZero() {
return "", merrors.InvalidArgument("callback reference is required", "callbackRef")
}
// Enforce read permissions through the public callback object first.
var callback model.Callback
if err := db.Get(ctx, accountRef, callbackRef, &callback); err != nil {
return "", err
}
internal := &callbackInternal{}
if err := db.DBImp.Repository.Get(ctx, callbackRef, internal); err != nil {
return "", err
}
return strings.TrimSpace(internal.SecretRef), nil
}
func (db *CallbacksDB) SetSigningSecretRef(ctx context.Context, accountRef, callbackRef bson.ObjectID, secretRef string) error {
if callbackRef.IsZero() {
return merrors.InvalidArgument("callback reference is required", "callbackRef")
}
value := strings.TrimSpace(secretRef)
if value == "" {
return merrors.InvalidArgument("secret reference is required", "secretRef")
}
return db.Patch(
ctx,
accountRef,
callbackRef,
repository.Patch().Set(repository.Field("secretRef"), value),
)
}
func (db *CallbacksDB) ClearSigningSecretRef(ctx context.Context, accountRef, callbackRef bson.ObjectID) error {
if callbackRef.IsZero() {
return merrors.InvalidArgument("callback reference is required", "callbackRef")
}
return db.Patch(
ctx,
accountRef,
callbackRef,
repository.Patch().Unset(repository.Field("secretRef")),
)
}

View File

@@ -12,6 +12,7 @@ import (
"github.com/tech/sendico/pkg/merrors" "github.com/tech/sendico/pkg/merrors"
"github.com/tech/sendico/pkg/model" "github.com/tech/sendico/pkg/model"
mutil "github.com/tech/sendico/pkg/mutil/db" mutil "github.com/tech/sendico/pkg/mutil/db"
"github.com/tech/sendico/pkg/mutil/mzap"
"go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/bson"
"go.uber.org/zap" "go.uber.org/zap"
) )
@@ -54,7 +55,6 @@ func (db *verificationDB) Consume(
zap.String("purpose", string(purpose)), zap.String("purpose", string(purpose)),
zap.Bool("account_scoped", accountScoped), zap.Bool("account_scoped", accountScoped),
zap.String("account_ref", accountRefHex), zap.String("account_ref", accountRefHex),
zap.Any("scope_filter", scopeFilter.BuildQuery()),
) )
// 1) Fast path for magic-link tokens: hash is deterministic and globally unique. // 1) Fast path for magic-link tokens: hash is deterministic and globally unique.
@@ -69,7 +69,6 @@ func (db *verificationDB) Consume(
zap.String("purpose", string(purpose)), zap.String("purpose", string(purpose)),
zap.Bool("account_scoped", accountScoped), zap.Bool("account_scoped", accountScoped),
zap.String("account_ref", accountRefHex), zap.String("account_ref", accountRefHex),
zap.Any("magic_filter", magicFilter.BuildQuery()),
) )
var direct model.VerificationToken var direct model.VerificationToken
err := db.DBImp.FindOne(ctx, magicFilter, &direct) err := db.DBImp.FindOne(ctx, magicFilter, &direct)
@@ -152,7 +151,7 @@ func (db *verificationDB) Consume(
db.Logger.Debug("Verification consume OTP candidate evaluated", db.Logger.Debug("Verification consume OTP candidate evaluated",
zap.Int("candidate_index", i), zap.Int("candidate_index", i),
zap.Int("candidate_total", len(tokens)), zap.Int("candidate_total", len(tokens)),
zap.String("candidate_id", t.ID.Hex()), mzap.ObjRef("candidate_ref", t.ID),
zap.Bool("candidate_has_salt", t.Salt != nil), zap.Bool("candidate_has_salt", t.Salt != nil),
zap.Bool("candidate_used", t.UsedAt != nil), zap.Bool("candidate_used", t.UsedAt != nil),
zap.Time("candidate_expires_at", t.ExpiresAt), zap.Time("candidate_expires_at", t.ExpiresAt),
@@ -181,7 +180,6 @@ func (db *verificationDB) Consume(
zap.String("purpose", string(purpose)), zap.String("purpose", string(purpose)),
zap.Bool("account_scoped", accountScoped), zap.Bool("account_scoped", accountScoped),
zap.String("account_ref", accountRefHex), zap.String("account_ref", accountRefHex),
zap.Any("active_filter", activeFilter.BuildQuery()),
) )
incremented, patchErr := db.DBImp.PatchMany( incremented, patchErr := db.DBImp.PatchMany(
@@ -271,8 +269,7 @@ func (db *verificationDB) Consume(
zap.String("purpose", string(purpose)), zap.String("purpose", string(purpose)),
zap.Bool("account_scoped", accountScoped), zap.Bool("account_scoped", accountScoped),
zap.String("account_ref", accountRefHex), zap.String("account_ref", accountRefHex),
zap.String("token_id", token.ID.Hex()), mzap.StorableRef(token),
zap.Any("consume_filter", consumeFilter.BuildQuery()),
) )
updated, err := db.DBImp.PatchMany( updated, err := db.DBImp.PatchMany(
@@ -285,7 +282,7 @@ func (db *verificationDB) Consume(
zap.String("purpose", string(purpose)), zap.String("purpose", string(purpose)),
zap.Bool("account_scoped", accountScoped), zap.Bool("account_scoped", accountScoped),
zap.String("account_ref", accountRefHex), zap.String("account_ref", accountRefHex),
zap.String("token_id", token.ID.Hex()), mzap.StorableRef(token),
zap.Error(err), zap.Error(err),
) )
return nil, err return nil, err
@@ -294,7 +291,7 @@ func (db *verificationDB) Consume(
zap.String("purpose", string(purpose)), zap.String("purpose", string(purpose)),
zap.Bool("account_scoped", accountScoped), zap.Bool("account_scoped", accountScoped),
zap.String("account_ref", accountRefHex), zap.String("account_ref", accountRefHex),
zap.String("token_id", token.ID.Hex()), mzap.StorableRef(token),
zap.Int("updated_count", updated), zap.Int("updated_count", updated),
) )
@@ -322,7 +319,7 @@ func (db *verificationDB) Consume(
zap.String("purpose", string(purpose)), zap.String("purpose", string(purpose)),
zap.Bool("account_scoped", accountScoped), zap.Bool("account_scoped", accountScoped),
zap.String("account_ref", accountRefHex), zap.String("account_ref", accountRefHex),
zap.String("token_id", token.ID.Hex()), mzap.StorableRef(token),
zap.Error(incrementErr), zap.Error(incrementErr),
) )
} else { } else {
@@ -330,7 +327,7 @@ func (db *verificationDB) Consume(
zap.String("purpose", string(purpose)), zap.String("purpose", string(purpose)),
zap.Bool("account_scoped", accountScoped), zap.Bool("account_scoped", accountScoped),
zap.String("account_ref", accountRefHex), zap.String("account_ref", accountRefHex),
zap.String("token_id", token.ID.Hex()), mzap.StorableRef(token),
zap.Int("updated_count", incremented), zap.Int("updated_count", incremented),
zap.String("transaction_note", "this update occurs inside transaction and may roll back depending on returned error"), zap.String("transaction_note", "this update occurs inside transaction and may roll back depending on returned error"),
) )
@@ -343,7 +340,7 @@ func (db *verificationDB) Consume(
zap.String("purpose", string(purpose)), zap.String("purpose", string(purpose)),
zap.Bool("account_scoped", accountScoped), zap.Bool("account_scoped", accountScoped),
zap.String("account_ref", accountRefHex), zap.String("account_ref", accountRefHex),
zap.String("token_id", token.ID.Hex()), mzap.StorableRef(token),
zap.Error(err), zap.Error(err),
) )
return nil, merrors.Internal("failed to re-check token state") return nil, merrors.Internal("failed to re-check token state")
@@ -353,7 +350,7 @@ func (db *verificationDB) Consume(
zap.String("purpose", string(purpose)), zap.String("purpose", string(purpose)),
zap.Bool("account_scoped", accountScoped), zap.Bool("account_scoped", accountScoped),
zap.String("account_ref", accountRefHex), zap.String("account_ref", accountRefHex),
zap.String("token_id", token.ID.Hex()), mzap.StorableRef(token),
}, tokenStateFields(&fresh)..., }, tokenStateFields(&fresh)...,
)..., )...,
) )
@@ -363,7 +360,7 @@ func (db *verificationDB) Consume(
zap.String("purpose", string(purpose)), zap.String("purpose", string(purpose)),
zap.Bool("account_scoped", accountScoped), zap.Bool("account_scoped", accountScoped),
zap.String("account_ref", accountRefHex), zap.String("account_ref", accountRefHex),
zap.String("token_id", token.ID.Hex()), mzap.StorableRef(token),
) )
return nil, verification.ErorrTokenAlreadyUsed() return nil, verification.ErorrTokenAlreadyUsed()
} }
@@ -372,7 +369,7 @@ func (db *verificationDB) Consume(
zap.String("purpose", string(purpose)), zap.String("purpose", string(purpose)),
zap.Bool("account_scoped", accountScoped), zap.Bool("account_scoped", accountScoped),
zap.String("account_ref", accountRefHex), zap.String("account_ref", accountRefHex),
zap.String("token_id", token.ID.Hex()), mzap.StorableRef(token),
zap.Time("now_utc", now), zap.Time("now_utc", now),
zap.Time("token_expires_at", fresh.ExpiresAt), zap.Time("token_expires_at", fresh.ExpiresAt),
) )
@@ -383,7 +380,7 @@ func (db *verificationDB) Consume(
zap.String("purpose", string(purpose)), zap.String("purpose", string(purpose)),
zap.Bool("account_scoped", accountScoped), zap.Bool("account_scoped", accountScoped),
zap.String("account_ref", accountRefHex), zap.String("account_ref", accountRefHex),
zap.String("token_id", token.ID.Hex()), mzap.StorableRef(token),
zap.Int("token_attempts", fresh.Attempts), zap.Int("token_attempts", fresh.Attempts),
zap.Int("token_max_retries", *fresh.MaxRetries), zap.Int("token_max_retries", *fresh.MaxRetries),
) )
@@ -394,7 +391,7 @@ func (db *verificationDB) Consume(
zap.String("purpose", string(purpose)), zap.String("purpose", string(purpose)),
zap.Bool("account_scoped", accountScoped), zap.Bool("account_scoped", accountScoped),
zap.String("account_ref", accountRefHex), zap.String("account_ref", accountRefHex),
zap.String("token_id", token.ID.Hex()), mzap.StorableRef(token),
) )
return nil, verification.ErorrTokenNotFound() return nil, verification.ErorrTokenNotFound()
}, },
@@ -457,8 +454,8 @@ func tokenIsDigitsOnly(value string) bool {
func tokenStateFields(token *model.VerificationToken) []zap.Field { func tokenStateFields(token *model.VerificationToken) []zap.Field {
fields := []zap.Field{ fields := []zap.Field{
zap.String("token_id", token.ID.Hex()), mzap.StorableRef(token),
zap.String("token_account_ref", token.AccountRef.Hex()), mzap.ObjRef("token_account_ref", token.AccountRef),
zap.String("token_purpose", string(token.Purpose)), zap.String("token_purpose", string(token.Purpose)),
zap.Bool("token_has_target", strings.TrimSpace(token.Target) != ""), zap.Bool("token_has_target", strings.TrimSpace(token.Target) != ""),
zap.Bool("token_has_idempotency_key", token.IdempotencyKey != nil), zap.Bool("token_has_idempotency_key", token.IdempotencyKey != nil),

View File

@@ -29,14 +29,6 @@ func syntheticIdempotencyKey() string {
return "auto:" + bson.NewObjectID().Hex() return "auto:" + bson.NewObjectID().Hex()
} }
func verificationContextFilter(request *verification.Request) builder.Query {
return repository.Query().And(
repository.Filter("accountRef", request.AccountRef),
repository.Filter("purpose", request.Purpose),
repository.Filter("target", request.Target),
)
}
func activeContextFilter(request *verification.Request, now time.Time) builder.Query { func activeContextFilter(request *verification.Request, now time.Time) builder.Query {
return repository.Query().And( return repository.Query().And(
repository.Filter("accountRef", request.AccountRef), repository.Filter("accountRef", request.AccountRef),

View File

@@ -28,7 +28,7 @@ require (
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260225065256-91dd007ecddc // indirect github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260302101851-b43f15d4ea3b // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.24.4 // indirect github.com/bits-and-blooms/bitset v1.24.4 // indirect
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect

View File

@@ -6,8 +6,8 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260225065256-91dd007ecddc h1:1stW1OipdBj8Me+nj26SzT8yil7OYve0r3cWobzk1JQ= github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260302101851-b43f15d4ea3b h1:zoMPWbn27kscAQflTWzSHhm9fuex5SXSpyMlhCFPfxk=
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260225065256-91dd007ecddc/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI= github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260302101851-b43f15d4ea3b/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI=
github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA=
github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=

View File

@@ -16,11 +16,14 @@ const (
CallbackSigningModeHMACSHA256 CallbackSigningMode = "hmac_sha256" CallbackSigningModeHMACSHA256 CallbackSigningMode = "hmac_sha256"
) )
type CallbackBackoff struct {
MinDelayMS int `bson:"min_ms" json:"minDelayMs"`
MaxDelayMS int `bson:"max_ms" json:"maxDelayMs"`
}
type CallbackRetryPolicy struct { type CallbackRetryPolicy struct {
MinDelayMS int `bson:"min_ms" json:"minDelayMs"` Backoff CallbackBackoff `bson:"backoff" json:"backoff"`
MaxDelayMS int `bson:"max_ms" json:"maxDelayMs"`
SigningMode CallbackSigningMode `bson:"signing_mode" json:"signingMode"` SigningMode CallbackSigningMode `bson:"signing_mode" json:"signingMode"`
SecretRef string `bson:"secret_ref,omitempty" json:"secretRef,omitempty"`
Headers map[string]string `bson:"headers,omitempty" json:"headers,omitempty"` Headers map[string]string `bson:"headers,omitempty" json:"headers,omitempty"`
MaxAttempts int `bson:"max_attempts" json:"maxAttempts"` MaxAttempts int `bson:"max_attempts" json:"maxAttempts"`
RequestTimeoutMS int `bson:"request_timeout_ms" json:"requestTimeoutMs"` RequestTimeoutMS int `bson:"request_timeout_ms" json:"requestTimeoutMs"`
@@ -29,7 +32,6 @@ type CallbackRetryPolicy struct {
type Callback struct { type Callback struct {
PermissionBound `bson:",inline" json:",inline"` PermissionBound `bson:",inline" json:",inline"`
Describable `bson:",inline" json:",inline"` Describable `bson:",inline" json:",inline"`
ClientID string `bson:"client_id" json:"clientId"`
Status CallbackStatus `bson:"status" json:"status"` Status CallbackStatus `bson:"status" json:"status"`
URL string `bson:"url" json:"url"` URL string `bson:"url" json:"url"`
EventTypes []string `bson:"event_types" json:"eventTypes"` EventTypes []string `bson:"event_types" json:"eventTypes"`

View File

@@ -1,6 +1,10 @@
package model package model
import "time" import (
"time"
"go.mongodb.org/mongo-driver/v2/bson"
)
const ( const (
PaymentStatusUpdatedType = "payment.status.updated" PaymentStatusUpdatedType = "payment.status.updated"
@@ -9,21 +13,20 @@ const (
type PaymentStatusUpdated struct { type PaymentStatusUpdated struct {
EventID string `json:"event_id,omitempty"` EventID string `json:"event_id,omitempty"`
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
ClientID string `json:"client_id,omitempty"`
OccurredAt time.Time `json:"occurred_at,omitempty"` OccurredAt time.Time `json:"occurred_at,omitempty"`
PublishedAt time.Time `json:"published_at,omitempty"` PublishedAt time.Time `json:"published_at,omitempty"`
Data PaymentStatusUpdatedData `json:"data"` Data PaymentStatusUpdatedData `json:"data"`
} }
type PaymentStatusUpdatedData struct { type PaymentStatusUpdatedData struct {
OrganizationRef string `json:"organization_ref,omitempty"` OrganizationRef bson.ObjectID `json:"organization_ref,omitempty"`
PaymentRef string `json:"payment_ref,omitempty"` PaymentRef string `json:"payment_ref,omitempty"`
QuotationRef string `json:"quotation_ref,omitempty"` QuotationRef string `json:"quotation_ref,omitempty"`
ClientPaymentRef string `json:"client_payment_ref,omitempty"` ClientPaymentRef string `json:"client_payment_ref,omitempty"`
IdempotencyKey string `json:"idempotency_key,omitempty"` IdempotencyKey string `json:"idempotency_key,omitempty"`
State string `json:"state,omitempty"` State string `json:"state,omitempty"`
PreviousState string `json:"previous_state,omitempty"` PreviousState string `json:"previous_state,omitempty"`
Version uint64 `json:"version,omitempty"` Version uint64 `json:"version,omitempty"`
IsTerminal bool `json:"is_terminal"` IsTerminal bool `json:"is_terminal"`
Event string `json:"event,omitempty"` Event string `json:"event,omitempty"`
} }

View File

@@ -3,7 +3,9 @@ package model
import ( import (
"time" "time"
"github.com/tech/sendico/pkg/db/storable"
"github.com/tech/sendico/pkg/mservice" "github.com/tech/sendico/pkg/mservice"
"go.mongodb.org/mongo-driver/v2/bson"
) )
type ClientRefreshToken struct { type ClientRefreshToken struct {
@@ -12,13 +14,14 @@ type ClientRefreshToken struct {
} }
type RefreshToken struct { type RefreshToken struct {
AccountBoundBase `bson:",inline" json:",inline"` storable.Base `bson:",inline" json:",inline"`
ClientRefreshToken `bson:",inline" json:",inline"` ClientRefreshToken `bson:",inline" json:",inline"`
ExpiresAt time.Time `bson:"expiresAt"` AccountRef *bson.ObjectID `bson:"accountRef,omitempty" json:"accountRef,omitempty"`
IsRevoked bool `bson:"isRevoked"` ExpiresAt time.Time `bson:"expiresAt"`
LastUsedAt time.Time `bson:"lastUsedAt,omitempty"` IsRevoked bool `bson:"isRevoked"`
UserAgent string `bson:"userAgent"` LastUsedAt time.Time `bson:"lastUsedAt,omitempty"`
IPAddress string `bson:"ipAddress"` UserAgent string `bson:"userAgent"`
IPAddress string `bson:"ipAddress"`
} }
func (*RefreshToken) Collection() string { func (*RefreshToken) Collection() string {

View File

@@ -1,388 +0,0 @@
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/aws/aws-sdk-go-v2 v1.41.2 h1:LuT2rzqNQsauaGkPK/7813XxcZ3o3yePY0Iy891T2ls=
github.com/aws/aws-sdk-go-v2 v1.41.2/go.mod h1:IvvlAZQXvTXznUPfRVfryiG1fbzE2NGK6m9u39YQ+S4=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 h1:zWFmPmgw4sveAYi1mRqG+E/g0461cJ5M4bJ8/nc6d3Q=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5/go.mod h1:nVUlMLVV8ycXSb7mSkcNu9e3v/1TJq2RTlrPwhYWr5c=
github.com/aws/aws-sdk-go-v2/config v1.32.10 h1:9DMthfO6XWZYLfzZglAgW5Fyou2nRI5CuV44sTedKBI=
github.com/aws/aws-sdk-go-v2/config v1.32.10/go.mod h1:2rUIOnA2JaiqYmSKYmRJlcMWy6qTj1vuRFscppSBMcw=
github.com/aws/aws-sdk-go-v2/credentials v1.19.10 h1:EEhmEUFCE1Yhl7vDhNOI5OCL/iKMdkkYFTRpZXNw7m8=
github.com/aws/aws-sdk-go-v2/credentials v1.19.10/go.mod h1:RnnlFCAlxQCkN2Q379B67USkBMu1PipEEiibzYN5UTE=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18 h1:Ii4s+Sq3yDfaMLpjrJsqD6SmG/Wq/P5L/hw2qa78UAY=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.18/go.mod h1:6x81qnY++ovptLE6nWQeWrpXxbnlIex+4H4eYYGcqfc=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18 h1:F43zk1vemYIqPAwhjTjYIz0irU2EY7sOb/F5eJ3HuyM=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.18/go.mod h1:w1jdlZXrGKaJcNoL+Nnrj+k5wlpGXqnNrKoP22HvAug=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18 h1:xCeWVjj0ki0l3nruoyP2slHsGArMxeiiaoPN5QZH6YQ=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.18/go.mod h1:r/eLGuGCBw6l36ZRWiw6PaZwPXb6YOj+i/7MizNl5/k=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18 h1:eZioDaZGJ0tMM4gzmkNIO2aAoQd+je7Ug7TkvAzlmkU=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.18/go.mod h1:CCXwUKAJdoWr6/NcxZ+zsiPr6oH/Q5aTooRGYieAyj4=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5 h1:CeY9LUdur+Dxoeldqoun6y4WtJ3RQtzk0JMP2gfUay0=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.5/go.mod h1:AZLZf2fMaahW5s/wMRciu1sYbdsikT/UHwbUjOdEVTc=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.10 h1:fJvQ5mIBVfKtiyx0AHY6HeWcRX5LGANLpq8SVR+Uazs=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.10/go.mod h1:Kzm5e6OmNH8VMkgK9t+ry5jEih4Y8whqs+1hrkxim1I=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18 h1:LTRCYFlnnKFlKsyIQxKhJuDuA3ZkrDQMRYm6rXiHlLY=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.18/go.mod h1:XhwkgGG6bHSd00nO/mexWTcTjgd6PjuvWQMqSn2UaEk=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18 h1:/A/xDuZAVD2BpsS2fftFRo/NoEKQJ8YTnJDEHBy2Gtg=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.18/go.mod h1:hWe9b4f+djUQGmyiGEeOnZv69dtMSgpDRIvNMvuvzvY=
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.2 h1:M1A9AjcFwlxTLuf0Faj88L8Iqw0n/AJHjpZTQzMMsSc=
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.2/go.mod h1:KsdTV6Q9WKUZm2mNJnUFmIoXfZux91M3sr/a4REX8e0=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.6 h1:MzORe+J94I+hYu2a6XmV5yC9huoTv8NRcCrUNedDypQ=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.6/go.mod h1:hXzcHLARD7GeWnifd8j9RWqtfIgxj4/cAtIVIK7hg8g=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.11 h1:7oGD8KPfBOJGXiCoRKrrrQkbvCp8N++u36hrLMPey6o=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.11/go.mod h1:0DO9B5EUJQlIDif+XJRWCljZRKsAFKh3gpFz7UnDtOo=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15 h1:edCcNp9eGIUDUCrzoCu1jWAXLGFIizeqkdkKgRlJwWc=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.15/go.mod h1:lyRQKED9xWfgkYC/wmmYfv7iVIM68Z5OQ88ZdcV1QbU=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7 h1:NITQpgo9A5NrDZ57uOWj+abvXSb83BbyggcUBVksN7c=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.7/go.mod h1:sks5UWBhEuWYDPdwlnRFn1w7xWdH29Jcpe+/PJQefEs=
github.com/aws/smithy-go v1.24.1 h1:VbyeNfmYkWoxMVpGUAbQumkODcYmfMRfZ8yQiH30SK0=
github.com/aws/smithy-go v1.24.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs=
github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/casbin/casbin/v2 v2.135.0 h1:6BLkMQiGotYyS5yYeWgW19vxqugUlvHFkFiLnLR/bxk=
github.com/casbin/casbin/v2 v2.135.0/go.mod h1:FmcfntdXLTcYXv/hxgNntcRPqAbwOG9xsism0yXT+18=
github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
github.com/casbin/govaluate v1.10.0 h1:ffGw51/hYH3w3rZcxO/KcaUIDOLP84w7nsidMVgaDG0=
github.com/casbin/govaluate v1.10.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
github.com/casbin/mongodb-adapter/v4 v4.3.0 h1:yYXky9v1by6vj/0QK7OyHyd/xpz4vzh0lCi7JKrS4qQ=
github.com/casbin/mongodb-adapter/v4 v4.3.0/go.mod h1:bOTSYZUjX7I9E0ExEvgq46m3mcDNRII7g8iWjrM1BHE=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 h1:5RVFMOWjMyRy8cARdy79nAmgYw3hK/4HUq48LQ6Wwqo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI=
github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg=
github.com/go-chi/chi v1.5.5 h1:vOB/HbEMt9QqBqErz07QehcOKHaWFtuj87tTDVz2qXE=
github.com/go-chi/chi v1.5.5/go.mod h1:C9JqLr3tIYjDOZpzn+BCuxY8z8vmca43EeMgyZt7irw=
github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-chi/jwtauth/v5 v5.4.0 h1:Ieh0xMJsFvqylqJ02/mQHKzbbKO9DYNBh4DPKCwTwYI=
github.com/go-chi/jwtauth/v5 v5.4.0/go.mod h1:w6yjqUUXz1b8+oiJel64Sz1KJwduQM6qUA5QNzO5+bQ=
github.com/go-chi/metrics v0.1.1 h1:CXhbnkAVVjb0k73EBRQ6Z2YdWFnbXZgNtg1Mboguibk=
github.com/go-chi/metrics v0.1.1/go.mod h1:mcGTM1pPalP7WCtb+akNYFO/lwNwBBLCuedepqjoPn4=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA=
github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw=
github.com/lestrrat-go/dsig v1.0.0 h1:OE09s2r9Z81kxzJYRn07TFM9XA4akrUdoMwr0L8xj38=
github.com/lestrrat-go/dsig v1.0.0/go.mod h1:dEgoOYYEJvW6XGbLasr8TFcAxoWrKlbQvmJgCR0qkDo=
github.com/lestrrat-go/dsig-secp256k1 v1.0.0 h1:JpDe4Aybfl0soBvoVwjqDbp+9S1Y2OM7gcrVVMFPOzY=
github.com/lestrrat-go/dsig-secp256k1 v1.0.0/go.mod h1:CxUgAhssb8FToqbL8NjSPoGQlnO4w3LG1P0qPWQm/NU=
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
github.com/lestrrat-go/httprc/v3 v3.0.4 h1:pXyH2ppK8GYYggygxJ3TvxpCZnbEUWc9qSwRTTApaLA=
github.com/lestrrat-go/httprc/v3 v3.0.4/go.mod h1:mSMtkZW92Z98M5YoNNztbRGxbXHql7tSitCvaxvo9l0=
github.com/lestrrat-go/jwx/v3 v3.0.13 h1:AdHKiPIYeCSnOJtvdpipPg/0SuFh9rdkN+HF3O0VdSk=
github.com/lestrrat-go/jwx/v3 v3.0.13/go.mod h1:2m0PV1A9tM4b/jVLMx8rh6rBl7F6WGb3EG2hufN9OQU=
github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss=
github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg=
github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54 h1:mFWunSatvkQQDhpdyuFAYwyAan3hzCuma+Pz8sqvOfg=
github.com/lufia/plan9stats v0.0.0-20250827001030-24949be3fa54/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo=
github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/nats-io/nats.go v1.49.0 h1:yh/WvY59gXqYpgl33ZI+XoVPKyut/IcEaqtsiuTJpoE=
github.com/nats-io/nats.go v1.49.0/go.mod h1:fDCn3mN5cY8HooHwE2ukiLb4p4G4ImmzvXyJt+tGwdw=
github.com/nats-io/nkeys v0.4.15 h1:JACV5jRVO9V856KOapQ7x+EY8Jo3qw1vJt/9Jpwzkk4=
github.com/nats-io/nkeys v0.4.15/go.mod h1:CpMchTXC9fxA5zrMo4KpySxNjiDVvr8ANOSZdiNfUrs=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
github.com/prometheus/procfs v0.20.0 h1:AA7aCvjxwAquZAlonN7888f2u4IN8WVeFgBi4k82M4Q=
github.com/prometheus/procfs v0.20.0/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8=
github.com/testcontainers/testcontainers-go v0.33.0 h1:zJS9PfXYT5O0ZFXM2xxXfk4J5UMw/kRiISng037Gxdw=
github.com/testcontainers/testcontainers-go v0.33.0/go.mod h1:W80YpTa8D5C3Yy16icheD01UTDu+LmXIA2Keo+jWtT8=
github.com/testcontainers/testcontainers-go/modules/mongodb v0.33.0 h1:iXVA84s5hKMS5gn01GWOYHE3ymy/2b+0YkpFeTxB2XY=
github.com/testcontainers/testcontainers-go/modules/mongodb v0.33.0/go.mod h1:R6tMjTojRiaoo89fh/hf7tOmfzohdqSU17R9DwSVSog=
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/valyala/fastjson v1.6.10 h1:/yjJg8jaVQdYR3arGxPE2X5z89xrlhS0eGXdv+ADTh4=
github.com/valyala/fastjson v1.6.10/go.mod h1:e6FubmQouUNP73jtMLmcbxS6ydWIpOfhz34TSfO3JaE=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs=
github.com/xdg-go/scram v1.2.0/go.mod h1:3dlrS0iBaWKYVt2ZfA4cj48umJZ+cAEbR6/SjLA88I8=
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.mongodb.org/mongo-driver v1.17.8 h1:BDP3+U3Y8K0vTrpqDJIRaXNhb/bKyoVeg6tIJsW5EhM=
go.mongodb.org/mongo-driver v1.17.8/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ=
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM=
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.8.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
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=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/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/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4=
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
moul.io/chizap v1.0.3 h1:mliXvvuS5HVo3QP8qPXczWtRM5dQ9UmK3bBVIkZo6ek=
moul.io/chizap v1.0.3/go.mod h1:pq4R9kGLwz4XjBc4hodQYuoE7Yc9RUabLBFyyi2uErk=

View File

@@ -26,8 +26,8 @@ WORKDIR /src
COPY --from=builder /src/api/proto ./api/proto COPY --from=builder /src/api/proto ./api/proto
COPY --from=builder /src/api/pkg ./api/pkg COPY --from=builder /src/api/pkg ./api/pkg
# Copy vault-aware entrypoint wrapper # Copy dev-specific entrypoint script
COPY api/edge/callbacks/entrypoint.sh /app/entrypoint.sh COPY ci/dev/entrypoints/callbacks.sh /app/entrypoint.sh
RUN chmod +x /app/entrypoint.sh RUN chmod +x /app/entrypoint.sh
# Source code will be mounted at runtime # Source code will be mounted at runtime

View File

@@ -0,0 +1,17 @@
#!/bin/sh
set -eu
# Load Vault token from file if VAULT_TOKEN_FILE is set
if [ -n "${VAULT_TOKEN_FILE:-}" ] && [ -f "${VAULT_TOKEN_FILE}" ]; then
token="$(cat "${VAULT_TOKEN_FILE}" 2>/dev/null | tr -d '[:space:]')"
if [ -n "${token}" ]; then
export VAULT_TOKEN="${token}"
fi
fi
if [ -z "${VAULT_TOKEN:-}" ]; then
echo "[entrypoint] VAULT_TOKEN is not set; expected Vault Agent sink to write a token to ${VAULT_TOKEN_FILE:-/run/vault/token}" >&2
fi
# Execute the command passed as arguments (e.g., air)
exec "$@"

View File

@@ -89,8 +89,6 @@ paths:
$ref: ./api/payments/quote.yaml $ref: ./api/payments/quote.yaml
/payments/multiquote/{organizations_ref}: /payments/multiquote/{organizations_ref}:
$ref: ./api/payments/multiquote.yaml $ref: ./api/payments/multiquote.yaml
/payments/immediate/{organizations_ref}:
$ref: ./api/payments/immediate.yaml
/payments/by-quote/{organizations_ref}: /payments/by-quote/{organizations_ref}:
$ref: ./api/payments/by_quote.yaml $ref: ./api/payments/by_quote.yaml
/payments/by-multiquote/{organizations_ref}: /payments/by-multiquote/{organizations_ref}:

View File

@@ -1,12 +1,14 @@
components: components:
requestBodies: requestBodies:
LoginBody: LoginBody:
description: JSON credentials payload for standard login.
required: true required: true
content: content:
application/json: application/json:
schema: schema:
$ref: ../request/auth.yaml#/components/schemas/LoginRequest $ref: ../request/auth.yaml#/components/schemas/LoginRequest
ApiLoginBody: ApiLoginBody:
description: JSON credentials payload for API client login.
required: true required: true
content: content:
application/json: application/json:
@@ -14,6 +16,7 @@ components:
$ref: ../request/auth.yaml#/components/schemas/ApiLoginRequest $ref: ../request/auth.yaml#/components/schemas/ApiLoginRequest
RefreshTokenBody: RefreshTokenBody:
description: JSON payload containing session and refresh token data.
required: true required: true
content: content:
application/json: application/json:

View File

@@ -11,6 +11,7 @@ components:
description: Client identifier bound to refresh token lifecycle and client policy checks. description: Client identifier bound to refresh token lifecycle and client policy checks.
deviceId: deviceId:
type: string type: string
description: Device/server identifier associated with the client session.
login: login:
$ref: ../../../models/auth/login_data.yaml#/components/schemas/LoginData $ref: ../../../models/auth/login_data.yaml#/components/schemas/LoginData

View File

@@ -1,6 +1,7 @@
components: components:
schemas: schemas:
AccountAuthData: AccountAuthData:
description: Authentication response containing account profile and access token.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
@@ -8,11 +9,14 @@ components:
- account - account
properties: properties:
accessToken: accessToken:
description: Access token used for authenticated requests.
$ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData $ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData
account: account:
description: Authenticated account data.
$ref: ../../../models/account/account.yaml#/components/schemas/AccountData $ref: ../../../models/account/account.yaml#/components/schemas/AccountData
LoginData: LoginData:
description: Full login response including access and refresh tokens.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
@@ -21,13 +25,17 @@ components:
- refreshToken - refreshToken
properties: properties:
accessToken: accessToken:
description: Access token used for authenticated requests.
$ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData $ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData
account: account:
description: Authenticated account data.
$ref: ../../../models/account/account.yaml#/components/schemas/AccountData $ref: ../../../models/account/account.yaml#/components/schemas/AccountData
refreshToken: refreshToken:
description: Refresh token used to obtain a new access token.
$ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData $ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData
PendingLoginData: PendingLoginData:
description: Pending login response requiring additional verification.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
@@ -36,14 +44,19 @@ components:
- target - target
properties: properties:
account: account:
description: Interim authentication payload prepared for verification completion.
type: object type: object
additionalProperties: false additionalProperties: false
properties: properties:
accessToken: accessToken:
description: Temporary access token issued before verification is completed.
$ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData $ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData
account: account:
description: Account data associated with the pending login flow.
$ref: ../../../models/account/account.yaml#/components/schemas/AccountData $ref: ../../../models/account/account.yaml#/components/schemas/AccountData
pendingToken: pendingToken:
description: Token proving the pending verification session.
$ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData $ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData
target: target:
description: Verification target destination (for example email or phone).
type: string type: string

View File

@@ -1,9 +1,9 @@
components: components:
requestBodies: requestBodies:
CallbackBody: CallbackBody:
description: JSON body containing callback endpoint configuration.
required: true required: true
content: content:
application/json: application/json:
schema: schema:
$ref: ../request/callback.yaml#/components/schemas/CallbackRequest $ref: ../request/callback.yaml#/components/schemas/CallbackRequest

View File

@@ -1,5 +1,5 @@
components: components:
schemas: schemas:
CallbackRequest: CallbackRequest:
$ref: ../../../models/callback/callback.yaml#/components/schemas/Callback description: Request payload used to create or update a callback configuration.
$ref: ../../../models/callback/callback.yaml#/components/schemas/CallbackContent

View File

@@ -1,6 +1,7 @@
components: components:
schemas: schemas:
CallbacksAuthData: CallbacksAuthData:
description: Authenticated response payload containing callback configurations.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
@@ -8,12 +9,14 @@ components:
- callbacks - callbacks
properties: properties:
accessToken: accessToken:
description: Refreshed access token to be used in subsequent API calls.
$ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData $ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData
callbacks: callbacks:
description: Collection of callbacks configured for the authenticated scope.
type: array type: array
items: items:
$ref: ../../../models/callback/callback.yaml#/components/schemas/Callback $ref: ../../../models/callback/callback.yaml#/components/schemas/Callback
generatedSigningSecret: generatedSigningSecret:
description: Newly generated signing secret when secret rotation is requested.
type: string type: string
nullable: true nullable: true

View File

@@ -1,7 +1,7 @@
post: post:
tags: [Callbacks] tags: [Callbacks]
summary: Rotate callback signing secret summary: Rotate callback signing secret
description: Generates and stores a new HMAC secret for the callback in Vault and returns it once. description: Generates and stores a new HMAC secret for the callback and returns it once.
operationId: callbacksRotateSecret operationId: callbacksRotateSecret
security: security:
- bearerAuth: [] - bearerAuth: []

View File

@@ -1,6 +1,7 @@
components: components:
schemas: schemas:
OrganizationsAuthData: OrganizationsAuthData:
description: Authenticated response payload containing organizations.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
@@ -8,8 +9,10 @@ components:
- organizations - organizations
properties: properties:
accessToken: accessToken:
description: Refreshed access token to be used in subsequent API calls.
$ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData $ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData
organizations: organizations:
description: Collection of organizations available to the authenticated account.
type: array type: array
items: items:
$ref: ../../../models/organization/organization.yaml#/components/schemas/Organization $ref: ../../../models/organization/organization.yaml#/components/schemas/Organization

View File

@@ -4,7 +4,7 @@ components:
name: callbacks_ref name: callbacks_ref
in: path in: path
required: true required: true
description: Callback subscription reference (Mongo ObjectId, 24 hex chars). description: Callback subscription reference, 24 hex chars.
schema: schema:
$ref: ../../models/objectid.yaml#/components/schemas/ObjectId $ref: ../../models/objectid.yaml#/components/schemas/ObjectId

View File

@@ -4,6 +4,6 @@ components:
name: org_ref name: org_ref
in: path in: path
required: true required: true
description: Organization reference in BFF route format (Mongo ObjectId, 24 hex chars). description: Organization reference in BFF route format, 24 hex chars.
schema: schema:
$ref: ../../models/objectid.yaml#/components/schemas/ObjectId $ref: ../../models/objectid.yaml#/components/schemas/ObjectId

View File

@@ -4,6 +4,6 @@ components:
name: payment_methods_ref name: payment_methods_ref
in: path in: path
required: true required: true
description: Payment method reference (Mongo ObjectId, 24 hex chars). description: Payment method reference, 24 hex chars.
schema: schema:
$ref: ../../models/objectid.yaml#/components/schemas/ObjectId $ref: ../../models/objectid.yaml#/components/schemas/ObjectId

View File

@@ -4,6 +4,6 @@ components:
name: recipients_ref name: recipients_ref
in: path in: path
required: true required: true
description: Recipient reference (Mongo ObjectId, 24 hex chars). description: Recipient reference, 24 hex chars.
schema: schema:
$ref: ../../models/objectid.yaml#/components/schemas/ObjectId $ref: ../../models/objectid.yaml#/components/schemas/ObjectId

View File

@@ -1,6 +1,7 @@
components: components:
requestBodies: requestBodies:
PaymentMethodBody: PaymentMethodBody:
description: JSON body containing a payment method definition.
required: true required: true
content: content:
application/json: application/json:

View File

@@ -1,4 +1,5 @@
components: components:
schemas: schemas:
PaymentMethodRequest: PaymentMethodRequest:
description: Request payload used to create or update a payment method.
$ref: ../../../models/payment_method/payment_method.yaml#/components/schemas/PaymentMethod $ref: ../../../models/payment_method/payment_method.yaml#/components/schemas/PaymentMethod

View File

@@ -1,6 +1,7 @@
components: components:
schemas: schemas:
Payment MethodsAuthData: Payment MethodsAuthData:
description: Authenticated response payload containing payment methods.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
@@ -8,8 +9,10 @@ components:
- payment_methods - payment_methods
properties: properties:
accessToken: accessToken:
description: Refreshed access token to be used in subsequent API calls.
$ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData $ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData
payment_methods: payment_methods:
description: Collection of payment methods available to the authenticated account.
type: array type: array
items: items:
$ref: ../../../models/payment_method/payment_method.yaml#/components/schemas/PaymentMethod $ref: ../../../models/payment_method/payment_method.yaml#/components/schemas/PaymentMethod

View File

@@ -1,6 +1,7 @@
components: components:
requestBodies: requestBodies:
QuotePaymentBody: QuotePaymentBody:
description: JSON payload used to request a quote for one payment intent.
required: true required: true
content: content:
application/json: application/json:
@@ -8,6 +9,7 @@ components:
$ref: ../request/payment.yaml#/components/schemas/QuotePaymentRequest $ref: ../request/payment.yaml#/components/schemas/QuotePaymentRequest
QuotePaymentsBody: QuotePaymentsBody:
description: JSON payload used to request quotes for multiple payment intents.
required: true required: true
content: content:
application/json: application/json:
@@ -15,6 +17,7 @@ components:
$ref: ../request/payment.yaml#/components/schemas/QuotePaymentsRequest $ref: ../request/payment.yaml#/components/schemas/QuotePaymentsRequest
InitiatePaymentBody: InitiatePaymentBody:
description: JSON payload used to initiate a single payment.
required: true required: true
content: content:
application/json: application/json:
@@ -22,6 +25,7 @@ components:
$ref: ../request/payment.yaml#/components/schemas/InitiatePaymentRequest $ref: ../request/payment.yaml#/components/schemas/InitiatePaymentRequest
InitiatePaymentsBody: InitiatePaymentsBody:
description: JSON payload used to initiate multiple payments from a quote reference.
required: true required: true
content: content:
application/json: application/json:

View File

@@ -1,33 +1,41 @@
components: components:
schemas: schemas:
PaymentBase: PaymentBase:
description: Base fields shared by payment initiation request payloads.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
- idempotencyKey - idempotencyKey
properties: properties:
idempotencyKey: idempotencyKey:
description: Client-supplied key used to safely deduplicate requests.
type: string type: string
metadata: metadata:
description: Optional request metadata forwarded through payment processing.
type: object type: object
additionalProperties: additionalProperties:
type: string type: string
QuotePaymentRequest: QuotePaymentRequest:
description: Request payload to quote a single payment intent.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
- intent - intent
properties: properties:
idempotencyKey: idempotencyKey:
description: Idempotency key used when persisting quote context.
type: string type: string
metadata: metadata:
description: Optional metadata associated with the quote request.
type: object type: object
additionalProperties: additionalProperties:
type: string type: string
intent: intent:
description: Payment intent to be priced.
$ref: ../../../models/payment/payment.yaml#/components/schemas/PaymentIntent $ref: ../../../models/payment/payment.yaml#/components/schemas/PaymentIntent
previewOnly: previewOnly:
description: If true, returns a preview quote without requiring idempotency.
type: boolean type: boolean
allOf: allOf:
- if: - if:
@@ -45,23 +53,28 @@ components:
- idempotencyKey - idempotencyKey
QuotePaymentsRequest: QuotePaymentsRequest:
description: Request payload to quote multiple payment intents in a single call.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
- intents - intents
properties: properties:
idempotencyKey: idempotencyKey:
description: Idempotency key used when persisting batch quote context.
type: string type: string
metadata: metadata:
description: Optional metadata associated with the quote request.
type: object type: object
additionalProperties: additionalProperties:
type: string type: string
intents: intents:
description: List of payment intents to be priced.
type: array type: array
minItems: 1 minItems: 1
items: items:
$ref: ../../../models/payment/payment.yaml#/components/schemas/PaymentIntent $ref: ../../../models/payment/payment.yaml#/components/schemas/PaymentIntent
previewOnly: previewOnly:
description: If true, returns preview quotes without requiring idempotency.
type: boolean type: boolean
allOf: allOf:
- if: - if:
@@ -79,6 +92,7 @@ components:
- idempotencyKey - idempotencyKey
InitiatePaymentRequest: InitiatePaymentRequest:
description: Request payload to initiate a single payment.
allOf: allOf:
- $ref: ./payment.yaml#/components/schemas/PaymentBase - $ref: ./payment.yaml#/components/schemas/PaymentBase
- type: object - type: object
@@ -88,11 +102,14 @@ components:
- required: [quoteRef] - required: [quoteRef]
properties: properties:
intent: intent:
description: Payment intent to execute directly.
$ref: ../../../models/payment/payment.yaml#/components/schemas/PaymentIntent $ref: ../../../models/payment/payment.yaml#/components/schemas/PaymentIntent
quoteRef: quoteRef:
description: Reference to a previously generated quote to execute.
type: string type: string
InitiatePaymentsRequest: InitiatePaymentsRequest:
description: Request payload to initiate multiple payments from a multi-quote reference.
allOf: allOf:
- $ref: ./payment.yaml#/components/schemas/PaymentBase - $ref: ./payment.yaml#/components/schemas/PaymentBase
- type: object - type: object
@@ -101,4 +118,5 @@ components:
- quoteRef - quoteRef
properties: properties:
quoteRef: quoteRef:
description: Reference to a previously generated multi-quote.
type: string type: string

View File

@@ -1,6 +1,7 @@
components: components:
schemas: schemas:
PaymentQuoteData: PaymentQuoteData:
description: Response payload for a single payment quote.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
@@ -8,13 +9,17 @@ components:
- quote - quote
properties: properties:
accessToken: accessToken:
description: Refreshed access token to be used in subsequent API calls.
$ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData $ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData
idempotencyKey: idempotencyKey:
description: Idempotency key associated with the quote response.
type: string type: string
quote: quote:
description: Generated quote data for the requested payment intent.
$ref: ../../../models/payment/payment.yaml#/components/schemas/PaymentQuote $ref: ../../../models/payment/payment.yaml#/components/schemas/PaymentQuote
PaymentQuotesData: PaymentQuotesData:
description: Response payload for a batch quote request.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
@@ -22,21 +27,27 @@ components:
- quote - quote
properties: properties:
accessToken: accessToken:
description: Refreshed access token to be used in subsequent API calls.
$ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData $ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData
quote: quote:
description: Batch quote summary and quoted items.
type: object type: object
additionalProperties: false additionalProperties: false
properties: properties:
idempotencyKey: idempotencyKey:
description: Idempotency key associated with the batch quote response.
type: string type: string
quoteRef: quoteRef:
description: Reference to the generated batch quote.
type: string type: string
items: items:
description: Collection of quotes for each requested payment intent.
type: array type: array
items: items:
$ref: ../../../models/payment/payment.yaml#/components/schemas/PaymentQuote $ref: ../../../models/payment/payment.yaml#/components/schemas/PaymentQuote
PaymentsData: PaymentsData:
description: Response payload containing a list of payments.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
@@ -44,15 +55,19 @@ components:
- payments - payments
properties: properties:
accessToken: accessToken:
description: Refreshed access token to be used in subsequent API calls.
$ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData $ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData
payments: payments:
description: Collection of payment records.
type: array type: array
items: items:
$ref: ../../../models/payment/payment.yaml#/components/schemas/Payment $ref: ../../../models/payment/payment.yaml#/components/schemas/Payment
page: page:
description: Pagination cursor metadata for payment listing endpoints.
$ref: ../../../models/common/pagination.yaml#/components/schemas/CursorPageResponse $ref: ../../../models/common/pagination.yaml#/components/schemas/CursorPageResponse
PaymentData: PaymentData:
description: Response payload containing a single payment record.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
@@ -60,6 +75,8 @@ components:
- payment - payment
properties: properties:
accessToken: accessToken:
description: Refreshed access token to be used in subsequent API calls.
$ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData $ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData
payment: payment:
description: Requested payment record.
$ref: ../../../models/payment/payment.yaml#/components/schemas/Payment $ref: ../../../models/payment/payment.yaml#/components/schemas/Payment

View File

@@ -1,6 +1,7 @@
components: components:
requestBodies: requestBodies:
RecipientBody: RecipientBody:
description: JSON body containing recipient details.
required: true required: true
content: content:
application/json: application/json:

View File

@@ -1,4 +1,5 @@
components: components:
schemas: schemas:
RecipientRequest: RecipientRequest:
description: Request payload used to create or update a recipient.
$ref: ../../../models/recipient/recipient.yaml#/components/schemas/Recipient $ref: ../../../models/recipient/recipient.yaml#/components/schemas/Recipient

View File

@@ -1,6 +1,7 @@
components: components:
schemas: schemas:
RecipientsAuthData: RecipientsAuthData:
description: Authenticated response payload containing recipients.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
@@ -8,8 +9,10 @@ components:
- recipients - recipients
properties: properties:
accessToken: accessToken:
description: Refreshed access token to be used in subsequent API calls.
$ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData $ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData
recipients: recipients:
description: Collection of recipients available to the authenticated account.
type: array type: array
items: items:
$ref: ../../../models/recipient/recipient.yaml#/components/schemas/Recipient $ref: ../../../models/recipient/recipient.yaml#/components/schemas/Recipient

View File

@@ -1,6 +1,7 @@
components: components:
schemas: schemas:
ApiError: ApiError:
description: Standard API error envelope with optional debugging details.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
@@ -9,11 +10,15 @@ components:
- source - source
properties: properties:
code: code:
description: Application-specific numeric error code.
type: integer type: integer
format: int32 format: int32
error: error:
description: Human-readable error summary.
type: string type: string
source: source:
description: Component or subsystem that produced the error.
type: string type: string
details: details:
description: Optional detailed message providing additional failure context.
type: string type: string

View File

@@ -1,6 +1,7 @@
components: components:
requestBodies: requestBodies:
VerificationCodeBody: VerificationCodeBody:
description: JSON payload used to request a new verification code.
required: true required: true
content: content:
application/json: application/json:
@@ -8,6 +9,7 @@ components:
$ref: ../request/verification.yaml#/components/schemas/VerificationCodeRequest $ref: ../request/verification.yaml#/components/schemas/VerificationCodeRequest
VerifyCodeBody: VerifyCodeBody:
description: JSON payload containing a verification code to validate.
required: true required: true
content: content:
application/json: application/json:

View File

@@ -1,6 +1,7 @@
components: components:
schemas: schemas:
VerificationResponseData: VerificationResponseData:
description: Response data returned after requesting a verification code.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
@@ -10,17 +11,22 @@ components:
- target - target
properties: properties:
idempotencyKey: idempotencyKey:
description: Idempotency key associated with the verification request.
type: string type: string
ttl_seconds: ttl_seconds:
description: Verification code validity period in seconds.
type: integer type: integer
format: int32 format: int32
cooldown_seconds: cooldown_seconds:
description: Cooldown period before another code can be requested.
type: integer type: integer
format: int32 format: int32
target: target:
description: Destination where the verification code was sent.
type: string type: string
VerifyResultData: VerifyResultData:
description: Result payload returned after code verification succeeds.
oneOf: oneOf:
- $ref: ../../accounts/response/auth.yaml#/components/schemas/LoginData - $ref: ../../accounts/response/auth.yaml#/components/schemas/LoginData
- $ref: ../../response/response.yaml#/components/schemas/SuccessResultData - $ref: ../../response/response.yaml#/components/schemas/SuccessResultData

View File

@@ -1,12 +1,15 @@
components: components:
schemas: schemas:
AccountData: AccountData:
description: Extended account payload including visibility-specific metadata.
allOf: allOf:
- $ref: ./account_public.yaml#/components/schemas/AccountPublic - $ref: ./account_public.yaml#/components/schemas/AccountPublic
- type: object - type: object
description: Account attributes that are not part of the public profile.
additionalProperties: false additionalProperties: false
required: required:
- isAnonymous - isAnonymous
properties: properties:
isAnonymous: isAnonymous:
description: Indicates whether the account is marked as anonymous.
type: boolean type: boolean

View File

@@ -1,10 +1,12 @@
components: components:
schemas: schemas:
AccountPublic: AccountPublic:
description: Public account profile data exposed in API responses.
allOf: allOf:
- $ref: ../storable.yaml#/components/schemas/Storable - $ref: ../storable.yaml#/components/schemas/Storable
- $ref: ../common/describable.yaml#/components/schemas/Describable - $ref: ../common/describable.yaml#/components/schemas/Describable
- type: object - type: object
description: Account-specific public attributes.
additionalProperties: false additionalProperties: false
required: required:
- login - login
@@ -13,14 +15,19 @@ components:
- isArchived - isArchived
properties: properties:
login: login:
description: Account login identifier represented as an email address.
type: string type: string
format: email format: email
locale: locale:
description: Preferred locale used for account-facing content.
type: string type: string
lastName: lastName:
description: Account holder last name.
type: string type: string
avatarUrl: avatarUrl:
description: Optional URL of the account avatar image.
type: string type: string
nullable: true nullable: true
isArchived: isArchived:
description: Indicates whether the account is archived and inactive.
type: boolean type: boolean

View File

@@ -1,12 +1,15 @@
components: components:
schemas: schemas:
ClientRefreshToken: ClientRefreshToken:
description: Refresh token bound to a specific client and device session.
allOf: allOf:
- $ref: ./session_identifier.yaml#/components/schemas/SessionIdentifier - $ref: ./session_identifier.yaml#/components/schemas/SessionIdentifier
- type: object - type: object
description: Refresh-token payload associated with the session identifier.
additionalProperties: false additionalProperties: false
required: required:
- token - token
properties: properties:
token: token:
description: Long-lived token used to obtain a new access token.
type: string type: string

View File

@@ -1,6 +1,7 @@
components: components:
schemas: schemas:
LoginData: LoginData:
description: Credentials and optional locale used during user sign-in.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
@@ -8,10 +9,13 @@ components:
- password - password
properties: properties:
login: login:
description: User login identifier, represented as an email address.
type: string type: string
format: email format: email
password: password:
description: User password submitted for authentication.
type: string type: string
format: password format: password
locale: locale:
description: Optional locale preference provided at login time.
type: string type: string

View File

@@ -1,6 +1,7 @@
components: components:
schemas: schemas:
SessionIdentifier: SessionIdentifier:
description: Identifies an authentication session by client and device.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
@@ -8,6 +9,8 @@ components:
- deviceId - deviceId
properties: properties:
clientId: clientId:
description: Client application identifier associated with the session.
type: string type: string
deviceId: deviceId:
description: Device/server identifier associated with the client session.
type: string type: string

View File

@@ -1,6 +1,7 @@
components: components:
schemas: schemas:
TokenData: TokenData:
description: Authentication token payload with expiration metadata.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
@@ -8,7 +9,9 @@ components:
- expiration - expiration
properties: properties:
token: token:
description: Issued authentication token value.
type: string type: string
expiration: expiration:
description: RFC 3339 timestamp when the token expires.
type: string type: string
format: date-time format: date-time

View File

@@ -1,67 +1,89 @@
components: components:
schemas: schemas:
CallbackRetryPolicy: CallbackBackoff:
description: Backoff window used between callback retry attempts.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
- minDelayMs - minDelayMs
- maxDelayMs - maxDelayMs
properties:
minDelayMs:
description: Minimum delay in milliseconds before scheduling a retry attempt.
type: integer
minimum: 1
maxDelayMs:
description: Maximum delay in milliseconds before scheduling a retry attempt.
type: integer
minimum: 1
CallbackRetryPolicy:
description: Retry and delivery behavior for callback requests.
type: object
additionalProperties: false
required:
- backoff
- signingMode - signingMode
- maxAttempts - maxAttempts
- requestTimeoutMs - requestTimeoutMs
properties: properties:
minDelayMs: backoff:
type: integer description: Delay boundaries used to compute retry backoff between attempts.
minimum: 1 $ref: '#/components/schemas/CallbackBackoff'
maxDelayMs:
type: integer
minimum: 1
signingMode: signingMode:
description: Request-signing strategy applied when dispatching callback payloads.
type: string type: string
enum: enum:
- none - none
- hmac_sha256 - hmac_sha256
secretRef:
type: string
nullable: true
headers: headers:
description: Additional HTTP headers included with every callback request.
type: object type: object
additionalProperties: additionalProperties:
type: string type: string
maxAttempts: maxAttempts:
description: Maximum number of delivery attempts before marking dispatch as failed.
type: integer type: integer
minimum: 1 minimum: 1
requestTimeoutMs: requestTimeoutMs:
description: Per-request timeout in milliseconds for callback HTTP calls.
type: integer type: integer
minimum: 1 minimum: 1
Callback: CallbackContent:
description: Callback endpoint configuration and delivery parameters.
allOf: allOf:
- $ref: ../permission_bound.yaml#/components/schemas/PermissionBound
- $ref: ../common/describable.yaml#/components/schemas/Describable
- type: object - type: object
additionalProperties: false additionalProperties: false
required: required:
- clientId
- status - status
- url - url
- eventTypes - eventTypes
- retryPolicy - retryPolicy
properties: properties:
clientId:
type: string
status: status:
description: Operational state controlling whether callback delivery is enabled.
type: string type: string
enum: enum:
- active - active
- disabled - disabled
url: url:
description: Absolute HTTPS endpoint URL that receives callback events.
type: string type: string
format: uri format: uri
eventTypes: eventTypes:
description: Event type names that trigger callback delivery to this endpoint.
type: array type: array
items: items:
type: string type: string
retryPolicy: retryPolicy:
description: Retry and timeout parameters used when dispatching callback events.
$ref: '#/components/schemas/CallbackRetryPolicy' $ref: '#/components/schemas/CallbackRetryPolicy'
Callback:
description: Stored callback configuration bound to an organization permission scope.
allOf:
- $ref: ../permission_bound.yaml#/components/schemas/PermissionBound
- $ref: ../common/describable.yaml#/components/schemas/Describable
- $ref: '#/components/schemas/CallbackContent'

View File

@@ -1,13 +1,16 @@
components: components:
schemas: schemas:
Describable: Describable:
description: Common naming metadata for user-defined entities.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
- name - name
properties: properties:
name: name:
description: Human-readable name of the entity.
type: string type: string
description: description:
description: Optional free-form description of the entity.
type: string type: string
nullable: true nullable: true

View File

@@ -1,6 +1,7 @@
components: components:
schemas: schemas:
Money: Money:
description: Monetary value represented by a decimal amount and ISO currency code.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
@@ -12,5 +13,6 @@ components:
description: Decimal string amount. description: Decimal string amount.
example: '125.50' example: '125.50'
currency: currency:
description: ISO 4217 currency code for the provided amount.
type: string type: string
example: USD example: USD

View File

@@ -1,8 +1,10 @@
components: components:
schemas: schemas:
CursorPageResponse: CursorPageResponse:
description: Cursor-based pagination metadata returned with list responses.
type: object type: object
additionalProperties: false additionalProperties: false
properties: properties:
next_cursor: next_cursor:
description: Opaque cursor to request the next page; omitted when no more items exist.
type: string type: string

View File

@@ -1,7 +1,9 @@
components: components:
schemas: schemas:
ObjectId: ObjectId:
description: Object identifier represented as a 24-character hexadecimal string.
type: string type: string
format: objectid
pattern: '^[a-fA-F0-9]{24}$' pattern: '^[a-fA-F0-9]{24}$'
examples: examples:
- 64f85f5f4c7dbf7cfb8f3f10 - 64f85f5f4c7dbf7cfb8f3f10

View File

@@ -7,11 +7,8 @@ components:
- type: object - type: object
additionalProperties: false additionalProperties: false
required: required:
- tenantRef
- timeZone - timeZone
properties: properties:
tenantRef:
$ref: ../objectid.yaml#/components/schemas/ObjectId
timeZone: timeZone:
type: string type: string
logoUrl: logoUrl:

View File

@@ -1,39 +1,50 @@
components: components:
schemas: schemas:
Asset: Asset:
description: Blockchain asset identifier used for on-chain transfers.
type: object type: object
additionalProperties: false additionalProperties: false
properties: properties:
chain: chain:
description: Chain/network where the asset exists.
$ref: ../../external/chain_network.yaml#/components/schemas/ChainNetwork $ref: ../../external/chain_network.yaml#/components/schemas/ChainNetwork
token_symbol: token_symbol:
description: Symbol of the token on the selected chain.
type: string type: string
contract_address: contract_address:
description: Smart-contract address for tokenized assets.
type: string type: string
LedgerEndpoint: LedgerEndpoint:
description: Internal ledger-based endpoint used as payment source or destination.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
- ledger_account_ref - ledger_account_ref
properties: properties:
ledger_account_ref: ledger_account_ref:
description: Reference of the primary ledger account.
type: string type: string
contra_ledger_account_ref: contra_ledger_account_ref:
description: Optional contra ledger account reference for balancing entries.
type: string type: string
ManagedWalletEndpoint: ManagedWalletEndpoint:
description: Endpoint referencing a managed wallet within the platform.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
- managed_wallet_ref - managed_wallet_ref
properties: properties:
managed_wallet_ref: managed_wallet_ref:
description: Reference of the managed wallet.
type: string type: string
asset: asset:
description: Asset details for the managed wallet transfer.
$ref: ./payment.yaml#/components/schemas/Asset $ref: ./payment.yaml#/components/schemas/Asset
ExternalChainEndpoint: ExternalChainEndpoint:
description: Endpoint representing an external blockchain address.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
@@ -41,56 +52,72 @@ components:
- address - address
properties: properties:
asset: asset:
description: Asset to be transferred to or from the external address.
$ref: ./payment.yaml#/components/schemas/Asset $ref: ./payment.yaml#/components/schemas/Asset
address: address:
description: Target blockchain address.
type: string type: string
memo: memo:
description: Optional destination memo or tag required by some chains.
type: string type: string
CardEndpoint: CardEndpoint:
description: Raw card details endpoint used for card-based payment flows.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
- pan - pan
properties: properties:
pan: pan:
description: Primary account number of the card.
type: string type: string
firstName: firstName:
description: Cardholder first name.
type: string type: string
lastName: lastName:
description: Cardholder last name.
type: string type: string
exp_month: exp_month:
description: Card expiration month.
type: integer type: integer
format: int32 format: int32
minimum: 0 minimum: 0
exp_year: exp_year:
description: Card expiration year.
type: integer type: integer
format: int32 format: int32
minimum: 0 minimum: 0
country: country:
description: Card issuing country code.
type: string type: string
CardTokenEndpoint: CardTokenEndpoint:
description: Tokenized card endpoint used instead of raw PAN details.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
- token - token
properties: properties:
token: token:
description: Provider-issued card token.
type: string type: string
masked_pan: masked_pan:
description: Masked PAN representation for display and auditing.
type: string type: string
WalletEndpoint: WalletEndpoint:
description: Generic wallet endpoint identified by wallet ID.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
- walletId - walletId
properties: properties:
walletId: walletId:
description: Unique identifier of the wallet.
type: string type: string
Endpoint: Endpoint:
description: Polymorphic payment endpoint definition.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
@@ -98,8 +125,10 @@ components:
- data - data
properties: properties:
type: type:
description: Endpoint type determining the shape of the `data` field.
$ref: ../../external/endpoint_type.yaml#/components/schemas/EndpointType $ref: ../../external/endpoint_type.yaml#/components/schemas/EndpointType
data: data:
description: Endpoint payload corresponding to the selected endpoint type.
oneOf: oneOf:
- $ref: ./payment.yaml#/components/schemas/LedgerEndpoint - $ref: ./payment.yaml#/components/schemas/LedgerEndpoint
- $ref: ./payment.yaml#/components/schemas/ManagedWalletEndpoint - $ref: ./payment.yaml#/components/schemas/ManagedWalletEndpoint
@@ -108,36 +137,49 @@ components:
- $ref: ./payment.yaml#/components/schemas/CardTokenEndpoint - $ref: ./payment.yaml#/components/schemas/CardTokenEndpoint
- $ref: ./payment.yaml#/components/schemas/WalletEndpoint - $ref: ./payment.yaml#/components/schemas/WalletEndpoint
metadata: metadata:
description: Optional key-value metadata attached to the endpoint.
type: object type: object
additionalProperties: additionalProperties:
type: string type: string
Customer: Customer:
description: Customer identity and address attributes for compliance and routing.
type: object type: object
additionalProperties: false additionalProperties: false
properties: properties:
id: id:
description: External or internal customer identifier.
type: string type: string
first_name: first_name:
description: Customer first name.
type: string type: string
middle_name: middle_name:
description: Customer middle name.
type: string type: string
last_name: last_name:
description: Customer last name.
type: string type: string
ip: ip:
description: Source IP address associated with the payment request.
type: string type: string
zip: zip:
description: Postal or ZIP code.
type: string type: string
country: country:
description: Country code of customer residence or billing address.
type: string type: string
state: state:
description: State or region.
type: string type: string
city: city:
description: City name.
type: string type: string
address: address:
description: Street address line.
type: string type: string
CurrencyPair: CurrencyPair:
description: Foreign-exchange currency pair.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
@@ -145,11 +187,14 @@ components:
- quote - quote
properties: properties:
base: base:
description: Base currency code.
type: string type: string
quote: quote:
description: Quote currency code.
type: string type: string
FxIntent: FxIntent:
description: FX execution preferences associated with a payment.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
@@ -157,21 +202,28 @@ components:
- side - side
properties: properties:
pair: pair:
description: Currency pair for FX conversion.
$ref: ./payment.yaml#/components/schemas/CurrencyPair $ref: ./payment.yaml#/components/schemas/CurrencyPair
side: side:
description: Side of the FX trade intent.
$ref: ../../external/fx_side.yaml#/components/schemas/FxSide $ref: ../../external/fx_side.yaml#/components/schemas/FxSide
firm: firm:
description: Whether a firm quote is required.
type: boolean type: boolean
ttl_ms: ttl_ms:
description: Desired quote time-to-live in milliseconds.
type: integer type: integer
format: int64 format: int64
preferred_provider: preferred_provider:
description: Preferred FX liquidity provider identifier.
type: string type: string
max_age_ms: max_age_ms:
description: Maximum accepted quote age in milliseconds.
type: integer type: integer
format: int32 format: int32
PaymentIntent: PaymentIntent:
description: Requested payment execution parameters before processing.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
@@ -181,160 +233,219 @@ components:
- amount - amount
properties: properties:
kind: kind:
description: High-level payment kind that drives orchestration behavior.
$ref: ../../external/payment_kind.yaml#/components/schemas/PaymentKind $ref: ../../external/payment_kind.yaml#/components/schemas/PaymentKind
source: source:
description: Source endpoint from which funds are debited.
$ref: ./payment.yaml#/components/schemas/Endpoint $ref: ./payment.yaml#/components/schemas/Endpoint
destination: destination:
description: Destination endpoint to which funds are credited.
$ref: ./payment.yaml#/components/schemas/Endpoint $ref: ./payment.yaml#/components/schemas/Endpoint
amount: amount:
description: Requested payment amount.
$ref: ../common/money.yaml#/components/schemas/Money $ref: ../common/money.yaml#/components/schemas/Money
fx: fx:
description: Optional FX preferences when conversion is needed.
$ref: ./payment.yaml#/components/schemas/FxIntent $ref: ./payment.yaml#/components/schemas/FxIntent
settlement_mode: settlement_mode:
description: Settlement strategy for the transfer.
$ref: ../../external/settlement_mode.yaml#/components/schemas/SettlementMode $ref: ../../external/settlement_mode.yaml#/components/schemas/SettlementMode
fee_treatment: fee_treatment:
description: How fees should be applied to source/destination amounts.
type: string type: string
enum: enum:
- unspecified - unspecified
- add_to_source - add_to_source
- deduct_from_destination - deduct_from_destination
attributes: attributes:
description: Additional key-value attributes used by downstream processors.
type: object type: object
additionalProperties: additionalProperties:
type: string type: string
customer: customer:
description: Optional customer information attached to the payment intent.
$ref: ./payment.yaml#/components/schemas/Customer $ref: ./payment.yaml#/components/schemas/Customer
FeeLine: FeeLine:
description: Single fee component included in a payment quote.
type: object type: object
additionalProperties: false additionalProperties: false
properties: properties:
ledgerAccountRef: ledgerAccountRef:
description: Ledger account reference receiving or charging this fee.
type: string type: string
amount: amount:
description: Monetary amount of the fee line.
$ref: ../common/money.yaml#/components/schemas/Money $ref: ../common/money.yaml#/components/schemas/Money
lineType: lineType:
description: Fee line classification.
type: string type: string
side: side:
description: Indicates whether the fee applies on debit or credit side.
type: string type: string
meta: meta:
description: Optional metadata for fee calculation traceability.
type: object type: object
additionalProperties: additionalProperties:
type: string type: string
QuoteFees: QuoteFees:
description: Collection of fee lines for a quoted payment.
type: object type: object
additionalProperties: false additionalProperties: false
properties: properties:
lines: lines:
description: List of individual quoted fee lines.
type: array type: array
items: items:
$ref: ./payment.yaml#/components/schemas/FeeLine $ref: ./payment.yaml#/components/schemas/FeeLine
QuoteAmounts: QuoteAmounts:
description: Amount breakdown returned with a payment quote.
type: object type: object
additionalProperties: false additionalProperties: false
properties: properties:
sourcePrincipal: sourcePrincipal:
description: Principal amount debited from the source before fees.
$ref: ../common/money.yaml#/components/schemas/Money $ref: ../common/money.yaml#/components/schemas/Money
sourceDebitTotal: sourceDebitTotal:
description: Total amount debited from the source including adjustments.
$ref: ../common/money.yaml#/components/schemas/Money $ref: ../common/money.yaml#/components/schemas/Money
destinationSettlement: destinationSettlement:
description: Net amount expected to settle at the destination.
$ref: ../common/money.yaml#/components/schemas/Money $ref: ../common/money.yaml#/components/schemas/Money
FxQuote: FxQuote:
description: FX quote details used to price converted payment amounts.
type: object type: object
additionalProperties: false additionalProperties: false
properties: properties:
quoteRef: quoteRef:
description: Identifier of the FX quote.
type: string type: string
baseCurrency: baseCurrency:
description: Base currency of the quote pair.
type: string type: string
quoteCurrency: quoteCurrency:
description: Quote currency of the quote pair.
type: string type: string
side: side:
description: FX side used for pricing.
type: string type: string
price: price:
description: Quoted FX price as a decimal string.
type: string type: string
baseAmount: baseAmount:
description: Base currency amount covered by this quote.
$ref: ../common/money.yaml#/components/schemas/Money $ref: ../common/money.yaml#/components/schemas/Money
quoteAmount: quoteAmount:
description: Quote currency amount corresponding to the base amount.
$ref: ../common/money.yaml#/components/schemas/Money $ref: ../common/money.yaml#/components/schemas/Money
expiresAtUnixMs: expiresAtUnixMs:
description: Quote expiration timestamp in Unix milliseconds.
type: integer type: integer
format: int64 format: int64
pricedAtUnixMs: pricedAtUnixMs:
description: Quote pricing timestamp in Unix milliseconds.
type: integer type: integer
format: int64 format: int64
provider: provider:
description: Provider that supplied the FX quote.
type: string type: string
rateRef: rateRef:
description: Provider-specific rate reference identifier.
type: string type: string
firm: firm:
description: Indicates whether the FX quote is firm.
type: boolean type: boolean
PaymentQuote: PaymentQuote:
description: Pricing result for a specific payment intent.
type: object type: object
additionalProperties: false additionalProperties: false
properties: properties:
quoteRef: quoteRef:
description: Unique identifier of the payment quote.
type: string type: string
intentRef: intentRef:
description: Reference to the payment intent used for quoting.
type: string type: string
amounts: amounts:
description: Amount breakdown for source and destination values.
$ref: ./payment.yaml#/components/schemas/QuoteAmounts $ref: ./payment.yaml#/components/schemas/QuoteAmounts
fees: fees:
description: Fee breakdown included in the quote.
$ref: ./payment.yaml#/components/schemas/QuoteFees $ref: ./payment.yaml#/components/schemas/QuoteFees
fxQuote: fxQuote:
description: Associated FX quote when conversion is part of the payment.
$ref: ./payment.yaml#/components/schemas/FxQuote $ref: ./payment.yaml#/components/schemas/FxQuote
PaymentOperation: PaymentOperation:
description: Execution step record captured during payment processing.
type: object type: object
additionalProperties: false additionalProperties: false
properties: properties:
stepRef: stepRef:
description: Identifier of the orchestration step.
type: string type: string
code: code:
description: Operation code representing the executed action.
type: string type: string
state: state:
description: Current state of the operation.
type: string type: string
label: label:
description: Human-readable operation label.
type: string type: string
failureCode: failureCode:
description: Machine-readable failure code when operation fails.
type: string type: string
failureReason: failureReason:
description: Human-readable failure reason when operation fails.
type: string type: string
startedAt: startedAt:
description: RFC 3339 timestamp when execution of the operation started.
type: string type: string
format: date-time format: date-time
completedAt: completedAt:
description: RFC 3339 timestamp when execution of the operation completed.
type: string type: string
format: date-time format: date-time
Payment: Payment:
description: Persisted payment aggregate with status, quote, and operation history.
type: object type: object
additionalProperties: false additionalProperties: false
properties: properties:
paymentRef: paymentRef:
description: Unique payment reference identifier.
type: string type: string
idempotencyKey: idempotencyKey:
description: Idempotency key used to safely deduplicate create requests.
type: string type: string
state: state:
description: Current lifecycle state of the payment.
$ref: ../../external/payment_state.yaml#/components/schemas/PaymentState $ref: ../../external/payment_state.yaml#/components/schemas/PaymentState
failureCode: failureCode:
description: Failure code set when the payment cannot be completed.
type: string type: string
failureReason: failureReason:
description: Human-readable explanation of the failure.
type: string type: string
operations: operations:
description: Chronological list of operations executed for this payment.
type: array type: array
items: items:
$ref: ./payment.yaml#/components/schemas/PaymentOperation $ref: ./payment.yaml#/components/schemas/PaymentOperation
lastQuote: lastQuote:
description: Most recent payment quote used or generated for the payment.
$ref: ./payment.yaml#/components/schemas/PaymentQuote $ref: ./payment.yaml#/components/schemas/PaymentQuote
createdAt: createdAt:
description: RFC 3339 timestamp when the payment was created.
type: string type: string
format: date-time format: date-time
meta: meta:
description: Additional metadata captured for the payment.
type: object type: object
additionalProperties: additionalProperties:
type: string type: string

View File

@@ -1,9 +1,11 @@
components: components:
schemas: schemas:
PermissionBound: PermissionBound:
description: Association between an organization and a permission, including archival state.
allOf: allOf:
- $ref: ./storable.yaml#/components/schemas/Storable - $ref: ./storable.yaml#/components/schemas/Storable
- type: object - type: object
description: Permission binding details scoped to an organization.
additionalProperties: false additionalProperties: false
required: required:
- organizationRef - organizationRef
@@ -11,8 +13,11 @@ components:
- isArchived - isArchived
properties: properties:
organizationRef: organizationRef:
description: Identifier of the organization that owns this permission binding.
$ref: ./objectid.yaml#/components/schemas/ObjectId $ref: ./objectid.yaml#/components/schemas/ObjectId
permissionRef: permissionRef:
description: Identifier of the permission granted to the organization.
$ref: ./objectid.yaml#/components/schemas/ObjectId $ref: ./objectid.yaml#/components/schemas/ObjectId
isArchived: isArchived:
description: Indicates whether this object is archived and inactive.
type: boolean type: boolean

View File

@@ -1,6 +1,7 @@
components: components:
schemas: schemas:
Storable: Storable:
description: Common persistence metadata included on stored entities.
type: object type: object
additionalProperties: false additionalProperties: false
required: required:
@@ -9,10 +10,13 @@ components:
- updatedAt - updatedAt
properties: properties:
id: id:
description: Unique identifier assigned to the stored entity.
$ref: ./objectid.yaml#/components/schemas/ObjectId $ref: ./objectid.yaml#/components/schemas/ObjectId
createdAt: createdAt:
description: RFC 3339 timestamp indicating when the entity was created.
type: string type: string
format: date-time format: date-time
updatedAt: updatedAt:
description: RFC 3339 timestamp indicating the most recent entity update.
type: string type: string
format: date-time format: date-time