money

package
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 2, 2025 License: MIT Imports: 8 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrOverflow occurs when an operation would exceed int64 capacity.
	ErrOverflow = errors.New("money: arithmetic overflow")

	// ErrAssetMismatch occurs when operating on different assets.
	ErrAssetMismatch = errors.New("money: asset mismatch")

	// ErrNegativeAmount occurs when negative amount is invalid for operation.
	ErrNegativeAmount = errors.New("money: negative amount not allowed")

	// ErrInvalidFormat occurs when parsing fails.
	ErrInvalidFormat = errors.New("money: invalid format")

	// ErrDivisionByZero occurs when dividing by zero.
	ErrDivisionByZero = errors.New("money: division by zero")
)
View Source
var KnownStablecoins = map[string]string{
	"CASHx9KJUStyftLFWGvEVf59SGeG9sh5FfcnZMVPCASH": "CASH",
	"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v": "USDC",
	"Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB": "USDT",
	"2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo": "PYUSD",
}

KnownStablecoins maps Solana token mint addresses to their stablecoin symbols. These are the ONLY tokens that should be used for payments to ensure proper decimal handling (all stablecoins use 2-6 decimals and are pegged to $1).

WARNING: Using non-stablecoin tokens (SOL, BONK, etc.) will cause precision issues because the system rounds to 2 decimal places (cents).

Functions

func GetMintAddressForSymbol

func GetMintAddressForSymbol(symbol string) string

GetMintAddressForSymbol returns the mint address for a stablecoin symbol. Returns empty string if symbol not found.

func GetStablecoinSymbol

func GetStablecoinSymbol(mintAddress string) string

GetStablecoinSymbol returns the symbol for a stablecoin mint address. Returns empty string if not a known stablecoin.

func IsStablecoin

func IsStablecoin(mintAddress string) bool

IsStablecoin returns true if the mint address is a known stablecoin.

func RegisterAsset

func RegisterAsset(asset Asset) error

RegisterAsset adds a new asset to the registry (for testing or dynamic tokens).

func ValidateStablecoinMint

func ValidateStablecoinMint(mintAddress string) (string, error)

ValidateStablecoinMint checks if a token mint address is a known stablecoin. Returns the stablecoin symbol if valid, or an error if not.

Why this matters:

  • Typo in token mint = payments go to wrong token = permanent loss
  • Non-stablecoins have unpredictable values (1 SOL ≠ $1, 1 BONK ≠ $1)
  • System rounds to 2 decimal places assuming $1 peg

Types

type Asset

type Asset struct {
	Code     string // Asset code (USD, USDC, SOL, etc.)
	Decimals uint8  // Number of decimal places (2 for USD, 6 for USDC, 9 for SOL)
	Type     AssetType
	Metadata AssetMetadata
}

Asset represents a currency or token with its properties.

func GetAsset

func GetAsset(code string) (Asset, error)

GetAsset retrieves an asset from the registry.

func ListAssets

func ListAssets() []Asset

ListAssets returns all registered assets.

func MustGetAsset

func MustGetAsset(code string) Asset

MustGetAsset retrieves an asset and panics if not found (for tests/constants).

func (Asset) GetSolanaMint

func (a Asset) GetSolanaMint() (string, error)

GetSolanaMint returns the Solana mint address or error.

func (Asset) GetStripeCurrency

func (a Asset) GetStripeCurrency() (string, error)

GetStripeCurrency returns the Stripe currency code or error.

func (Asset) IsSPLToken

func (a Asset) IsSPLToken() bool

IsSPLToken returns true if the asset is a Solana SPL token.

func (Asset) IsStripeCurrency

func (a Asset) IsStripeCurrency() bool

IsStripeCurrency returns true if the asset is a Stripe fiat currency.

type AssetMetadata

type AssetMetadata struct {
	StripeCurrency string // Stripe currency code (lowercase: "usd", "eur")
	SolanaMint     string // Solana token mint address (base58)
}

AssetMetadata contains backend-specific information.

type AssetType

type AssetType int

AssetType categorizes the asset for different backends.

const (
	AssetTypeFiat AssetType = iota // Fiat currency (Stripe)
	AssetTypeSPL                   // Solana SPL token
)

type Money

type Money struct {
	Asset  Asset // The currency/token
	Atomic int64 // Amount in smallest unit (cents, lamports, etc.)
}

