mirror of
https://github.com/hanzo-fi/payments.git
synced 2026-10-30 20:31:20 +00:00
- go.mod module path + all self-imports: github.com/formancehq/payments -> github.com/hanzo-fi/payments (1590 .go files / 3152 refs; 20 modules: root, pkg/client, pkg/domain, 18 ce/plugins). External dep kept: formancehq/go-libs. - plugins: each ce/plugins/* consumed a *published* pkg/domain; added local 'replace .../pkg/domain => ../../../pkg/domain' (consistent with root) so every standalone plugin module builds in-monorepo. compile-plugin.sh MOD var rebranded too. - branding: README, LICENSE (dual MIT+EE; upstream copyright retained + Hanzo added), goreleaser (release images -> registry.hanzo.ai/hanzo-fi, OCI labels, homepage), proto go_package, renovate, speakeasy gen.yaml, connector-template *.gotpl. - CI: removed Numary/Formance workflows + composite action; added canonical hanzo.yml + cicd.yml (hanzoai/ci, gated HANZO_CI_ENABLED) + secretless ci.yml (go build/vet). - kept as data/compat keys: wire capability enums ALLOW_FORMANCE_*, generated SDK type client.Formance, domain field FormanceRedirectURL, OTEL_SERVICE_NAME, upstream base image. - verified: go build ./... (default + -tags ee) + all 20 nested modules green.
536 lines
14 KiB
Go
536 lines
14 KiB
Go
package workbench
|
|
|
|
import (
|
|
"encoding/json"
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/hanzo-fi/payments/pkg/domain/models"
|
|
)
|
|
|
|
// MemoryStorage provides an in-memory implementation of storage for the workbench.
|
|
// It stores only the data needed for connector development and testing.
|
|
type MemoryStorage struct {
|
|
mu sync.RWMutex
|
|
|
|
// Core data
|
|
accounts map[string]models.PSPAccount
|
|
payments map[string]models.PSPPayment
|
|
balances map[string]models.PSPBalance
|
|
orders map[string]models.PSPOrder
|
|
conversions map[string]models.PSPConversion
|
|
others map[string][]models.PSPOther
|
|
|
|
// External accounts (beneficiaries, etc.)
|
|
externalAccounts map[string]models.PSPAccount
|
|
|
|
// State management
|
|
states map[string]json.RawMessage
|
|
|
|
// Workflow tree from install
|
|
tasksTree *models.ConnectorTasksTree
|
|
|
|
// Webhook configs
|
|
webhookConfigs []models.PSPWebhookConfig
|
|
|
|
// Statistics
|
|
stats StorageStats
|
|
}
|
|
|
|
// StorageStats tracks storage statistics.
|
|
type StorageStats struct {
|
|
AccountsCount int
|
|
PaymentsCount int
|
|
BalancesCount int
|
|
OrdersCount int
|
|
ConversionsCount int
|
|
ExternalAccountsCount int
|
|
LastUpdated time.Time
|
|
}
|
|
|
|
// NewMemoryStorage creates a new in-memory storage.
|
|
func NewMemoryStorage() *MemoryStorage {
|
|
return &MemoryStorage{
|
|
accounts: make(map[string]models.PSPAccount),
|
|
payments: make(map[string]models.PSPPayment),
|
|
balances: make(map[string]models.PSPBalance),
|
|
orders: make(map[string]models.PSPOrder),
|
|
conversions: make(map[string]models.PSPConversion),
|
|
others: make(map[string][]models.PSPOther),
|
|
externalAccounts: make(map[string]models.PSPAccount),
|
|
states: make(map[string]json.RawMessage),
|
|
}
|
|
}
|
|
|
|
// Clear clears all stored data.
|
|
func (s *MemoryStorage) Clear() {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
s.accounts = make(map[string]models.PSPAccount)
|
|
s.payments = make(map[string]models.PSPPayment)
|
|
s.balances = make(map[string]models.PSPBalance)
|
|
s.orders = make(map[string]models.PSPOrder)
|
|
s.conversions = make(map[string]models.PSPConversion)
|
|
s.others = make(map[string][]models.PSPOther)
|
|
s.externalAccounts = make(map[string]models.PSPAccount)
|
|
s.states = make(map[string]json.RawMessage)
|
|
s.tasksTree = nil
|
|
s.webhookConfigs = nil
|
|
s.stats = StorageStats{}
|
|
}
|
|
|
|
// === Accounts ===
|
|
|
|
// StoreAccounts stores accounts.
|
|
func (s *MemoryStorage) StoreAccounts(accounts []models.PSPAccount) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
for _, acc := range accounts {
|
|
s.accounts[acc.Reference] = acc
|
|
}
|
|
s.stats.AccountsCount = len(s.accounts)
|
|
s.stats.LastUpdated = time.Now()
|
|
}
|
|
|
|
// GetAccounts returns all accounts sorted by creation time, then reference.
|
|
func (s *MemoryStorage) GetAccounts() []models.PSPAccount {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
accounts := make([]models.PSPAccount, 0, len(s.accounts))
|
|
for _, acc := range s.accounts {
|
|
accounts = append(accounts, acc)
|
|
}
|
|
sort.Slice(accounts, func(i, j int) bool {
|
|
if !accounts[i].CreatedAt.Equal(accounts[j].CreatedAt) {
|
|
return accounts[i].CreatedAt.Before(accounts[j].CreatedAt)
|
|
}
|
|
return accounts[i].Reference < accounts[j].Reference
|
|
})
|
|
return accounts
|
|
}
|
|
|
|
// GetAccount returns a specific account by reference.
|
|
func (s *MemoryStorage) GetAccount(reference string) (models.PSPAccount, bool) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
acc, ok := s.accounts[reference]
|
|
return acc, ok
|
|
}
|
|
|
|
// === External Accounts ===
|
|
|
|
// StoreExternalAccounts stores external accounts.
|
|
func (s *MemoryStorage) StoreExternalAccounts(accounts []models.PSPAccount) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
for _, acc := range accounts {
|
|
s.externalAccounts[acc.Reference] = acc
|
|
}
|
|
s.stats.ExternalAccountsCount = len(s.externalAccounts)
|
|
s.stats.LastUpdated = time.Now()
|
|
}
|
|
|
|
// GetExternalAccounts returns all external accounts sorted by creation time, then reference.
|
|
func (s *MemoryStorage) GetExternalAccounts() []models.PSPAccount {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
accounts := make([]models.PSPAccount, 0, len(s.externalAccounts))
|
|
for _, acc := range s.externalAccounts {
|
|
accounts = append(accounts, acc)
|
|
}
|
|
sort.Slice(accounts, func(i, j int) bool {
|
|
if !accounts[i].CreatedAt.Equal(accounts[j].CreatedAt) {
|
|
return accounts[i].CreatedAt.Before(accounts[j].CreatedAt)
|
|
}
|
|
return accounts[i].Reference < accounts[j].Reference
|
|
})
|
|
return accounts
|
|
}
|
|
|
|
// === Payments ===
|
|
|
|
// StorePayments stores payments.
|
|
func (s *MemoryStorage) StorePayments(payments []models.PSPPayment) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
for _, p := range payments {
|
|
s.payments[p.Reference] = p
|
|
}
|
|
s.stats.PaymentsCount = len(s.payments)
|
|
s.stats.LastUpdated = time.Now()
|
|
}
|
|
|
|
// GetPayments returns all payments sorted by creation time, then reference.
|
|
func (s *MemoryStorage) GetPayments() []models.PSPPayment {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
payments := make([]models.PSPPayment, 0, len(s.payments))
|
|
for _, p := range s.payments {
|
|
payments = append(payments, p)
|
|
}
|
|
sort.Slice(payments, func(i, j int) bool {
|
|
if !payments[i].CreatedAt.Equal(payments[j].CreatedAt) {
|
|
return payments[i].CreatedAt.Before(payments[j].CreatedAt)
|
|
}
|
|
return payments[i].Reference < payments[j].Reference
|
|
})
|
|
return payments
|
|
}
|
|
|
|
// GetPayment returns a specific payment by reference.
|
|
func (s *MemoryStorage) GetPayment(reference string) (models.PSPPayment, bool) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
p, ok := s.payments[reference]
|
|
return p, ok
|
|
}
|
|
|
|
// === Balances ===
|
|
|
|
// StoreBalances stores balances.
|
|
func (s *MemoryStorage) StoreBalances(balances []models.PSPBalance) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
for _, b := range balances {
|
|
key := b.AccountReference + "/" + b.Asset
|
|
s.balances[key] = b
|
|
}
|
|
s.stats.BalancesCount = len(s.balances)
|
|
s.stats.LastUpdated = time.Now()
|
|
}
|
|
|
|
// GetBalances returns all balances.
|
|
func (s *MemoryStorage) GetBalances() []models.PSPBalance {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
balances := make([]models.PSPBalance, 0, len(s.balances))
|
|
for _, b := range s.balances {
|
|
balances = append(balances, b)
|
|
}
|
|
sort.Slice(balances, func(i, j int) bool {
|
|
if balances[i].AccountReference != balances[j].AccountReference {
|
|
return balances[i].AccountReference < balances[j].AccountReference
|
|
}
|
|
if !balances[i].CreatedAt.Equal(balances[j].CreatedAt) {
|
|
return balances[i].CreatedAt.Before(balances[j].CreatedAt)
|
|
}
|
|
return balances[i].Asset < balances[j].Asset
|
|
})
|
|
return balances
|
|
}
|
|
|
|
// GetBalancesForAccount returns balances for a specific account.
|
|
func (s *MemoryStorage) GetBalancesForAccount(accountRef string) []models.PSPBalance {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
var balances []models.PSPBalance
|
|
for _, b := range s.balances {
|
|
if b.AccountReference == accountRef {
|
|
balances = append(balances, b)
|
|
}
|
|
}
|
|
sort.Slice(balances, func(i, j int) bool {
|
|
if !balances[i].CreatedAt.Equal(balances[j].CreatedAt) {
|
|
return balances[i].CreatedAt.Before(balances[j].CreatedAt)
|
|
}
|
|
return balances[i].Asset < balances[j].Asset
|
|
})
|
|
return balances
|
|
}
|
|
|
|
// === Orders ===
|
|
|
|
// StoreOrders stores orders.
|
|
func (s *MemoryStorage) StoreOrders(orders []models.PSPOrder) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
for _, o := range orders {
|
|
s.orders[o.Reference] = o
|
|
}
|
|
s.stats.OrdersCount = len(s.orders)
|
|
s.stats.LastUpdated = time.Now()
|
|
}
|
|
|
|
// GetOrders returns all orders sorted by creation time, then reference.
|
|
func (s *MemoryStorage) GetOrders() []models.PSPOrder {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
orders := make([]models.PSPOrder, 0, len(s.orders))
|
|
for _, o := range s.orders {
|
|
orders = append(orders, o)
|
|
}
|
|
sort.Slice(orders, func(i, j int) bool {
|
|
if !orders[i].CreatedAt.Equal(orders[j].CreatedAt) {
|
|
return orders[i].CreatedAt.Before(orders[j].CreatedAt)
|
|
}
|
|
return orders[i].Reference < orders[j].Reference
|
|
})
|
|
return orders
|
|
}
|
|
|
|
// GetOrder returns a specific order by reference.
|
|
func (s *MemoryStorage) GetOrder(reference string) (models.PSPOrder, bool) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
o, ok := s.orders[reference]
|
|
return o, ok
|
|
}
|
|
|
|
// === Conversions ===
|
|
|
|
// StoreConversions stores conversions.
|
|
func (s *MemoryStorage) StoreConversions(conversions []models.PSPConversion) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
for _, c := range conversions {
|
|
s.conversions[c.Reference] = c
|
|
}
|
|
s.stats.ConversionsCount = len(s.conversions)
|
|
s.stats.LastUpdated = time.Now()
|
|
}
|
|
|
|
// GetConversions returns all conversions sorted by creation time, then reference.
|
|
func (s *MemoryStorage) GetConversions() []models.PSPConversion {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
conversions := make([]models.PSPConversion, 0, len(s.conversions))
|
|
for _, c := range s.conversions {
|
|
conversions = append(conversions, c)
|
|
}
|
|
sort.Slice(conversions, func(i, j int) bool {
|
|
if !conversions[i].CreatedAt.Equal(conversions[j].CreatedAt) {
|
|
return conversions[i].CreatedAt.Before(conversions[j].CreatedAt)
|
|
}
|
|
return conversions[i].Reference < conversions[j].Reference
|
|
})
|
|
return conversions
|
|
}
|
|
|
|
// GetConversion returns a specific conversion by reference.
|
|
func (s *MemoryStorage) GetConversion(reference string) (models.PSPConversion, bool) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
c, ok := s.conversions[reference]
|
|
return c, ok
|
|
}
|
|
|
|
// === Others ===
|
|
|
|
// StoreOthers stores other data by name.
|
|
func (s *MemoryStorage) StoreOthers(name string, others []models.PSPOther) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
s.others[name] = append(s.others[name], others...)
|
|
s.stats.LastUpdated = time.Now()
|
|
}
|
|
|
|
// GetOthers returns other data by name.
|
|
func (s *MemoryStorage) GetOthers(name string) []models.PSPOther {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
return s.others[name]
|
|
}
|
|
|
|
// GetAllOthers returns all other data.
|
|
func (s *MemoryStorage) GetAllOthers() map[string][]models.PSPOther {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
result := make(map[string][]models.PSPOther)
|
|
for k, v := range s.others {
|
|
result[k] = v
|
|
}
|
|
return result
|
|
}
|
|
|
|
// === State Management ===
|
|
|
|
// SaveState saves state for a given key.
|
|
func (s *MemoryStorage) SaveState(key string, state json.RawMessage) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
s.states[key] = state
|
|
}
|
|
|
|
// GetState returns state for a given key.
|
|
func (s *MemoryStorage) GetState(key string) json.RawMessage {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
return s.states[key]
|
|
}
|
|
|
|
// GetAllStates returns all states.
|
|
func (s *MemoryStorage) GetAllStates() map[string]json.RawMessage {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
result := make(map[string]json.RawMessage)
|
|
for k, v := range s.states {
|
|
result[k] = v
|
|
}
|
|
return result
|
|
}
|
|
|
|
// ClearState clears state for a given key.
|
|
func (s *MemoryStorage) ClearState(key string) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
delete(s.states, key)
|
|
}
|
|
|
|
// ClearAllStates clears all states.
|
|
func (s *MemoryStorage) ClearAllStates() {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
s.states = make(map[string]json.RawMessage)
|
|
}
|
|
|
|
// === Tasks Tree ===
|
|
|
|
// SetTasksTree sets the connector tasks tree.
|
|
func (s *MemoryStorage) SetTasksTree(tree *models.ConnectorTasksTree) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
s.tasksTree = tree
|
|
}
|
|
|
|
// GetTasksTree returns the connector tasks tree.
|
|
func (s *MemoryStorage) GetTasksTree() *models.ConnectorTasksTree {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
return s.tasksTree
|
|
}
|
|
|
|
// === Webhook Configs ===
|
|
|
|
// SetWebhookConfigs sets webhook configs.
|
|
func (s *MemoryStorage) SetWebhookConfigs(configs []models.PSPWebhookConfig) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
s.webhookConfigs = configs
|
|
}
|
|
|
|
// GetWebhookConfigs returns webhook configs.
|
|
func (s *MemoryStorage) GetWebhookConfigs() []models.PSPWebhookConfig {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
return s.webhookConfigs
|
|
}
|
|
|
|
// === Statistics ===
|
|
|
|
// GetStats returns storage statistics.
|
|
func (s *MemoryStorage) GetStats() StorageStats {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
return s.stats
|
|
}
|
|
|
|
// === Export/Import ===
|
|
|
|
// StorageSnapshot represents a snapshot of all storage data.
|
|
type StorageSnapshot struct {
|
|
Accounts map[string]models.PSPAccount `json:"accounts"`
|
|
Payments map[string]models.PSPPayment `json:"payments"`
|
|
Balances map[string]models.PSPBalance `json:"balances"`
|
|
Orders map[string]models.PSPOrder `json:"orders"`
|
|
Conversions map[string]models.PSPConversion `json:"conversions"`
|
|
ExternalAccounts map[string]models.PSPAccount `json:"external_accounts"`
|
|
Others map[string][]models.PSPOther `json:"others"`
|
|
States map[string]json.RawMessage `json:"states"`
|
|
TasksTree *models.ConnectorTasksTree `json:"tasks_tree,omitempty"`
|
|
WebhookConfigs []models.PSPWebhookConfig `json:"webhook_configs,omitempty"`
|
|
ExportedAt time.Time `json:"exported_at"`
|
|
}
|
|
|
|
// Export exports all storage data as a snapshot.
|
|
func (s *MemoryStorage) Export() StorageSnapshot {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
return StorageSnapshot{
|
|
Accounts: s.accounts,
|
|
Payments: s.payments,
|
|
Balances: s.balances,
|
|
Orders: s.orders,
|
|
Conversions: s.conversions,
|
|
ExternalAccounts: s.externalAccounts,
|
|
Others: s.others,
|
|
States: s.states,
|
|
TasksTree: s.tasksTree,
|
|
WebhookConfigs: s.webhookConfigs,
|
|
ExportedAt: time.Now(),
|
|
}
|
|
}
|
|
|
|
// Import imports storage data from a snapshot.
|
|
func (s *MemoryStorage) Import(snapshot StorageSnapshot) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
if snapshot.Accounts != nil {
|
|
s.accounts = snapshot.Accounts
|
|
}
|
|
if snapshot.Payments != nil {
|
|
s.payments = snapshot.Payments
|
|
}
|
|
if snapshot.Balances != nil {
|
|
s.balances = snapshot.Balances
|
|
}
|
|
if snapshot.Orders != nil {
|
|
s.orders = snapshot.Orders
|
|
}
|
|
if snapshot.Conversions != nil {
|
|
s.conversions = snapshot.Conversions
|
|
}
|
|
if snapshot.ExternalAccounts != nil {
|
|
s.externalAccounts = snapshot.ExternalAccounts
|
|
}
|
|
if snapshot.Others != nil {
|
|
s.others = snapshot.Others
|
|
}
|
|
if snapshot.States != nil {
|
|
s.states = snapshot.States
|
|
}
|
|
s.tasksTree = snapshot.TasksTree
|
|
s.webhookConfigs = snapshot.WebhookConfigs
|
|
|
|
s.stats.AccountsCount = len(s.accounts)
|
|
s.stats.PaymentsCount = len(s.payments)
|
|
s.stats.BalancesCount = len(s.balances)
|
|
s.stats.OrdersCount = len(s.orders)
|
|
s.stats.ConversionsCount = len(s.conversions)
|
|
s.stats.ExternalAccountsCount = len(s.externalAccounts)
|
|
s.stats.LastUpdated = time.Now()
|
|
}
|