2022-11-17 16:50:42 +01:00
|
|
|
package accounts
|
|
|
|
|
|
|
|
|
|
import (
|
2025-01-05 08:57:19 +02:00
|
|
|
"bytes"
|
2024-12-27 17:24:01 +02:00
|
|
|
"context"
|
2025-01-05 08:57:19 +02:00
|
|
|
"encoding/binary"
|
2022-11-17 16:50:42 +01:00
|
|
|
"encoding/hex"
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"time"
|
|
|
|
|
|
2025-01-04 13:57:32 +02:00
|
|
|
"github.com/lightningnetwork/lnd/fn"
|
2022-11-17 16:50:42 +01:00
|
|
|
"github.com/lightningnetwork/lnd/lnrpc"
|
|
|
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
|
|
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
2022-11-17 16:50:48 +01:00
|
|
|
"gopkg.in/macaroon-bakery.v2/bakery"
|
2022-11-17 16:50:42 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
// AccountIDLen is the length of the ID that is generated as a unique
|
|
|
|
|
// identifier of an account. It is 8 bytes long so guessing is
|
|
|
|
|
// improbable, but it's still not mistaken for a SHA256 hash.
|
|
|
|
|
AccountIDLen = 8
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// AccountType is an enum-like type which denotes the possible account types
|
|
|
|
|
// that can be referenced in macaroons to keep track of user's balances.
|
|
|
|
|
type AccountType uint8
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
// TypeInitialBalance represents an account that has an initial balance
|
|
|
|
|
// that is used up when it is spent and is not replenished
|
|
|
|
|
// automatically.
|
|
|
|
|
TypeInitialBalance AccountType = 0
|
|
|
|
|
|
|
|
|
|
// TODO(guggero): Add support for auto-replenishing (e.g. monthly
|
|
|
|
|
// allowance) or spend-only (no invoice creation) accounts.
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// AccountID represents an account's unique ID.
|
|
|
|
|
type AccountID [AccountIDLen]byte
|
|
|
|
|
|
|
|
|
|
// ParseAccountID attempts to parse a string as an account ID.
|
|
|
|
|
func ParseAccountID(idStr string) (*AccountID, error) {
|
|
|
|
|
if len(idStr) != hex.EncodedLen(AccountIDLen) {
|
|
|
|
|
return nil, fmt.Errorf("invalid account ID length")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
idBytes, err := hex.DecodeString(idStr)
|
|
|
|
|
if err != nil {
|
2023-07-26 11:50:31 +02:00
|
|
|
return nil, fmt.Errorf("error decoding account ID: %w", err)
|
2022-11-17 16:50:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var id AccountID
|
|
|
|
|
copy(id[:], idBytes)
|
|
|
|
|
|
|
|
|
|
return &id, nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-05 08:57:19 +02:00
|
|
|
// ToInt64 converts an AccountID to its int64 representation.
|
|
|
|
|
func (a AccountID) ToInt64() (int64, error) {
|
|
|
|
|
var value int64
|
|
|
|
|
buf := bytes.NewReader(a[:])
|
|
|
|
|
if err := binary.Read(buf, byteOrder, &value); err != nil {
|
|
|
|
|
return 0, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return value, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// AccountIDFromInt64 converts an int64 to an AccountID.
|
|
|
|
|
func AccountIDFromInt64(value int64) (AccountID, error) {
|
|
|
|
|
var (
|
|
|
|
|
a = AccountID{}
|
|
|
|
|
buf = new(bytes.Buffer)
|
|
|
|
|
)
|
|
|
|
|
if err := binary.Write(buf, byteOrder, value); err != nil {
|
|
|
|
|
return a, err
|
|
|
|
|
}
|
|
|
|
|
copy(a[:], buf.Bytes())
|
|
|
|
|
|
|
|
|
|
return a, nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-05 15:44:26 -04:00
|
|
|
// String returns the string representation of the AccountID.
|
|
|
|
|
func (a AccountID) String() string {
|
|
|
|
|
return hex.EncodeToString(a[:])
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-17 16:50:42 +01:00
|
|
|
// PaymentEntry is the data we track per payment that is associated with an
|
|
|
|
|
// account. This basically includes all information required to make sure
|
|
|
|
|
// in-flight payments don't exceed the total available account balance.
|
|
|
|
|
type PaymentEntry struct {
|
|
|
|
|
// Status is the RPC status of the payment as reported by lnd.
|
|
|
|
|
Status lnrpc.Payment_PaymentStatus
|
|
|
|
|
|
|
|
|
|
// FullAmount is the total amount of the payment which includes the
|
|
|
|
|
// payment amount and the estimated routing fee. The routing fee is
|
|
|
|
|
// set to the fee limit set when sending the payment and updated to the
|
|
|
|
|
// actual routing fee when the payment settles.
|
|
|
|
|
FullAmount lnwire.MilliSatoshi
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-26 11:50:31 +02:00
|
|
|
// AccountInvoices is the set of invoices that are associated with an account.
|
|
|
|
|
type AccountInvoices map[lntypes.Hash]struct{}
|
|
|
|
|
|
|
|
|
|
// AccountPayments is the set of payments that are associated with an account.
|
|
|
|
|
type AccountPayments map[lntypes.Hash]*PaymentEntry
|
|
|
|
|
|
2026-07-07 11:21:54 -05:00
|
|
|
// AccountPaymentEntry wraps a payment hash with its entry details.
|
|
|
|
|
type AccountPaymentEntry struct {
|
|
|
|
|
Hash lntypes.Hash
|
|
|
|
|
*PaymentEntry
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-17 16:50:42 +01:00
|
|
|
// OffChainBalanceAccount holds all information that is needed to keep track of
|
|
|
|
|
// a user's off-chain account balance. This balance can only be spent by paying
|
|
|
|
|
// invoices.
|
|
|
|
|
type OffChainBalanceAccount struct {
|
|
|
|
|
// ID is the randomly generated account identifier.
|
|
|
|
|
ID AccountID
|
|
|
|
|
|
|
|
|
|
// Type is the account type.
|
|
|
|
|
Type AccountType
|
|
|
|
|
|
|
|
|
|
// InitialBalance stores the initial balance in millisatoshis and is
|
|
|
|
|
// never updated.
|
|
|
|
|
InitialBalance lnwire.MilliSatoshi
|
|
|
|
|
|
|
|
|
|
// CurrentBalance is the currently available balance of the account
|
|
|
|
|
// in millisatoshis that is updated every time an invoice is paid. This
|
|
|
|
|
// value can be negative (for example if the fees for a payment are
|
|
|
|
|
// larger than the estimate made when checking the balance and the
|
|
|
|
|
// account is close to zero value).
|
|
|
|
|
CurrentBalance int64
|
|
|
|
|
|
|
|
|
|
// LastUpdate keeps track of the last time the balance of the account
|
|
|
|
|
// was updated.
|
|
|
|
|
LastUpdate time.Time
|
|
|
|
|
|
|
|
|
|
// ExpirationDate is a specific date in the future after which the
|
|
|
|
|
// account is marked as expired. Can be set to zero for accounts that
|
|
|
|
|
// never expire.
|
|
|
|
|
ExpirationDate time.Time
|
|
|
|
|
|
|
|
|
|
// Invoices is a list of all invoices that are associated with the
|
|
|
|
|
// account.
|
2023-07-26 11:50:31 +02:00
|
|
|
Invoices AccountInvoices
|
2022-11-17 16:50:42 +01:00
|
|
|
|
|
|
|
|
// Payments is a list of all payments that are associated with the
|
|
|
|
|
// account and the last status we were aware of.
|
2023-07-26 11:50:31 +02:00
|
|
|
Payments AccountPayments
|
2023-07-26 11:50:27 +02:00
|
|
|
|
|
|
|
|
// Label is an optional label that can be set for the account. If it is
|
|
|
|
|
// not empty then it must be unique.
|
|
|
|
|
Label string
|
2022-11-17 16:50:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// HasExpired returns true if the account has an expiration date set and that
|
|
|
|
|
// date is in the past.
|
|
|
|
|
func (a *OffChainBalanceAccount) HasExpired() bool {
|
|
|
|
|
if a.ExpirationDate.IsZero() {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return a.ExpirationDate.Before(time.Now())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CurrentBalanceSats returns the current account balance in satoshis.
|
|
|
|
|
func (a *OffChainBalanceAccount) CurrentBalanceSats() int64 {
|
|
|
|
|
return a.CurrentBalance / 1000
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
// ErrAccountBucketNotFound specifies that there is no bucket for the
|
|
|
|
|
// accounts in the DB yet which can/should only happen if the account
|
|
|
|
|
// store has been corrupted or was initialized incorrectly.
|
|
|
|
|
ErrAccountBucketNotFound = errors.New("account bucket not found")
|
|
|
|
|
|
|
|
|
|
// ErrAccNotFound is returned if an account could not be found in the
|
|
|
|
|
// local bolt DB.
|
|
|
|
|
ErrAccNotFound = errors.New("account not found")
|
|
|
|
|
|
|
|
|
|
// ErrNoInvoiceIndexKnown is the error that is returned by the store if
|
|
|
|
|
// it does not yet have any invoice indexes stored.
|
|
|
|
|
ErrNoInvoiceIndexKnown = errors.New("no invoice index known")
|
|
|
|
|
|
|
|
|
|
// ErrAccExpired is returned if an account has an expiration date set
|
|
|
|
|
// and that date is in the past.
|
|
|
|
|
ErrAccExpired = errors.New("account has expired")
|
|
|
|
|
|
|
|
|
|
// ErrAccBalanceInsufficient is returned if the amount required to
|
|
|
|
|
// perform a certain action is larger than the current balance of the
|
|
|
|
|
// account
|
|
|
|
|
ErrAccBalanceInsufficient = errors.New("account balance insufficient")
|
|
|
|
|
|
2026-08-03 14:25:23 -07:00
|
|
|
// ErrPaymentExceedsMaxSize is returned when a maximum account payment
|
|
|
|
|
// size is configured and a payment's total amount exceeds it.
|
|
|
|
|
ErrPaymentExceedsMaxSize = errors.New("payment amount exceeds the " +
|
|
|
|
|
"maximum allowed account payment size")
|
|
|
|
|
|
2026-08-03 14:21:03 -07:00
|
|
|
// ErrBalanceReservationExceeded is returned when the channel-balance
|
|
|
|
|
// check is enabled and an account balance allocation would push the sum
|
|
|
|
|
// of all account balances above the node's available local channel
|
|
|
|
|
// balance.
|
|
|
|
|
ErrBalanceReservationExceeded = errors.New("account balance " +
|
|
|
|
|
"allocation would exceed the node's available local channel " +
|
|
|
|
|
"balance")
|
|
|
|
|
|
2022-11-17 16:50:42 +01:00
|
|
|
// ErrNotSupportedWithAccounts is the error that is returned when an RPC
|
|
|
|
|
// is called that isn't supported to be handled by the account
|
|
|
|
|
// interceptor.
|
|
|
|
|
ErrNotSupportedWithAccounts = errors.New("this RPC call is not " +
|
|
|
|
|
"supported with restricted account macaroons")
|
2022-11-17 16:50:48 +01:00
|
|
|
|
2023-09-15 13:25:28 +02:00
|
|
|
// ErrAccountServiceDisabled is the error that is returned when the
|
|
|
|
|
// account service has been disabled due to an error being thrown
|
|
|
|
|
// in the service that cannot be recovered from.
|
|
|
|
|
ErrAccountServiceDisabled = errors.New("the account service has been " +
|
|
|
|
|
"stopped")
|
|
|
|
|
|
2022-11-17 16:50:48 +01:00
|
|
|
// MacaroonPermissions are the permissions required for an account
|
|
|
|
|
// macaroon.
|
|
|
|
|
MacaroonPermissions = []bakery.Op{{
|
|
|
|
|
Entity: "info",
|
|
|
|
|
Action: "read",
|
|
|
|
|
}, {
|
|
|
|
|
Entity: "offchain",
|
|
|
|
|
Action: "read",
|
|
|
|
|
}, {
|
|
|
|
|
Entity: "offchain",
|
|
|
|
|
Action: "write",
|
|
|
|
|
}, {
|
|
|
|
|
Entity: "onchain",
|
|
|
|
|
Action: "read",
|
|
|
|
|
}, {
|
|
|
|
|
Entity: "invoices",
|
|
|
|
|
Action: "read",
|
|
|
|
|
}, {
|
|
|
|
|
Entity: "invoices",
|
|
|
|
|
Action: "write",
|
|
|
|
|
}, {
|
|
|
|
|
Entity: "peers",
|
|
|
|
|
Action: "read",
|
|
|
|
|
}}
|
2022-11-17 16:50:42 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Store is the main account store interface.
|
|
|
|
|
type Store interface {
|
|
|
|
|
// NewAccount creates a new OffChainBalanceAccount with the given
|
|
|
|
|
// balance and a randomly chosen ID.
|
2024-12-27 17:24:01 +02:00
|
|
|
NewAccount(ctx context.Context, balance lnwire.MilliSatoshi,
|
|
|
|
|
expirationDate time.Time, label string) (
|
|
|
|
|
*OffChainBalanceAccount, error)
|
2022-11-17 16:50:42 +01:00
|
|
|
|
|
|
|
|
// Account retrieves an account from the Store and un-marshals it. If
|
|
|
|
|
// the account cannot be found, then ErrAccNotFound is returned.
|
2024-12-27 17:24:01 +02:00
|
|
|
Account(ctx context.Context, id AccountID) (*OffChainBalanceAccount,
|
|
|
|
|
error)
|
2022-11-17 16:50:42 +01:00
|
|
|
|
|
|
|
|
// Accounts retrieves all accounts from the store and un-marshals them.
|
2024-12-27 17:24:01 +02:00
|
|
|
Accounts(ctx context.Context) ([]*OffChainBalanceAccount, error)
|
2022-11-17 16:50:42 +01:00
|
|
|
|
2026-04-15 00:37:02 -05:00
|
|
|
// UpdateAccount updates the balance, expiry and/or label of an account.
|
|
|
|
|
UpdateAccount(ctx context.Context, id AccountID,
|
2025-01-19 19:07:08 +02:00
|
|
|
newBalance fn.Option[int64],
|
2026-04-15 00:37:02 -05:00
|
|
|
newExpiry fn.Option[time.Time],
|
|
|
|
|
newLabel fn.Option[string]) error
|
2025-01-04 13:57:32 +02:00
|
|
|
|
2025-01-04 14:22:59 +02:00
|
|
|
// AddAccountInvoice adds an invoice hash to an account.
|
|
|
|
|
AddAccountInvoice(ctx context.Context, id AccountID,
|
|
|
|
|
hash lntypes.Hash) error
|
|
|
|
|
|
2025-02-11 17:32:42 +01:00
|
|
|
// CreditAccount increases the balance of the account with the
|
2025-01-04 14:33:24 +02:00
|
|
|
// given ID by the given amount.
|
2025-02-11 17:32:42 +01:00
|
|
|
CreditAccount(ctx context.Context, id AccountID,
|
2025-01-04 14:33:24 +02:00
|
|
|
amount lnwire.MilliSatoshi) error
|
|
|
|
|
|
2025-02-05 18:14:07 +01:00
|
|
|
// DebitAccount decreases the balance of the account with the
|
|
|
|
|
// given ID by the given amount.
|
|
|
|
|
DebitAccount(ctx context.Context, id AccountID,
|
|
|
|
|
amount lnwire.MilliSatoshi) error
|
|
|
|
|
|
2025-01-16 10:33:32 +02:00
|
|
|
// UpsertAccountPayment updates or inserts a payment entry for the given
|
|
|
|
|
// account. Various functional options can be passed to modify the
|
2025-01-04 15:49:13 +02:00
|
|
|
// behavior of the method. The returned boolean is true if the payment
|
|
|
|
|
// was already known before the update. This is to be treated as a
|
|
|
|
|
// best-effort indication if an error is also returned since the method
|
|
|
|
|
// may error before the boolean can be set correctly.
|
2025-01-16 10:33:32 +02:00
|
|
|
UpsertAccountPayment(_ context.Context, id AccountID,
|
|
|
|
|
paymentHash lntypes.Hash, fullAmount lnwire.MilliSatoshi,
|
|
|
|
|
status lnrpc.Payment_PaymentStatus,
|
2025-01-04 15:49:13 +02:00
|
|
|
options ...UpsertPaymentOption) (bool, error)
|
2025-01-16 10:33:32 +02:00
|
|
|
|
2025-01-04 15:59:07 +02:00
|
|
|
// DeleteAccountPayment removes a payment entry from the account with
|
|
|
|
|
// the given ID. It will return the ErrPaymentNotAssociated error if the
|
|
|
|
|
// payment is not associated with the account.
|
|
|
|
|
DeleteAccountPayment(_ context.Context, id AccountID,
|
|
|
|
|
hash lntypes.Hash) error
|
|
|
|
|
|
2026-07-07 11:22:14 -05:00
|
|
|
// ListAccountPayments returns a paginated list of payments
|
|
|
|
|
// associated with the given account, sorted in ascending
|
|
|
|
|
// lexicographical order of their payment hash.
|
|
|
|
|
ListAccountPayments(ctx context.Context, id AccountID, offset,
|
|
|
|
|
limit int32) ([]*AccountPaymentEntry, error)
|
|
|
|
|
|
|
|
|
|
// CountAccountPayments returns the total number of payments associated
|
|
|
|
|
// with the given account.
|
|
|
|
|
CountAccountPayments(ctx context.Context, id AccountID) (uint64, error)
|
|
|
|
|
|
2022-11-17 16:50:42 +01:00
|
|
|
// RemoveAccount finds an account by its ID and removes it from the¨
|
|
|
|
|
// store.
|
2024-12-27 17:24:01 +02:00
|
|
|
RemoveAccount(ctx context.Context, id AccountID) error
|
2022-11-17 16:50:42 +01:00
|
|
|
|
|
|
|
|
// LastIndexes returns the last invoice add and settle index or
|
|
|
|
|
// ErrNoInvoiceIndexKnown if no indexes are known yet.
|
2024-12-27 17:24:01 +02:00
|
|
|
LastIndexes(ctx context.Context) (uint64, uint64, error)
|
2022-11-17 16:50:42 +01:00
|
|
|
|
|
|
|
|
// StoreLastIndexes stores the last invoice add and settle index.
|
2024-12-27 17:24:01 +02:00
|
|
|
StoreLastIndexes(ctx context.Context, addIndex,
|
|
|
|
|
settleIndex uint64) error
|
2022-11-17 16:50:42 +01:00
|
|
|
|
|
|
|
|
// Close closes the underlying store.
|
|
|
|
|
Close() error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Service is the main account service interface.
|
|
|
|
|
type Service interface {
|
|
|
|
|
// CheckBalance ensures an account is valid and has a balance equal to
|
|
|
|
|
// or larger than the amount that is required.
|
2024-12-27 17:24:01 +02:00
|
|
|
CheckBalance(ctx context.Context, id AccountID,
|
|
|
|
|
requiredBalance lnwire.MilliSatoshi) error
|
2022-11-17 16:50:42 +01:00
|
|
|
|
|
|
|
|
// AssociateInvoice associates a generated invoice with the given
|
|
|
|
|
// account, making it possible for the account to be credited in case
|
|
|
|
|
// the invoice is paid.
|
2024-12-27 17:24:01 +02:00
|
|
|
AssociateInvoice(ctx context.Context, id AccountID,
|
|
|
|
|
hash lntypes.Hash) error
|
2022-11-17 16:50:42 +01:00
|
|
|
|
|
|
|
|
// TrackPayment adds a new payment to be tracked to the service. If the
|
|
|
|
|
// payment is eventually settled, its amount needs to be debited from
|
|
|
|
|
// the given account.
|
2024-12-27 17:24:01 +02:00
|
|
|
TrackPayment(ctx context.Context, id AccountID, hash lntypes.Hash,
|
2022-11-17 16:50:42 +01:00
|
|
|
fullAmt lnwire.MilliSatoshi) error
|
|
|
|
|
|
|
|
|
|
// RemovePayment removes a failed payment from the service because it no
|
|
|
|
|
// longer needs to be tracked. The payment is certain to never succeed,
|
|
|
|
|
// so we never need to debit the amount from the account.
|
2024-12-27 17:24:01 +02:00
|
|
|
RemovePayment(ctx context.Context, hash lntypes.Hash) error
|
2023-09-15 13:25:28 +02:00
|
|
|
|
2023-09-20 01:07:20 +02:00
|
|
|
// AssociatePayment associates a payment (hash) with the given account,
|
|
|
|
|
// ensuring that the payment will be tracked for a user when LiT is
|
|
|
|
|
// restarted.
|
2024-12-27 17:24:01 +02:00
|
|
|
AssociatePayment(ctx context.Context, id AccountID,
|
|
|
|
|
paymentHash lntypes.Hash, fullAmt lnwire.MilliSatoshi) error
|
2024-06-05 15:30:54 -04:00
|
|
|
|
2024-06-04 16:58:49 -04:00
|
|
|
// PaymentErrored removes a pending payment from the accounts
|
|
|
|
|
// registered payment list. This should only ever be called if we are
|
|
|
|
|
// sure that the payment request errored out.
|
2024-12-27 17:24:01 +02:00
|
|
|
PaymentErrored(ctx context.Context, id AccountID,
|
|
|
|
|
hash lntypes.Hash) error
|
2024-06-04 16:58:49 -04:00
|
|
|
|
2025-02-11 19:14:36 +01:00
|
|
|
// CreditAccount increases the balance of an existing account in the
|
|
|
|
|
// database.
|
|
|
|
|
CreditAccount(ctx context.Context, accountID AccountID,
|
|
|
|
|
amount lnwire.MilliSatoshi) (*OffChainBalanceAccount, error)
|
|
|
|
|
|
|
|
|
|
// DebitAccount decreases the balance of an existing account in the
|
|
|
|
|
// database.
|
|
|
|
|
DebitAccount(ctx context.Context, accountID AccountID,
|
|
|
|
|
amount lnwire.MilliSatoshi) (*OffChainBalanceAccount, error)
|
|
|
|
|
|
2024-06-05 15:30:54 -04:00
|
|
|
RequestValuesStore
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RequestValues holds various values associated with a specific request that
|
|
|
|
|
// we may want access to when handling the response. At the moment this only
|
|
|
|
|
// stores payment related data.
|
|
|
|
|
type RequestValues struct {
|
|
|
|
|
// PaymentHash is the hash of the payment that this request is
|
|
|
|
|
// associated with.
|
|
|
|
|
PaymentHash lntypes.Hash
|
|
|
|
|
|
|
|
|
|
// PaymentAmount is the value of the payment being made.
|
|
|
|
|
PaymentAmount lnwire.MilliSatoshi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RequestValuesStore is a store that can be used to keep track of the mapping
|
|
|
|
|
// between a request ID and various values associated with that request which
|
|
|
|
|
// we may want access to when handling the request response.
|
|
|
|
|
type RequestValuesStore interface {
|
|
|
|
|
// RegisterValues stores values for the given request ID.
|
|
|
|
|
RegisterValues(reqID uint64, values *RequestValues) error
|
|
|
|
|
|
|
|
|
|
// GetValues returns the corresponding request values for the given
|
|
|
|
|
// request ID if they exist.
|
|
|
|
|
GetValues(reqID uint64) (*RequestValues, bool)
|
|
|
|
|
|
|
|
|
|
// DeleteValues deletes any values stored for the given request ID.
|
|
|
|
|
DeleteValues(reqID uint64)
|
2022-11-17 16:50:42 +01:00
|
|
|
}
|
2025-01-16 10:33:32 +02:00
|
|
|
|
|
|
|
|
// UpsertPaymentOption is a functional option that can be passed to the
|
|
|
|
|
// UpsertAccountPayment method to modify its behavior.
|
|
|
|
|
type UpsertPaymentOption func(*upsertAcctPaymentOption)
|
|
|
|
|
|
|
|
|
|
// upsertAcctPaymentOption is a struct that holds optional parameters for the
|
|
|
|
|
// UpsertAccountPayment method.
|
|
|
|
|
type upsertAcctPaymentOption struct {
|
2025-01-04 15:49:13 +02:00
|
|
|
debitAccount bool
|
|
|
|
|
errIfAlreadyPending bool
|
|
|
|
|
usePendingAmount bool
|
|
|
|
|
errIfAlreadySucceeded bool
|
2025-01-04 16:12:04 +02:00
|
|
|
errIfUnknown bool
|
2025-01-16 10:33:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// newUpsertPaymentOption creates a new upsertAcctPaymentOption with default
|
|
|
|
|
// values.
|
|
|
|
|
func newUpsertPaymentOption() *upsertAcctPaymentOption {
|
|
|
|
|
return &upsertAcctPaymentOption{
|
2025-01-04 15:49:13 +02:00
|
|
|
debitAccount: false,
|
|
|
|
|
errIfAlreadyPending: false,
|
|
|
|
|
usePendingAmount: false,
|
|
|
|
|
errIfAlreadySucceeded: false,
|
2025-01-04 16:12:04 +02:00
|
|
|
errIfUnknown: false,
|
2025-01-16 10:33:32 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WithDebitAccount is a functional option that can be passed to the
|
|
|
|
|
// UpsertAccountPayment method to indicate that the account balance should be
|
|
|
|
|
// debited by the full amount of the payment.
|
|
|
|
|
func WithDebitAccount() UpsertPaymentOption {
|
|
|
|
|
return func(o *upsertAcctPaymentOption) {
|
|
|
|
|
o.debitAccount = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WithErrIfAlreadyPending is a functional option that can be passed to the
|
|
|
|
|
// UpsertAccountPayment method to indicate that an error should be returned if
|
|
|
|
|
// the payment is already pending or succeeded.
|
|
|
|
|
func WithErrIfAlreadyPending() UpsertPaymentOption {
|
|
|
|
|
return func(o *upsertAcctPaymentOption) {
|
|
|
|
|
o.errIfAlreadyPending = true
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-01-04 15:49:13 +02:00
|
|
|
|
|
|
|
|
// WithErrIfAlreadySucceeded is a functional option that can be passed to the
|
|
|
|
|
// UpsertAccountPayment method to indicate that the ErrAlreadySucceeded error
|
|
|
|
|
// should be returned if the payment is already in a succeeded state.
|
|
|
|
|
func WithErrIfAlreadySucceeded() UpsertPaymentOption {
|
|
|
|
|
return func(o *upsertAcctPaymentOption) {
|
|
|
|
|
o.errIfAlreadySucceeded = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WithPendingAmount is a functional option that can be passed to the
|
|
|
|
|
// UpsertAccountPayment method to indicate that if the payment already exists,
|
|
|
|
|
// then the known payment amount should be used instead of the new value passed
|
|
|
|
|
// to the method.
|
|
|
|
|
func WithPendingAmount() UpsertPaymentOption {
|
|
|
|
|
return func(o *upsertAcctPaymentOption) {
|
|
|
|
|
o.usePendingAmount = true
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-01-04 16:12:04 +02:00
|
|
|
|
|
|
|
|
// WithErrIfUnknown is a functional option that can be passed to the
|
|
|
|
|
// UpsertAccountPayment method to indicate that the ErrPaymentNotAssociated
|
|
|
|
|
// error should be returned if the payment is not associated with the account.
|
|
|
|
|
func WithErrIfUnknown() UpsertPaymentOption {
|
|
|
|
|
return func(o *upsertAcctPaymentOption) {
|
|
|
|
|
o.errIfUnknown = true
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-15 00:29:17 -05:00
|
|
|
|
|
|
|
|
// First, ensure that if a label is set, it can't be
|
|
|
|
|
// mistaken for a hex encoded account ID.
|
|
|
|
|
func checkLabel(label string) error {
|
|
|
|
|
if len(label) == hex.EncodedLen(AccountIDLen) {
|
|
|
|
|
_, err := hex.DecodeString(label)
|
|
|
|
|
if err == nil {
|
2026-05-05 12:42:23 -05:00
|
|
|
return fmt.Errorf("the label '%s' "+
|
|
|
|
|
"is not allowed as it "+
|
2026-04-15 00:29:17 -05:00
|
|
|
"can be mistaken for an account ID", label)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|