Money represents a monetary amount in atomic units for a specific asset. All arithmetic is performed on int64 to avoid floating-point precision issues.

Examples:

  • $10.50 USD = Money{Asset: USD, Atomic: 1050} // 1050 cents
  • 1.5 USDC = Money{Asset: USDC, Atomic: 1500000} // 1.5 × 10^6
  • 0.5 SOL = Money{Asset: SOL, Atomic: 500000000} // 0.5 × 10^9

func FromAtomic

func FromAtomic(asset Asset, atomic string) (Money, error)

FromAtomic creates Money from an atomic units string.

Example:

  • FromAtomic(USD, "1050") → $10.50
  • FromAtomic(USDC, "1500000") → 1.5 USDC

func FromMajor

func FromMajor(asset Asset, major string) (Money, error)

FromMajor creates Money from a major unit string (e.g., "10.50"). Uses half-up rounding for fractional atomic units.

Examples:

  • FromMajor(USD, "10.50") → 1050 cents
  • FromMajor(USDC, "1.5") → 1500000 micro-USDC

func New

func New(asset Asset, atomic int64) Money

New creates a Money from atomic units.

func SumMoney

func SumMoney(amounts ...Money) (Money, error)

SumMoney adds multiple Money values together. All values must be the same asset. Returns error if assets don't match or overflow occurs.

func Zero

func Zero(asset Asset) Money

Zero returns a zero amount for the given asset.

func (Money) Abs

func (m Money) Abs() Money

Abs returns the absolute value.

func (Money) Add

func (m Money) Add(other Money) (Money, error)

Add returns the sum of two Money values. Returns error if assets don't match or overflow occurs.

func (Money) ApplyFixedDiscount

func (m Money) ApplyFixedDiscount(discount Money) (Money, error)

ApplyFixedDiscount subtracts a fixed amount from the Money value. Returns zero if the discount exceeds the original amount.

func (Money) ApplyPercentageDiscount

func (m Money) ApplyPercentageDiscount(discountPercent float64) (Money, error)

ApplyPercentageDiscount applies a percentage discount to the Money amount. The discount is a value between 0-100 (e.g., 10 for 10% off). Uses half-up rounding (standard) for the discount calculation. Returns the discounted amount (not the discount itself).

func (Money) ApplyPercentageDiscountWithRounding

func (m Money) ApplyPercentageDiscountWithRounding(discountPercent float64, mode RoundingMode) (Money, error)

ApplyPercentageDiscountWithRounding applies a percentage discount with configurable rounding. The discount is a value between 0-100 (e.g., 10 for 10% off). Returns the discounted amount (not the discount itself).

func (Money) Div

func (m Money) Div(divisor int64) (Money, error)

Div divides Money by an integer divisor. Uses half-up rounding for remainders.

func (Money) Equal

func (m Money) Equal(other Money) bool

Equal returns true if m == other (same asset and amount).

func (Money) GreaterThan

func (m Money) GreaterThan(other Money) bool

GreaterThan returns true if m > other (same asset required).

func (Money) IsNegative

func (m Money) IsNegative() bool

IsNegative returns true if amount is less than zero.

func (Money) IsPositive

func (m Money) IsPositive() bool

IsPositive returns true if amount is greater than zero.

func (Money) IsZero

func (m Money) IsZero() bool

IsZero returns true if amount is exactly zero.

func (Money) LessThan

func (m Money) LessThan(other Money) bool

LessThan returns true if m < other (same asset required).

func (Money) MarshalJSON

func (m Money) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler for Money. Outputs atomic-only JSON:

{
  "asset": "USD",
  "atomic": "1050"
}

func (Money) Mul

func (m Money) Mul(multiplier int64) (Money, error)

Mul multiplies Money by an integer scalar.

func (Money) MulBasisPoints

func (m Money) MulBasisPoints(basisPoints int64) (Money, error)

MulBasisPoints multiplies Money by basis points (1/100th of a percent). Example: amount.MulBasisPoints(250) applies a 2.5% rate. Uses half-up rounding (standard).

func (Money) MulBasisPointsWithRounding

func (m Money) MulBasisPointsWithRounding(basisPoints int64, mode RoundingMode) (Money, error)

MulBasisPointsWithRounding multiplies Money by basis points with configurable rounding. Example: amount.MulBasisPointsWithRounding(250, RoundingCeiling) applies a 2.5% rate with ceiling rounding.

