diff --git a/components/ledger/internal/api/v2/query.go b/components/ledger/internal/api/v2/query.go
index d7d87581f6..dab68f4935 100644
--- a/components/ledger/internal/api/v2/query.go
+++ b/components/ledger/internal/api/v2/query.go
@@ -4,6 +4,7 @@ import (
"net/http"
"strings"
+ "github.com/formancehq/stack/libs/go-libs/api"
"github.com/formancehq/stack/libs/go-libs/bun/bunpaginate"
"github.com/formancehq/ledger/internal/engine/command"
@@ -28,10 +29,8 @@ func getCommandParameters(r *http.Request) command.Parameters {
dryRunAsString := r.URL.Query().Get("dryRun")
dryRun := strings.ToUpper(dryRunAsString) == "YES" || strings.ToUpper(dryRunAsString) == "TRUE" || dryRunAsString == "1"
- idempotencyKey := r.Header.Get("Idempotency-Key")
-
return command.Parameters{
DryRun: dryRun,
- IdempotencyKey: idempotencyKey,
+ IdempotencyKey: api.IdempotencyKeyFromRequest(r),
}
}
diff --git a/ee/wallets/openapi.yaml b/ee/wallets/openapi.yaml
index e38899aa5c..36aaea0866 100644
--- a/ee/wallets/openapi.yaml
+++ b/ee/wallets/openapi.yaml
@@ -191,6 +191,12 @@ paths:
operationId: updateWallet
tags:
- Wallets
+ parameters:
+ - name: Idempotency-Key
+ in: header
+ description: Use an idempotency key
+ schema:
+ type: string
requestBody:
content:
application/json:
@@ -323,6 +329,11 @@ paths:
required: true
schema:
type: string
+ - name: Idempotency-Key
+ in: header
+ description: Use an idempotency key
+ schema:
+ type: string
post:
summary: Debit a wallet
operationId: debitWallet
@@ -356,6 +367,11 @@ paths:
required: true
schema:
type: string
+ - name: Idempotency-Key
+ in: header
+ description: Use an idempotency key
+ schema:
+ type: string
post:
summary: Credit a wallet
operationId: creditWallet
@@ -467,6 +483,11 @@ paths:
required: true
schema:
type: string
+ - name: Idempotency-Key
+ in: header
+ description: Use an idempotency key
+ schema:
+ type: string
requestBody:
content:
application/json:
@@ -493,6 +514,11 @@ paths:
required: true
schema:
type: string
+ - name: Idempotency-Key
+ in: header
+ description: Use an idempotency key
+ schema:
+ type: string
post:
summary: Cancel a hold
operationId: voidHold
@@ -974,6 +1000,7 @@ components:
expiresAt:
type: string
format: date-time
+ nullable: true
priority:
type: integer
format: bigint
diff --git a/ee/wallets/pkg/api/handler_balances_create_test.go b/ee/wallets/pkg/api/handler_balances_create_test.go
index e072caf76d..b2277201b2 100644
--- a/ee/wallets/pkg/api/handler_balances_create_test.go
+++ b/ee/wallets/pkg/api/handler_balances_create_test.go
@@ -76,7 +76,7 @@ func TestBalancesCreate(t *testing.T) {
appliedMetadata map[string]string
)
testEnv := newTestEnv(
- WithAddMetadataToAccount(func(ctx context.Context, ledger, account string, metadata map[string]string) error {
+ WithAddMetadataToAccount(func(ctx context.Context, ledger, account, ik string, metadata map[string]string) error {
targetedLedger = ledger
targetedAccount = account
appliedMetadata = metadata
diff --git a/ee/wallets/pkg/api/handler_holds_confirm.go b/ee/wallets/pkg/api/handler_holds_confirm.go
index d2b66e376d..93ce9e2194 100644
--- a/ee/wallets/pkg/api/handler_holds_confirm.go
+++ b/ee/wallets/pkg/api/handler_holds_confirm.go
@@ -5,6 +5,8 @@ import (
"math/big"
"net/http"
+ "github.com/formancehq/stack/libs/go-libs/api"
+
wallet "github.com/formancehq/wallets/pkg"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
@@ -28,7 +30,7 @@ func (m *MainHandler) confirmHoldHandler(w http.ResponseWriter, r *http.Request)
}
}
- err := m.manager.ConfirmHold(r.Context(), wallet.ConfirmHold{
+ err := m.manager.ConfirmHold(r.Context(), api.IdempotencyKeyFromRequest(r), wallet.ConfirmHold{
HoldID: chi.URLParam(r, "holdID"),
Amount: big.NewInt(data.Amount),
Final: data.Final,
diff --git a/ee/wallets/pkg/api/handler_holds_confirm_test.go b/ee/wallets/pkg/api/handler_holds_confirm_test.go
index 1d59f91dfd..5bddef366f 100644
--- a/ee/wallets/pkg/api/handler_holds_confirm_test.go
+++ b/ee/wallets/pkg/api/handler_holds_confirm_test.go
@@ -40,7 +40,7 @@ func TestHoldsConfirm(t *testing.T) {
Balances: balances,
}, nil
}),
- WithCreateTransaction(func(ctx context.Context, name string, postTransaction wallet.PostTransaction) (*shared.V2Transaction, error) {
+ WithCreateTransaction(func(ctx context.Context, name, ik string, postTransaction wallet.PostTransaction) (*shared.V2Transaction, error) {
compareJSON(t, wallet.PostTransaction{
Script: &shared.V2PostTransactionScript{
Plain: wallet.BuildConfirmHoldScript(false, "USD"),
@@ -95,7 +95,7 @@ func TestHoldsPartialConfirm(t *testing.T) {
},
}, nil
}),
- WithCreateTransaction(func(ctx context.Context, name string, postTransaction wallet.PostTransaction) (*shared.V2Transaction, error) {
+ WithCreateTransaction(func(ctx context.Context, name, ik string, postTransaction wallet.PostTransaction) (*shared.V2Transaction, error) {
compareJSON(t, wallet.PostTransaction{
Script: &shared.V2PostTransactionScript{
Plain: wallet.BuildConfirmHoldScript(false, "USD"),
@@ -230,7 +230,7 @@ func TestHoldsPartialConfirmWithFinal(t *testing.T) {
},
}, nil
}),
- WithCreateTransaction(func(ctx context.Context, name string, script wallet.PostTransaction) (*shared.V2Transaction, error) {
+ WithCreateTransaction(func(ctx context.Context, name, ik string, script wallet.PostTransaction) (*shared.V2Transaction, error) {
compareJSON(t, wallet.PostTransaction{
Script: &shared.V2PostTransactionScript{
Plain: wallet.BuildConfirmHoldScript(true, "USD"),
diff --git a/ee/wallets/pkg/api/handler_holds_void.go b/ee/wallets/pkg/api/handler_holds_void.go
index 4ae66dd061..52aca4f48c 100644
--- a/ee/wallets/pkg/api/handler_holds_void.go
+++ b/ee/wallets/pkg/api/handler_holds_void.go
@@ -4,12 +4,14 @@ import (
"errors"
"net/http"
+ "github.com/formancehq/stack/libs/go-libs/api"
+
wallet "github.com/formancehq/wallets/pkg"
"github.com/go-chi/chi/v5"
)
func (m *MainHandler) voidHoldHandler(w http.ResponseWriter, r *http.Request) {
- err := m.manager.VoidHold(r.Context(), wallet.VoidHold{
+ err := m.manager.VoidHold(r.Context(), api.IdempotencyKeyFromRequest(r), wallet.VoidHold{
HoldID: chi.URLParam(r, "holdID"),
})
if err != nil {
diff --git a/ee/wallets/pkg/api/handler_holds_void_test.go b/ee/wallets/pkg/api/handler_holds_void_test.go
index 99c5b7d283..6e4f8e37be 100644
--- a/ee/wallets/pkg/api/handler_holds_void_test.go
+++ b/ee/wallets/pkg/api/handler_holds_void_test.go
@@ -45,7 +45,7 @@ func TestHoldsVoid(t *testing.T) {
},
}, nil
}),
- WithCreateTransaction(func(ctx context.Context, name string, script wallet.PostTransaction) (*shared.V2Transaction, error) {
+ WithCreateTransaction(func(ctx context.Context, name, ik string, script wallet.PostTransaction) (*shared.V2Transaction, error) {
compareJSON(t, wallet.PostTransaction{
Script: &shared.V2PostTransactionScript{
Plain: wallet.BuildCancelHoldScript("USD"),
diff --git a/ee/wallets/pkg/api/handler_wallets_create_test.go b/ee/wallets/pkg/api/handler_wallets_create_test.go
index ff90046e4e..a1d830e37a 100644
--- a/ee/wallets/pkg/api/handler_wallets_create_test.go
+++ b/ee/wallets/pkg/api/handler_wallets_create_test.go
@@ -33,7 +33,7 @@ func TestWalletsCreate(t *testing.T) {
md map[string]string
)
testEnv := newTestEnv(
- WithAddMetadataToAccount(func(ctx context.Context, l, a string, m map[string]string) error {
+ WithAddMetadataToAccount(func(ctx context.Context, l, a, ik string, m map[string]string) error {
ledger = l
account = a
md = m
diff --git a/ee/wallets/pkg/api/handler_wallets_credit.go b/ee/wallets/pkg/api/handler_wallets_credit.go
index 04076fa1c2..8747a9201c 100644
--- a/ee/wallets/pkg/api/handler_wallets_credit.go
+++ b/ee/wallets/pkg/api/handler_wallets_credit.go
@@ -4,6 +4,8 @@ import (
"errors"
"net/http"
+ "github.com/formancehq/stack/libs/go-libs/api"
+
wallet "github.com/formancehq/wallets/pkg"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
@@ -29,7 +31,7 @@ func (m *MainHandler) creditWalletHandler(w http.ResponseWriter, r *http.Request
CreditRequest: *data,
}
- err := m.manager.Credit(r.Context(), credit)
+ err := m.manager.Credit(r.Context(), api.IdempotencyKeyFromRequest(r), credit)
if err != nil {
switch {
case errors.Is(err, wallet.ErrBalanceNotExists),
diff --git a/ee/wallets/pkg/api/handler_wallets_credit_test.go b/ee/wallets/pkg/api/handler_wallets_credit_test.go
index 365ab1ebb0..bbcb2d4edd 100644
--- a/ee/wallets/pkg/api/handler_wallets_credit_test.go
+++ b/ee/wallets/pkg/api/handler_wallets_credit_test.go
@@ -181,7 +181,7 @@ func TestWalletsCredit(t *testing.T) {
postTransaction wallet.PostTransaction
)
testEnv = newTestEnv(
- WithCreateTransaction(func(ctx context.Context, ledger string, p wallet.PostTransaction) (*shared.V2Transaction, error) {
+ WithCreateTransaction(func(ctx context.Context, ledger, ik string, p wallet.PostTransaction) (*shared.V2Transaction, error) {
require.Equal(t, testEnv.LedgerName(), ledger)
postTransaction = p
return &testCase.postTransactionResult, nil
diff --git a/ee/wallets/pkg/api/handler_wallets_debit.go b/ee/wallets/pkg/api/handler_wallets_debit.go
index 4f6ea72d87..f7be696251 100644
--- a/ee/wallets/pkg/api/handler_wallets_debit.go
+++ b/ee/wallets/pkg/api/handler_wallets_debit.go
@@ -4,6 +4,8 @@ import (
"errors"
"net/http"
+ "github.com/formancehq/stack/libs/go-libs/api"
+
wallet "github.com/formancehq/wallets/pkg"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
@@ -16,7 +18,7 @@ func (m *MainHandler) debitWalletHandler(w http.ResponseWriter, r *http.Request)
return
}
- hold, err := m.manager.Debit(r.Context(), wallet.Debit{
+ hold, err := m.manager.Debit(r.Context(), api.IdempotencyKeyFromRequest(r), wallet.Debit{
WalletID: chi.URLParam(r, "walletID"),
DebitRequest: *data,
})
diff --git a/ee/wallets/pkg/api/handler_wallets_debit_test.go b/ee/wallets/pkg/api/handler_wallets_debit_test.go
index 18745cb644..cf4d382abb 100644
--- a/ee/wallets/pkg/api/handler_wallets_debit_test.go
+++ b/ee/wallets/pkg/api/handler_wallets_debit_test.go
@@ -336,7 +336,7 @@ func TestWalletsDebit(t *testing.T) {
},
}, nil
}),
- WithCreateTransaction(func(ctx context.Context, ledger string, p wallet.PostTransaction) (*shared.V2Transaction, error) {
+ WithCreateTransaction(func(ctx context.Context, ledger, ik string, p wallet.PostTransaction) (*shared.V2Transaction, error) {
require.Equal(t, testEnv.LedgerName(), ledger)
postTransaction = p
if testCase.postTransactionError != nil {
diff --git a/ee/wallets/pkg/api/handler_wallets_patch.go b/ee/wallets/pkg/api/handler_wallets_patch.go
index 90eb60d72a..9a667d6085 100644
--- a/ee/wallets/pkg/api/handler_wallets_patch.go
+++ b/ee/wallets/pkg/api/handler_wallets_patch.go
@@ -4,6 +4,8 @@ import (
"errors"
"net/http"
+ "github.com/formancehq/stack/libs/go-libs/api"
+
wallet "github.com/formancehq/wallets/pkg"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
@@ -16,7 +18,7 @@ func (m *MainHandler) patchWalletHandler(w http.ResponseWriter, r *http.Request)
return
}
- err := m.manager.UpdateWallet(r.Context(), chi.URLParam(r, "walletID"), data)
+ err := m.manager.UpdateWallet(r.Context(), chi.URLParam(r, "walletID"), api.IdempotencyKeyFromRequest(r), data)
if err != nil {
switch {
case errors.Is(err, wallet.ErrWalletNotFound):
diff --git a/ee/wallets/pkg/api/handler_wallets_patch_test.go b/ee/wallets/pkg/api/handler_wallets_patch_test.go
index 2fffadd996..c18eec6724 100644
--- a/ee/wallets/pkg/api/handler_wallets_patch_test.go
+++ b/ee/wallets/pkg/api/handler_wallets_patch_test.go
@@ -42,7 +42,7 @@ func TestWalletsPatch(t *testing.T) {
},
}, nil
}),
- WithAddMetadataToAccount(func(ctx context.Context, ledger, account string, md map[string]string) error {
+ WithAddMetadataToAccount(func(ctx context.Context, ledger, account, ik string, md map[string]string) error {
require.Equal(t, testEnv.LedgerName(), ledger)
require.Equal(t, testEnv.Chart().GetMainBalanceAccount(w.ID), account)
compareJSON(t, metadata.Metadata{
diff --git a/ee/wallets/pkg/api/utils_test.go b/ee/wallets/pkg/api/utils_test.go
index 0a549c2ded..cf166543c5 100644
--- a/ee/wallets/pkg/api/utils_test.go
+++ b/ee/wallets/pkg/api/utils_test.go
@@ -92,11 +92,11 @@ func newTestEnv(opts ...Option) *testEnv {
}
type (
- addMetadataToAccountFn func(ctx context.Context, ledger, account string, metadata map[string]string) error
+ addMetadataToAccountFn func(ctx context.Context, ledger, account, ik string, metadata map[string]string) error
getAccountFn func(ctx context.Context, ledger, account string) (*wallet.AccountWithVolumesAndBalances, error)
listAccountsFn func(ctx context.Context, ledger string, query wallet.ListAccountsQuery) (*wallet.AccountsCursorResponseCursor, error)
listTransactionsFn func(ctx context.Context, ledger string, query wallet.ListTransactionsQuery) (*shared.V2TransactionsCursorResponseCursor, error)
- createTransactionFn func(ctx context.Context, ledger string, postTransaction wallet.PostTransaction) (*shared.V2Transaction, error)
+ createTransactionFn func(ctx context.Context, ledger, ik string, postTransaction wallet.PostTransaction) (*shared.V2Transaction, error)
)
type LedgerMock struct {
@@ -111,8 +111,8 @@ func (l *LedgerMock) EnsureLedgerExists(ctx context.Context, name string) error
return nil
}
-func (l *LedgerMock) AddMetadataToAccount(ctx context.Context, ledger, account string, metadata map[string]string) error {
- return l.addMetadataToAccount(ctx, ledger, account, metadata)
+func (l *LedgerMock) AddMetadataToAccount(ctx context.Context, ledger, account, ik string, metadata map[string]string) error {
+ return l.addMetadataToAccount(ctx, ledger, account, ik, metadata)
}
func (l *LedgerMock) GetAccount(ctx context.Context, ledger, account string) (*wallet.AccountWithVolumesAndBalances, error) {
@@ -123,8 +123,8 @@ func (l *LedgerMock) ListAccounts(ctx context.Context, ledger string, query wall
return l.listAccounts(ctx, ledger, query)
}
-func (l *LedgerMock) CreateTransaction(ctx context.Context, ledger string, postTransaction wallet.PostTransaction) (*shared.V2Transaction, error) {
- return l.createTransaction(ctx, ledger, postTransaction)
+func (l *LedgerMock) CreateTransaction(ctx context.Context, ledger, ik string, postTransaction wallet.PostTransaction) (*shared.V2Transaction, error) {
+ return l.createTransaction(ctx, ledger, ik, postTransaction)
}
func (l *LedgerMock) ListTransactions(ctx context.Context, ledger string, query wallet.ListTransactionsQuery) (*shared.V2TransactionsCursorResponseCursor, error) {
diff --git a/ee/wallets/pkg/ledger_interface.go b/ee/wallets/pkg/ledger_interface.go
index 0ba9891675..3a0bdd102e 100644
--- a/ee/wallets/pkg/ledger_interface.go
+++ b/ee/wallets/pkg/ledger_interface.go
@@ -98,11 +98,11 @@ func (c AccountsCursorResponseCursor) GetHasMore() bool {
type Ledger interface {
EnsureLedgerExists(ctx context.Context, name string) error
- AddMetadataToAccount(ctx context.Context, ledger, account string, metadata map[string]string) error
+ AddMetadataToAccount(ctx context.Context, ledger, account, ik string, metadata map[string]string) error
GetAccount(ctx context.Context, ledger, account string) (*AccountWithVolumesAndBalances, error)
ListAccounts(ctx context.Context, ledger string, query ListAccountsQuery) (*AccountsCursorResponseCursor, error)
ListTransactions(ctx context.Context, ledger string, query ListTransactionsQuery) (*shared.V2TransactionsCursorResponseCursor, error)
- CreateTransaction(ctx context.Context, ledger string, postTransaction PostTransaction) (*shared.V2Transaction, error)
+ CreateTransaction(ctx context.Context, ledger, ik string, postTransaction PostTransaction) (*shared.V2Transaction, error)
}
type DefaultLedger struct {
@@ -179,7 +179,7 @@ func (d DefaultLedger) ListTransactions(ctx context.Context, ledger string, q Li
return &rsp.V2TransactionsCursorResponse.Cursor, nil
}
-func (d DefaultLedger) CreateTransaction(ctx context.Context, ledger string, transaction PostTransaction) (*shared.V2Transaction, error) {
+func (d DefaultLedger) CreateTransaction(ctx context.Context, ledger, ik string, transaction PostTransaction) (*shared.V2Transaction, error) {
ret, err := d.client.Ledger.V2CreateTransaction(ctx, operations.V2CreateTransactionRequest{
V2PostTransaction: shared.V2PostTransaction{
Metadata: transaction.Metadata,
@@ -193,7 +193,8 @@ func (d DefaultLedger) CreateTransaction(ctx context.Context, ledger string, tra
return &transaction.Timestamp.Time
}(),
},
- Ledger: ledger,
+ Ledger: ledger,
+ IdempotencyKey: pointer.For(ik),
})
if err != nil {
return nil, err
@@ -202,12 +203,13 @@ func (d DefaultLedger) CreateTransaction(ctx context.Context, ledger string, tra
return &ret.V2CreateTransactionResponse.Data, nil
}
-func (d DefaultLedger) AddMetadataToAccount(ctx context.Context, ledger, account string, metadata map[string]string) error {
+func (d DefaultLedger) AddMetadataToAccount(ctx context.Context, ledger, account, ik string, metadata map[string]string) error {
_, err := d.client.Ledger.V2AddMetadataToAccount(ctx, operations.V2AddMetadataToAccountRequest{
- RequestBody: metadata,
- Address: account,
- Ledger: ledger,
+ RequestBody: metadata,
+ Address: account,
+ Ledger: ledger,
+ IdempotencyKey: pointer.For(ik),
})
if err != nil {
return err
diff --git a/ee/wallets/pkg/manager.go b/ee/wallets/pkg/manager.go
index f9106b381e..2fcd66a90a 100644
--- a/ee/wallets/pkg/manager.go
+++ b/ee/wallets/pkg/manager.go
@@ -106,7 +106,7 @@ func (m *Manager) Init(ctx context.Context) error {
}
//nolint:cyclop
-func (m *Manager) Debit(ctx context.Context, debit Debit) (*DebitHold, error) {
+func (m *Manager) Debit(ctx context.Context, ik string, debit Debit) (*DebitHold, error) {
if err := debit.Validate(); err != nil {
return nil, err
}
@@ -181,14 +181,14 @@ func (m *Manager) Debit(ctx context.Context, debit Debit) (*DebitHold, error) {
postTransaction.Reference = &debit.Reference
}
- if err := m.CreateTransaction(ctx, postTransaction); err != nil {
+ if err := m.CreateTransaction(ctx, ik, postTransaction); err != nil {
return nil, err
}
return hold, nil
}
-func (m *Manager) ConfirmHold(ctx context.Context, debit ConfirmHold) error {
+func (m *Manager) ConfirmHold(ctx context.Context, ik string, debit ConfirmHold) error {
account, err := m.client.GetAccount(ctx, m.ledgerName, m.chart.GetHoldAccount(debit.HoldID))
if err != nil {
return errors.Wrap(err, "getting account")
@@ -227,14 +227,14 @@ func (m *Manager) ConfirmHold(ctx context.Context, debit ConfirmHold) error {
Metadata: TransactionMetadata(metadata.Metadata{}),
}
- if err := m.CreateTransaction(ctx, postTransaction); err != nil {
+ if err := m.CreateTransaction(ctx, ik, postTransaction); err != nil {
return err
}
return nil
}
-func (m *Manager) VoidHold(ctx context.Context, void VoidHold) error {
+func (m *Manager) VoidHold(ctx context.Context, ik string, void VoidHold) error {
account, err := m.client.GetAccount(ctx, m.ledgerName, m.chart.GetHoldAccount(void.HoldID))
if err != nil {
return errors.Wrap(err, "getting account")
@@ -256,14 +256,14 @@ func (m *Manager) VoidHold(ctx context.Context, void VoidHold) error {
Metadata: TransactionMetadata(metadata.Metadata{}),
}
- if err := m.CreateTransaction(ctx, postTransaction); err != nil {
+ if err := m.CreateTransaction(ctx, ik, postTransaction); err != nil {
return err
}
return nil
}
-func (m *Manager) Credit(ctx context.Context, credit Credit) error {
+func (m *Manager) Credit(ctx context.Context, ik string, credit Credit) error {
if err := credit.Validate(); err != nil {
return err
}
@@ -293,15 +293,15 @@ func (m *Manager) Credit(ctx context.Context, credit Credit) error {
postTransaction.Reference = &credit.Reference
}
- if err := m.CreateTransaction(ctx, postTransaction); err != nil {
+ if err := m.CreateTransaction(ctx, ik, postTransaction); err != nil {
return err
}
return nil
}
-func (m *Manager) CreateTransaction(ctx context.Context, postTransaction PostTransaction) error {
- if _, err := m.client.CreateTransaction(ctx, m.ledgerName, postTransaction); err != nil {
+func (m *Manager) CreateTransaction(ctx context.Context, ik string, postTransaction PostTransaction) error {
+ if _, err := m.client.CreateTransaction(ctx, m.ledgerName, ik, postTransaction); err != nil {
switch err := err.(type) {
case *sdkerrors.WalletsErrorResponse:
if err.ErrorCode == sdkerrors.SchemasWalletsErrorResponseErrorCodeInsufficientFund {
@@ -417,6 +417,7 @@ func (m *Manager) CreateWallet(ctx context.Context, data *CreateRequest) (*Walle
ctx,
m.ledgerName,
m.chart.GetMainBalanceAccount(wallet.ID),
+ "",
wallet.LedgerMetadata(),
); err != nil {
return nil, errors.Wrap(err, "adding metadata to account")
@@ -425,7 +426,7 @@ func (m *Manager) CreateWallet(ctx context.Context, data *CreateRequest) (*Walle
return &wallet, nil
}
-func (m *Manager) UpdateWallet(ctx context.Context, id string, data *PatchRequest) error {
+func (m *Manager) UpdateWallet(ctx context.Context, id, ik string, data *PatchRequest) error {
account, err := m.client.GetAccount(ctx, m.ledgerName, m.chart.GetMainBalanceAccount(id))
if err != nil {
return ErrWalletNotFound
@@ -442,7 +443,7 @@ func (m *Manager) UpdateWallet(ctx context.Context, id string, data *PatchReques
meta := metadata.Metadata(account.GetMetadata())
meta = meta.Merge(EncodeCustomMetadata(newCustomMetadata))
- if err := m.client.AddMetadataToAccount(ctx, m.ledgerName, m.chart.GetMainBalanceAccount(id), meta); err != nil {
+ if err := m.client.AddMetadataToAccount(ctx, m.ledgerName, m.chart.GetMainBalanceAccount(id), ik, meta); err != nil {
return errors.Wrap(err, "adding metadata to account")
}
@@ -584,6 +585,7 @@ func (m *Manager) CreateBalance(ctx context.Context, data *CreateBalance) (*Bala
ctx,
m.ledgerName,
m.chart.GetBalanceAccount(data.WalletID, balance.Name),
+ "",
balance.LedgerMetadata(data.WalletID),
); err != nil {
return nil, errors.Wrap(err, "adding metadata to account")
diff --git a/libs/go-libs/api/idempotency.go b/libs/go-libs/api/idempotency.go
new file mode 100644
index 0000000000..c2f58c05dc
--- /dev/null
+++ b/libs/go-libs/api/idempotency.go
@@ -0,0 +1,7 @@
+package api
+
+import "net/http"
+
+func IdempotencyKeyFromRequest(r *http.Request) string {
+ return r.Header.Get("Idempotency-Key")
+}
diff --git a/releases/sdks/go/.speakeasy/gen.lock b/releases/sdks/go/.speakeasy/gen.lock
index f635d35c6d..c668ea4903 100755
--- a/releases/sdks/go/.speakeasy/gen.lock
+++ b/releases/sdks/go/.speakeasy/gen.lock
@@ -1,7 +1,7 @@
lockVersion: 2.0.0
id: 7eac0a45-60a2-40bb-9e85-26bd77ec2a6d
management:
- docChecksum: 09fd9d887717d67e69a8602402ba2302
+ docChecksum: 9b5e414195f0c0428e44134300a0532d
docVersion: v0.0.0
speakeasyVersion: 1.292.0
generationVersion: 2.332.4
diff --git a/releases/sdks/go/docs/pkg/models/operations/confirmholdrequest.md b/releases/sdks/go/docs/pkg/models/operations/confirmholdrequest.md
index ab8b18c1fd..32876ade63 100644
--- a/releases/sdks/go/docs/pkg/models/operations/confirmholdrequest.md
+++ b/releases/sdks/go/docs/pkg/models/operations/confirmholdrequest.md
@@ -6,4 +6,5 @@
| Field | Type | Required | Description |
| ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ |
| `ConfirmHoldRequest` | [*shared.ConfirmHoldRequest](../../../pkg/models/shared/confirmholdrequest.md) | :heavy_minus_sign: | N/A |
+| `IdempotencyKey` | **string* | :heavy_minus_sign: | Use an idempotency key |
| `HoldID` | *string* | :heavy_check_mark: | N/A |
\ No newline at end of file
diff --git a/releases/sdks/go/docs/pkg/models/operations/creditwalletrequest.md b/releases/sdks/go/docs/pkg/models/operations/creditwalletrequest.md
index a399e2b51c..3d1aa9e822 100644
--- a/releases/sdks/go/docs/pkg/models/operations/creditwalletrequest.md
+++ b/releases/sdks/go/docs/pkg/models/operations/creditwalletrequest.md
@@ -6,4 +6,5 @@
| Field | Type | Required | Description | Example |
| ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `CreditWalletRequest` | [*shared.CreditWalletRequest](../../../pkg/models/shared/creditwalletrequest.md) | :heavy_minus_sign: | N/A | {
"amount": {
"asset": "USD/2",
"amount": 100
},
"metadata": {
"key": ""
},
"sources": []
} |
+| `IdempotencyKey` | **string* | :heavy_minus_sign: | Use an idempotency key | |
| `ID` | *string* | :heavy_check_mark: | N/A | |
\ No newline at end of file
diff --git a/releases/sdks/go/docs/pkg/models/operations/debitwalletrequest.md b/releases/sdks/go/docs/pkg/models/operations/debitwalletrequest.md
index 714425cb7c..3dd98e2fb9 100644
--- a/releases/sdks/go/docs/pkg/models/operations/debitwalletrequest.md
+++ b/releases/sdks/go/docs/pkg/models/operations/debitwalletrequest.md
@@ -6,4 +6,5 @@
| Field | Type | Required | Description | Example |
| --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `DebitWalletRequest` | [*shared.DebitWalletRequest](../../../pkg/models/shared/debitwalletrequest.md) | :heavy_minus_sign: | N/A | {
"amount": {
"asset": "USD/2",
"amount": 100
},
"metadata": {
"key": ""
},
"pending": true
} |
+| `IdempotencyKey` | **string* | :heavy_minus_sign: | Use an idempotency key | |
| `ID` | *string* | :heavy_check_mark: | N/A | |
\ No newline at end of file
diff --git a/releases/sdks/go/docs/pkg/models/operations/updatewalletrequest.md b/releases/sdks/go/docs/pkg/models/operations/updatewalletrequest.md
index 5186bdb6a1..5970671d68 100644
--- a/releases/sdks/go/docs/pkg/models/operations/updatewalletrequest.md
+++ b/releases/sdks/go/docs/pkg/models/operations/updatewalletrequest.md
@@ -5,5 +5,6 @@
| Field | Type | Required | Description |
| ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ |
+| `IdempotencyKey` | **string* | :heavy_minus_sign: | Use an idempotency key |
| `RequestBody` | [*operations.UpdateWalletRequestBody](../../../pkg/models/operations/updatewalletrequestbody.md) | :heavy_minus_sign: | N/A |
| `ID` | *string* | :heavy_check_mark: | N/A |
\ No newline at end of file
diff --git a/releases/sdks/go/docs/pkg/models/operations/voidholdrequest.md b/releases/sdks/go/docs/pkg/models/operations/voidholdrequest.md
index 48a62532af..b2ce4b0542 100644
--- a/releases/sdks/go/docs/pkg/models/operations/voidholdrequest.md
+++ b/releases/sdks/go/docs/pkg/models/operations/voidholdrequest.md
@@ -3,6 +3,7 @@
## Fields
-| Field | Type | Required | Description |
-| ------------------ | ------------------ | ------------------ | ------------------ |
-| `HoldID` | *string* | :heavy_check_mark: | N/A |
\ No newline at end of file
+| Field | Type | Required | Description |
+| ---------------------- | ---------------------- | ---------------------- | ---------------------- |
+| `IdempotencyKey` | **string* | :heavy_minus_sign: | Use an idempotency key |
+| `HoldID` | *string* | :heavy_check_mark: | N/A |
\ No newline at end of file
diff --git a/releases/sdks/go/pkg/models/operations/confirmhold.go b/releases/sdks/go/pkg/models/operations/confirmhold.go
index c8be47d8ae..09096dfa92 100644
--- a/releases/sdks/go/pkg/models/operations/confirmhold.go
+++ b/releases/sdks/go/pkg/models/operations/confirmhold.go
@@ -9,7 +9,9 @@ import (
type ConfirmHoldRequest struct {
ConfirmHoldRequest *shared.ConfirmHoldRequest `request:"mediaType=application/json"`
- HoldID string `pathParam:"style=simple,explode=false,name=hold_id"`
+ // Use an idempotency key
+ IdempotencyKey *string `header:"style=simple,explode=false,name=Idempotency-Key"`
+ HoldID string `pathParam:"style=simple,explode=false,name=hold_id"`
}
func (o *ConfirmHoldRequest) GetConfirmHoldRequest() *shared.ConfirmHoldRequest {
@@ -19,6 +21,13 @@ func (o *ConfirmHoldRequest) GetConfirmHoldRequest() *shared.ConfirmHoldRequest
return o.ConfirmHoldRequest
}
+func (o *ConfirmHoldRequest) GetIdempotencyKey() *string {
+ if o == nil {
+ return nil
+ }
+ return o.IdempotencyKey
+}
+
func (o *ConfirmHoldRequest) GetHoldID() string {
if o == nil {
return ""
diff --git a/releases/sdks/go/pkg/models/operations/creditwallet.go b/releases/sdks/go/pkg/models/operations/creditwallet.go
index eef13532f8..8757b3a088 100644
--- a/releases/sdks/go/pkg/models/operations/creditwallet.go
+++ b/releases/sdks/go/pkg/models/operations/creditwallet.go
@@ -9,7 +9,9 @@ import (
type CreditWalletRequest struct {
CreditWalletRequest *shared.CreditWalletRequest `request:"mediaType=application/json"`
- ID string `pathParam:"style=simple,explode=false,name=id"`
+ // Use an idempotency key
+ IdempotencyKey *string `header:"style=simple,explode=false,name=Idempotency-Key"`
+ ID string `pathParam:"style=simple,explode=false,name=id"`
}
func (o *CreditWalletRequest) GetCreditWalletRequest() *shared.CreditWalletRequest {
@@ -19,6 +21,13 @@ func (o *CreditWalletRequest) GetCreditWalletRequest() *shared.CreditWalletReque
return o.CreditWalletRequest
}
+func (o *CreditWalletRequest) GetIdempotencyKey() *string {
+ if o == nil {
+ return nil
+ }
+ return o.IdempotencyKey
+}
+
func (o *CreditWalletRequest) GetID() string {
if o == nil {
return ""
diff --git a/releases/sdks/go/pkg/models/operations/debitwallet.go b/releases/sdks/go/pkg/models/operations/debitwallet.go
index 0d7f7101c0..5d06c65a79 100644
--- a/releases/sdks/go/pkg/models/operations/debitwallet.go
+++ b/releases/sdks/go/pkg/models/operations/debitwallet.go
@@ -9,7 +9,9 @@ import (
type DebitWalletRequest struct {
DebitWalletRequest *shared.DebitWalletRequest `request:"mediaType=application/json"`
- ID string `pathParam:"style=simple,explode=false,name=id"`
+ // Use an idempotency key
+ IdempotencyKey *string `header:"style=simple,explode=false,name=Idempotency-Key"`
+ ID string `pathParam:"style=simple,explode=false,name=id"`
}
func (o *DebitWalletRequest) GetDebitWalletRequest() *shared.DebitWalletRequest {
@@ -19,6 +21,13 @@ func (o *DebitWalletRequest) GetDebitWalletRequest() *shared.DebitWalletRequest
return o.DebitWalletRequest
}
+func (o *DebitWalletRequest) GetIdempotencyKey() *string {
+ if o == nil {
+ return nil
+ }
+ return o.IdempotencyKey
+}
+
func (o *DebitWalletRequest) GetID() string {
if o == nil {
return ""
diff --git a/releases/sdks/go/pkg/models/operations/updatewallet.go b/releases/sdks/go/pkg/models/operations/updatewallet.go
index 408313dce5..a526db51e2 100644
--- a/releases/sdks/go/pkg/models/operations/updatewallet.go
+++ b/releases/sdks/go/pkg/models/operations/updatewallet.go
@@ -19,8 +19,17 @@ func (o *UpdateWalletRequestBody) GetMetadata() map[string]string {
}
type UpdateWalletRequest struct {
- RequestBody *UpdateWalletRequestBody `request:"mediaType=application/json"`
- ID string `pathParam:"style=simple,explode=false,name=id"`
+ // Use an idempotency key
+ IdempotencyKey *string `header:"style=simple,explode=false,name=Idempotency-Key"`
+ RequestBody *UpdateWalletRequestBody `request:"mediaType=application/json"`
+ ID string `pathParam:"style=simple,explode=false,name=id"`
+}
+
+func (o *UpdateWalletRequest) GetIdempotencyKey() *string {
+ if o == nil {
+ return nil
+ }
+ return o.IdempotencyKey
}
func (o *UpdateWalletRequest) GetRequestBody() *UpdateWalletRequestBody {
diff --git a/releases/sdks/go/pkg/models/operations/voidhold.go b/releases/sdks/go/pkg/models/operations/voidhold.go
index fed071ee44..e1a717ac29 100644
--- a/releases/sdks/go/pkg/models/operations/voidhold.go
+++ b/releases/sdks/go/pkg/models/operations/voidhold.go
@@ -7,7 +7,16 @@ import (
)
type VoidHoldRequest struct {
- HoldID string `pathParam:"style=simple,explode=false,name=hold_id"`
+ // Use an idempotency key
+ IdempotencyKey *string `header:"style=simple,explode=false,name=Idempotency-Key"`
+ HoldID string `pathParam:"style=simple,explode=false,name=hold_id"`
+}
+
+func (o *VoidHoldRequest) GetIdempotencyKey() *string {
+ if o == nil {
+ return nil
+ }
+ return o.IdempotencyKey
}
func (o *VoidHoldRequest) GetHoldID() string {
diff --git a/releases/sdks/go/wallets.go b/releases/sdks/go/wallets.go
index 72a7bf1476..585f4f7bb7 100644
--- a/releases/sdks/go/wallets.go
+++ b/releases/sdks/go/wallets.go
@@ -54,6 +54,8 @@ func (s *Wallets) ConfirmHold(ctx context.Context, request operations.ConfirmHol
req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent)
req.Header.Set("Content-Type", reqContentType)
+ utils.PopulateHeaders(ctx, req, request, nil)
+
if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil {
return nil, err
}
@@ -355,6 +357,8 @@ func (s *Wallets) CreditWallet(ctx context.Context, request operations.CreditWal
req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent)
req.Header.Set("Content-Type", reqContentType)
+ utils.PopulateHeaders(ctx, req, request, nil)
+
if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil {
return nil, err
}
@@ -448,6 +452,8 @@ func (s *Wallets) DebitWallet(ctx context.Context, request operations.DebitWalle
req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent)
req.Header.Set("Content-Type", reqContentType)
+ utils.PopulateHeaders(ctx, req, request, nil)
+
if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil {
return nil, err
}
@@ -1340,6 +1346,8 @@ func (s *Wallets) UpdateWallet(ctx context.Context, request operations.UpdateWal
req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent)
req.Header.Set("Content-Type", reqContentType)
+ utils.PopulateHeaders(ctx, req, request, nil)
+
if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil {
return nil, err
}
@@ -1427,6 +1435,8 @@ func (s *Wallets) VoidHold(ctx context.Context, request operations.VoidHoldReque
req.Header.Set("Accept", "application/json")
req.Header.Set("User-Agent", s.sdkConfiguration.UserAgent)
+ utils.PopulateHeaders(ctx, req, request, nil)
+
if err := utils.PopulateSecurity(ctx, req, s.sdkConfiguration.Security); err != nil {
return nil, err
}
diff --git a/tests/integration/suite/wallets-credit.go b/tests/integration/suite/wallets-credit.go
index 6a47c5dc00..18ba9de08b 100644
--- a/tests/integration/suite/wallets-credit.go
+++ b/tests/integration/suite/wallets-credit.go
@@ -4,6 +4,7 @@ import (
"github.com/formancehq/formance-sdk-go/v2/pkg/models/operations"
"github.com/formancehq/formance-sdk-go/v2/pkg/models/sdkerrors"
"github.com/formancehq/formance-sdk-go/v2/pkg/models/shared"
+ "github.com/formancehq/stack/libs/go-libs/pointer"
. "github.com/formancehq/stack/tests/integration/internal"
"github.com/formancehq/stack/tests/integration/internal/modules"
"github.com/google/uuid"
@@ -44,10 +45,36 @@ var _ = WithModules([]*Module{modules.Auth, modules.Ledger, modules.Wallets}, fu
Metadata: map[string]string{},
},
ID: response.CreateWalletResponse.Data.ID,
+ IdempotencyKey: pointer.For("foo"),
})
Expect(err).To(Succeed())
})
It("should be ok", func() {})
+ Then("crediting again with the same ik", func() {
+ BeforeEach(func() {
+ _, err := Client().Wallets.CreditWallet(TestContext(), operations.CreditWalletRequest{
+ CreditWalletRequest: &shared.CreditWalletRequest{
+ Amount: shared.Monetary{
+ Amount: big.NewInt(1000),
+ Asset: "USD/2",
+ },
+ Sources: []shared.Subject{},
+ Metadata: map[string]string{},
+ },
+ ID: response.CreateWalletResponse.Data.ID,
+ IdempotencyKey: pointer.For("foo"),
+ })
+ Expect(err).To(Succeed())
+ })
+ It("Should not trigger any movements", func() {
+ balance, err := Client().Wallets.GetBalance(TestContext(), operations.GetBalanceRequest{
+ BalanceName: "main",
+ ID: response.CreateWalletResponse.Data.ID,
+ })
+ Expect(err).To(Succeed())
+ Expect(balance.GetBalanceResponse.Data.Assets["USD/2"]).To(Equal(big.NewInt(1000)))
+ })
+ })
})
Then("crediting it with specified timestamp", func() {
now := time.Now().Round(time.Microsecond).UTC()