package callbacksimp import ( "context" api "github.com/tech/sendico/pkg/api/http" "github.com/tech/sendico/pkg/db/callbacks" "github.com/tech/sendico/pkg/merrors" "github.com/tech/sendico/pkg/model" "github.com/tech/sendico/pkg/mservice" eapi "github.com/tech/sendico/server/interface/api" "github.com/tech/sendico/server/internal/server/papitemplate" "go.uber.org/zap" ) type CallbacksAPI struct { papitemplate.ProtectedAPI[model.Callback] db callbacks.DB secrets signingSecretManager config callbacksConfig } func (a *CallbacksAPI) Name() mservice.Type { return mservice.Callbacks } func (a *CallbacksAPI) Finish(_ context.Context) error { return nil } func CreateAPI(apiCtx eapi.API) (*CallbacksAPI, error) { dbFactory := func() (papitemplate.ProtectedDB[model.Callback], error) { return apiCtx.DBFactory().NewCallbacksDB() } res := &CallbacksAPI{ config: newCallbacksConfig(apiCtx.Config().Callbacks), } p, err := papitemplate.CreateAPI(apiCtx, dbFactory, mservice.Organizations, mservice.Callbacks) if err != nil { return nil, err } res.ProtectedAPI = *p. WithNoCreateNotification(). WithNoUpdateNotification(). WithNoDeleteNotification(). WithCreateHandler(res.create). WithUpdateHandler(res.update). Build() if res.db, err = apiCtx.DBFactory().NewCallbacksDB(); err != nil { res.Logger.Warn("Failed to create callbacks database", zap.Error(err)) return nil, err } if res.secrets, err = newSigningSecretManager(res.Logger, res.config); err != nil { res.Logger.Warn("Failed to initialize callbacks signing secret manager", zap.Error(err)) return nil, err } apiCtx.Register().AccountHandler(res.Name(), res.Cph.AddRef("/rotate-secret"), api.Post, res.rotateSecret) return res, nil } const ( defaultCallbackStatus = model.CallbackStatusActive defaultRetryMaxAttempts = 8 defaultRetryMinDelayMS = 1000 defaultRetryMaxDelayMS = 300000 defaultRetryRequestTimeoutMS = 10000 defaultSigningSecretLengthBytes = 32 defaultSigningSecretField = "value" defaultSigningSecretPathPrefix = "sendico/callbacks" ) type callbacksConfig struct { DefaultEventTypes []string DefaultStatus model.CallbackStatus SecretPathPrefix string SecretField string SecretLengthBytes int Vault VaultConfig } type VaultConfig struct { Address string TokenEnv string TokenFileEnv string TokenFile string Namespace string MountPath string } func newCallbacksConfig(source *eapi.CallbacksConfig) callbacksConfig { cfg := callbacksConfig{ DefaultEventTypes: []string{model.PaymentStatusUpdatedType}, DefaultStatus: defaultCallbackStatus, SecretPathPrefix: defaultSigningSecretPathPrefix, SecretField: defaultSigningSecretField, SecretLengthBytes: defaultSigningSecretLengthBytes, } if source == nil { return cfg } if source.SecretPathPrefix != "" { cfg.SecretPathPrefix = source.SecretPathPrefix } if source.SecretField != "" { cfg.SecretField = source.SecretField } if source.SecretLengthBytes > 0 { cfg.SecretLengthBytes = source.SecretLengthBytes } if len(source.DefaultEventTypes) > 0 { cfg.DefaultEventTypes = source.DefaultEventTypes } if source.DefaultStatus != "" { cfg.DefaultStatus = model.CallbackStatus(source.DefaultStatus) } cfg.Vault = VaultConfig{ Address: source.Vault.Address, TokenEnv: source.Vault.TokenEnv, TokenFileEnv: source.Vault.TokenFileEnv, TokenFile: source.Vault.TokenFile, Namespace: source.Vault.Namespace, MountPath: source.Vault.MountPath, } return cfg } func (c callbacksConfig) validate() error { if c.SecretLengthBytes <= 0 { return merrors.InvalidArgument("callbacks signing secret length must be greater than zero", "api.callbacks.secret_length_bytes") } return nil }