func (Money) MulPercent

func (m Money) MulPercent(percent int64) (Money, error)

MulPercent multiplies Money by a percentage (0-100). Example: amount.MulPercent(10) applies a 10% rate.

func (Money) MultiplyByFloat

func (m Money) MultiplyByFloat(multiplier float64) (Money, error)

MultiplyByFloat multiplies Money by a float64 using precise decimal arithmetic. This is used for percentage-based calculations where the multiplier is not an integer. Uses big.Float for intermediate precision, then rounds half-up to nearest atomic unit. WARNING: Only use when necessary (e.g., calculating percentage discounts). Prefer integer operations when possible.

func (Money) Negate

func (m Money) Negate() Money

Negate returns the negated amount.

func (Money) RoundUpToCents

func (m Money) RoundUpToCents() Money

RoundUpToCents rounds the amount up to the nearest cent (2 decimal places). This is used for pricing to ensure we never undercharge. Only affects assets with more than 2 decimals (like USDC with 6 decimals).

For positive amounts: rounds up (ceiling) - $0.184 → $0.19 For negative amounts: rounds towards zero (floor) - -$0.184 → -$0.18 This ensures consistent behavior for both payments and refunds.

func (Money) String

func (m Money) String() string

String returns a human-readable representation. Example: Money{USD, 1050} → "$10.50 USD"

func (Money) Sub

func (m Money) Sub(other Money) (Money, error)

Sub returns the difference of two Money values.

func (Money) ToAtomic

func (m Money) ToAtomic() string

ToAtomic returns the atomic units as a string.

func (Money) ToMajor

func (m Money) ToMajor() string

ToMajor converts Money to major unit string with proper decimal places.

Examples:

  • Money{USD, 1050}.ToMajor() → "10.50"
  • Money{USDC, 1500000}.ToMajor() → "1.500000"
  • Money{USDC, 1500000}.ToMajor() → "1.500000"

func (*Money) UnmarshalJSON

func (m *Money) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler for Money. Accepts atomic format only:

  • {"asset":"USDC", "atomic":"1500000"} → Money{USDC, 1500000}

Returns error if:

  • Asset code is missing or unknown
  • Atomic field is missing
  • Parsing fails

type MoneyJSON

type MoneyJSON struct {
	Asset  string `json:"asset"`  // Asset code (USD, USDC, SOL, etc.)
	Atomic string `json:"atomic"` // Atomic units as string
}

MoneyJSON represents the JSON format for Money. Uses atomic units for precision:

{"asset":"USDC", "atomic":"1500000"}

type MoneyRequest

type MoneyRequest Money

MoneyRequest is a helper type for API request parsing. Use this in request structs for clearer intent.

Example:

type PaymentRequest struct {
    Amount MoneyRequest `json:"amount"`
}

func (MoneyRequest) MarshalJSON

func (mr MoneyRequest) MarshalJSON() ([]byte, error)

MarshalJSON for MoneyRequest uses the same atomic-only format as Money.

func (MoneyRequest) ToMoney

func (mr MoneyRequest) ToMoney() Money

ToMoney converts MoneyRequest to Money.

func (*MoneyRequest) UnmarshalJSON

func (mr *MoneyRequest) UnmarshalJSON(data []byte) error

UnmarshalJSON for MoneyRequest uses the same parsing as Money.

type MoneyResponse

type MoneyResponse Money

MoneyResponse is a helper type for API response formatting. Use this in response structs for clearer intent.

Example:

type QuoteResponse struct {
    Total MoneyResponse `json:"total"`
}

func FromMoney

func FromMoney(m Money) MoneyResponse

FromMoney creates a MoneyResponse from Money.

func (MoneyResponse) MarshalJSON

func (mr MoneyResponse) MarshalJSON() ([]byte, error)

MarshalJSON for MoneyResponse uses the same atomic-only format as Money.

func (MoneyResponse) ToMoney

func (mr MoneyResponse) ToMoney() Money

ToMoney converts MoneyResponse to Money.

func (*MoneyResponse) UnmarshalJSON

func (mr *MoneyResponse) UnmarshalJSON(data []byte) error

UnmarshalJSON for MoneyResponse uses the same parsing as Money.

type RoundingMode

type RoundingMode int

RoundingMode determines how fractional cents are rounded.

const (
	// RoundingStandard uses half-up rounding (0.5 rounds up).
	// This matches Stripe's default behavior: $0.025 → $0.03, $0.024 → $0.02
	RoundingStandard RoundingMode = iota

	// RoundingCeiling always rounds up to the next cent.
	// Example: $0.024 → $0.03, $0.001 → $0.01
	RoundingCeiling
)

func ParseRoundingMode

func ParseRoundingMode(mode string) RoundingMode

ParseRoundingMode converts a string to RoundingMode. Accepts "standard", "ceiling", or empty string (defaults to standard).

type SPLAdapter

type SPLAdapter struct{}

SPLAdapter converts Money to Solana SPL token format. SPL tokens use uint64 for amounts, while we use int64 internally.

func NewSPLAdapter

func NewSPLAdapter() *SPLAdapter

NewSPLAdapter creates a new SPL token adapter.

func (*SPLAdapter) FromSPLAmount

func (a *SPLAdapter) FromSPLAmount(mint string, amount uint64) (Money, error)

FromSPLAmount converts SPL token format to Money. Takes mint address and uint64 amount.

Example:

  • ("EPjF...", 1500000) → Money{USDC, 1500000} // 1.5 USDC
  • ("So11...", 500000000) → Money{SOL, 500000000} // 0.5 SOL

Returns error if:

  • Mint address is not recognized
  • Amount exceeds int64 max value (overflow)

func (*SPLAdapter) GetMintDecimals

func (a *SPLAdapter) GetMintDecimals(mint string) (uint8, error)

GetMintDecimals returns the number of decimals for an SPL token mint. This is useful for external callers who have a mint address and need to know the token's decimal places.

func (*SPLAdapter) ToSPLAmount

func (a *SPLAdapter) ToSPLAmount(m Money) (mint string, amount uint64, err error)

ToSPLAmount converts Money to SPL token format. Returns (mint address, amount) where:

  • mint is the base58-encoded Solana token mint address
  • amount is uint64 in token's atomic units (lamports, micro-USDC, etc.)

Example:

  • Money{USDC, 1500000} → ("EPjF...", 1500000) // 1.5 USDC
  • Money{SOL, 500000000} → ("So11...", 500000000) // 0.5 SOL

Returns error if:

  • Asset is not an SPL token
  • Amount is negative (SPL tokens use uint64, cannot represent negative)
  • Amount exceeds uint64 max value

func (*SPLAdapter) ValidateSPLAmount

func (a *SPLAdapter) ValidateSPLAmount(m Money) error

ValidateSPLAmount checks if a Money value is valid for SPL tokens. SPL token requirements:

  • Must be an SPL token asset
  • Amount must be non-negative (uint64 limitation)
  • Amount must fit in uint64 (always true for non-negative int64)

type StripeAdapter

type StripeAdapter struct{}

StripeAdapter converts Money to Stripe API format. Stripe expects amounts in the currency's smallest unit (cents for USD).

func NewStripeAdapter

func NewStripeAdapter() *StripeAdapter

NewStripeAdapter creates a new Stripe adapter.

func (*StripeAdapter) FromStripeAmount

func (a *StripeAdapter) FromStripeAmount(currency string, amount int64) (Money, error)

FromStripeAmount converts Stripe format to Money. Takes Stripe currency code (lowercase) and amount in smallest unit.

Example:

  • ("usd", 1050) → Money{USD, 1050} // $10.50
  • ("eur", 2500) → Money{EUR, 2500} // €25.00

Returns error if currency code is not recognized.

func (*StripeAdapter) ToStripeAmount

func (a *StripeAdapter) ToStripeAmount(m Money) (currency string, amount int64, err error)

ToStripeAmount converts Money to Stripe format. Returns (currency, amount) where:

  • currency is lowercase Stripe currency code (e.g., "usd", "eur")
  • amount is int64 in smallest unit (cents)

Example:

  • Money{USD, 1050} → ("usd", 1050) // $10.50
  • Money{EUR, 2500} → ("eur", 2500) // €25.00

Returns error if asset is not a Stripe-supported fiat currency.

func (*StripeAdapter) ValidateStripeAmount

func (a *StripeAdapter) ValidateStripeAmount(m Money) error

ValidateStripeAmount checks if a Money value is valid for Stripe. Stripe has specific requirements:

  • Must be a fiat currency
  • Amount must be non-negative
  • Amount must be within Stripe's limits (0 to 99,999,999 for most currencies)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL