models

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Feb 15, 2026 License: MIT Imports: 5 Imported by: 0

README

Models Package

Common domain types for Nivo services with India-centric defaults.

Overview

The models package provides fundamental domain types used across all Nivo services, with a focus on fintech-specific types like Money and Currency that require precision and correctness. INR (Indian Rupee) is the primary currency for India-centric operations.

Features

  • Money: Precise monetary amounts using integer arithmetic (no float precision issues)
  • Currency: ISO 4217 currency codes with validation
  • Timestamp: Custom timestamp type with consistent JSON/database serialization

Money Type

The Money type stores monetary amounts in the smallest currency unit (cents) to avoid floating-point precision issues.

Basic Usage
import "github.com/1mb-dev/nivomoney/shared/models"

// Create from paise (smallest unit for INR)
money := models.NewMoney(10050, models.INR) // ₹100.50

// Create from float
money := models.NewMoneyFromFloat(100.50, models.INR)

// String representation
fmt.Println(money) // Output: "100.50 INR"

// Convert to float
amount := money.ToFloat() // 100.50

// Using default currency
defaultMoney := models.NewMoney(50000, models.DefaultCurrency) // ₹500.00 (INR is default)
Arithmetic Operations
price := models.NewMoney(100000, models.INR)  // ₹1,000.00
tax := models.NewMoney(18000, models.INR)     // ₹180.00 (18% GST)

// Addition
total, err := price.Add(tax) // ₹1,180.00
if err != nil {
    // Handle currency mismatch error
}

// Subtraction
discount := models.NewMoney(20000, models.INR)
final, err := total.Subtract(discount) // ₹980.00

// Multiplication
double := price.Multiply(2) // ₹2,000.00

// Division
half := price.Divide(2) // ₹500.00
Comparisons
balance := models.NewMoney(1000000, models.INR) // ₹10,000.00
amount := models.NewMoney(500000, models.INR)   // ₹5,000.00

if balance.GreaterThan(amount) {
    fmt.Println("Sufficient funds")
}

if amount.LessThanOrEqual(balance) {
    // Process transaction
}

if money1.Equal(money2) {
    // Same amount and currency
}
Validation
money := models.NewMoney(100000, models.INR) // ₹1,000.00

if money.IsZero() {
    // Handle zero amount
}

if money.IsPositive() {
    // Process positive amount
}

if money.IsNegative() {
    // Handle negative balance
}

// Validate currency
if err := money.Validate(); err != nil {
    // Handle invalid currency
}
JSON Serialization
money := models.NewMoney(10050, models.INR)

// Marshal to JSON
data, _ := json.Marshal(money)
// {"amount":10050,"currency":"INR"}

// Unmarshal from JSON
var decoded models.Money
json.Unmarshal(data, &decoded)

Currency Type

The Currency type represents ISO 4217 currency codes.

Supported Currencies

Primary Currency (India-centric):

  • INR - Indian Rupee (₹) - Default currency

International Currencies:

  • USD - US Dollar ($)
  • EUR - Euro (€)
  • GBP - British Pound (£)
  • JPY - Japanese Yen (¥)
  • CNY - Chinese Yuan (¥)
  • CAD - Canadian Dollar (C$)
  • AUD - Australian Dollar (A$)
  • CHF - Swiss Franc (CHF)
  • SGD - Singapore Dollar (S$)
Usage
// Using constants (INR is the default/primary currency)
currency := models.INR

// Using default currency constant
currency := models.DefaultCurrency // INR

// Parse from string
currency, err := models.ParseCurrency("INR")
if err != nil {
    // Handle invalid currency
}

// Case-insensitive parsing
currency, _ := models.ParseCurrency("inr") // Returns INR

// Validation
if err := currency.Validate(); err != nil {
    // Handle invalid currency
}

// Check support
if currency.IsSupported() {
    // Currency is valid
}

// Get all supported currencies
currencies := models.GetSupportedCurrencies()
Currency Properties
currency := models.INR

// Get symbol
symbol := currency.GetSymbol() // "₹"

// Get decimal places
places := currency.GetDecimalPlaces() // 2 (paise)
// Note: JPY returns 0 (no decimal places)

// String representation
name := currency.String() // "INR"

Timestamp Type

Custom timestamp type with consistent JSON and database serialization.

Usage
// Create from time.Time
t := time.Now()
ts := models.NewTimestamp(t)

// Get current timestamp
ts := models.Now()

// String representation (ISO 8601)
str := ts.String() // "2025-01-15T10:30:00Z"
Comparisons
ts1 := models.Now()
time.Sleep(1 * time.Second)
ts2 := models.Now()

if ts2.After(ts1) {
    fmt.Println("ts2 is after ts1")
}

if ts1.Before(ts2) {
    fmt.Println("ts1 is before ts2")
}

if ts1.Equal(ts1) {
    fmt.Println("Equal timestamps")
}
JSON Serialization
ts := models.Now()

// Marshal to JSON (ISO 8601 format)
data, _ := json.Marshal(ts)
// "2025-01-15T10:30:00Z"

// Unmarshal from JSON
var decoded models.Timestamp
json.Unmarshal(data, &decoded)

// Zero timestamps serialize as null
zero := models.Timestamp{}
data, _ := json.Marshal(zero) // null
Database Integration

The Timestamp type implements sql.Scanner and driver.Valuer for seamless database integration:

// Scan from database
var ts models.Timestamp
err := db.QueryRow("SELECT created_at FROM users WHERE id = $1", userID).Scan(&ts)

// Store in database
_, err := db.Exec("INSERT INTO users (name, created_at) VALUES ($1, $2)",
    name, models.Now())

Complete Examples

India UPI Transfer (Primary Use Case)
package main

import (
    "fmt"
    "github.com/1mb-dev/nivomoney/shared/models"
)

func UPITransfer(senderBalance, receiverBalance, amount models.Money) error {
    // Validate currencies match (should all be INR for UPI)
    if senderBalance.Currency != models.INR || amount.Currency != models.INR {
        return fmt.Errorf("UPI transfers require INR currency")
    }

    // Check sufficient funds
    if senderBalance.LessThan(amount) {
        return fmt.Errorf("insufficient funds")
    }

    // Validate amount is positive
    if !amount.IsPositive() {
        return fmt.Errorf("amount must be positive")
    }

    // Perform transfer
    newSenderBalance, _ := senderBalance.Subtract(amount)
    newReceiverBalance, _ := receiverBalance.Add(amount)

    fmt.Printf("Sender:   %s -> %s\n", senderBalance, newSenderBalance)
    fmt.Printf("Receiver: %s -> %s\n", receiverBalance, newReceiverBalance)

    return nil
}

func main() {
    // UPI transfer amounts in paise (1 rupee = 100 paise)
    sender := models.NewMoney(1000000, models.INR)   // ₹10,000.00
    receiver := models.NewMoney(500000, models.INR)  // ₹5,000.00
    amount := models.NewMoney(250000, models.INR)    // ₹2,500.00

    if err := UPITransfer(sender, receiver, amount); err != nil {
        fmt.Printf("Transfer failed: %v\n", err)
        return
    }

    fmt.Println("UPI transfer successful!")
    // Output:
    // Sender:   10000.00 INR -> 7500.00 INR
    // Receiver: 5000.00 INR -> 7500.00 INR
    // UPI transfer successful!
}
International Transfer
func InternationalTransfer(from, to models.Money, amount models.Money) error {
    // Validate currencies match
    if from.Currency != amount.Currency {
        return fmt.Errorf("currency mismatch")
    }

    // Check sufficient funds
    if from.LessThan(amount) {
        return fmt.Errorf("insufficient funds")
    }

    // Validate amount is positive
    if !amount.IsPositive() {
        return fmt.Errorf("amount must be positive")
    }

    // Perform transfer
    newFrom, _ := from.Subtract(amount)
    newTo, _ := to.Add(amount)

    fmt.Printf("From: %s -> %s\n", from, newFrom)
    fmt.Printf("To:   %s -> %s\n", to, newTo)

    return nil
}

func main() {
    sender := models.NewMoney(1000000, models.INR)   // ₹10,000.00
    receiver := models.NewMoney(500000, models.INR)  // ₹5,000.00
    amount := models.NewMoney(250000, models.INR)    // ₹2,500.00

    if err := InternationalTransfer(sender, receiver, amount); err != nil {
        fmt.Printf("Transfer failed: %v\n", err)
        return
    }

    fmt.Println("Transfer successful!")
}

Best Practices

Money
  1. Always use integer storage: Store amounts in paise (for INR) to avoid float precision issues

    // Good (paise for INR)
    money := models.NewMoney(100050, models.INR) // ₹1,000.50
    
    // Avoid
    amount := 1000.50 // float64 has precision issues
    
  2. Use default currency (INR): For India-centric operations, use default currency constant

    // Good
    money := models.NewMoney(50000, models.DefaultCurrency) // ₹500.00 (INR)
    
    // Explicit
    money := models.NewMoney(50000, models.INR)
    
  3. Validate currency compatibility: Always check currencies match before operations

    result, err := money1.Add(money2)
    if err != nil {
        // Handle currency mismatch
    }
    
  4. Use comparison methods: Don't compare amounts directly

    // Good
    if balance.GreaterThan(amount) { }
    
    // Avoid
    if balance.Amount > amount.Amount { } // Ignores currency
    
  5. Handle zero division: Check divisor before division

    if divisor != 0 {
        result := money.Divide(divisor)
    }
    
Currency
  1. Use INR as primary: Default to INR for India-centric operations

    // Good (India-centric)
    currency := models.DefaultCurrency // INR
    
    // Explicit
    currency := models.INR
    
  2. Use constants: Prefer predefined currency constants

    // Good
    currency := models.INR
    
    // Less ideal
    currency := models.Currency("INR")
    
  3. Always validate: Validate currency before use

    currency, err := models.ParseCurrency(input)
    if err != nil {
        return err
    }
    
Timestamp
  1. Use UTC: Always work with UTC timestamps

    ts := models.NewTimestamp(time.Now().UTC())
    
  2. Check for zero: Check if timestamp is set before use

    if !ts.IsZero() {
        // Use timestamp
    }
    

Testing

go test ./shared/models/...
go test -cover ./shared/models/...

Documentation

Overview

Package models provides common domain types for Nivo services.

Index

Constants

View Source
const DefaultCurrency = INR

DefaultCurrency is the default currency for the system (India-centric)

Variables

This section is empty.

Functions

This section is empty.

Types

type Currency

type Currency string

Currency represents an ISO 4217 currency code.

const (
	INR Currency = "INR" // Indian Rupee (primary)
	USD Currency = "USD" // US Dollar
	EUR Currency = "EUR" // Euro
	GBP Currency = "GBP" // British Pound
	JPY Currency = "JPY" // Japanese Yen
	CNY Currency = "CNY" // Chinese Yuan
	CAD Currency = "CAD" // Canadian Dollar
	AUD Currency = "AUD" // Australian Dollar
	CHF Currency = "CHF" // Swiss Franc
	SGD Currency = "SGD" // Singapore Dollar
)

Supported currencies Note: INR (Indian Rupee) is listed first as the primary currency for India-centric operations

func GetSupportedCurrencies

func GetSupportedCurrencies() []Currency

GetSupportedCurrencies returns a list of all supported currencies.

func ParseCurrency

func ParseCurrency(s string) (Currency, error)

ParseCurrency parses a string into a Currency.

func (Currency) GetDecimalPlaces

func (c Currency) GetDecimalPlaces() int

GetDecimalPlaces returns the number of decimal places for a currency. Most currencies use 2 decimal places, but some (like JPY) use 0.

func (Currency) GetSymbol

func (c Currency) GetSymbol() string

GetSymbol returns the currency symbol.

func (Currency) IsSupported

func (c Currency) IsSupported() bool

IsSupported returns true if the currency is supported.

func (Currency) String

func (c Currency) String() string

String returns the currency code as a string.

func (Currency) Validate

func (c Currency) Validate() error

Validate checks if the currency is supported.

type Money

type Money struct {
	Amount   int64    `json:"amount"`   // Amount in smallest unit (cents)
	Currency Currency `json:"currency"` // Currency code
}

Money represents a monetary amount in the smallest currency unit (e.g., cents). Using int64 avoids floating-point precision issues.

func NewMoney

func NewMoney(amount int64, currency Currency) Money

NewMoney creates a new Money instance.

func NewMoneyFromFloat

func NewMoneyFromFloat(amount float64, currency Currency) Money

NewMoneyFromFloat creates Money from a float value (e.g., 10.50 USD). The float is multiplied by 100 to convert to cents.

func (Money) Add

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

Add adds two Money values. Returns error if currencies don't match.

func (Money) Divide

func (m Money) Divide(divisor int64) Money

Divide divides Money by an integer divisor.

func (Money) Equal

func (m Money) Equal(other Money) bool

Equal returns true if both amount and currency are equal.

func (Money) GreaterThan

func (m Money) GreaterThan(other Money) bool

GreaterThan returns true if m > other. Returns false if currencies don't match.

func (Money) GreaterThanOrEqual

func (m Money) GreaterThanOrEqual(other Money) bool

GreaterThanOrEqual returns true if m >= other. Returns false if currencies don't match.

func (Money) IsNegative

func (m Money) IsNegative() bool

IsNegative returns true if the amount is negative.

func (Money) IsPositive

func (m Money) IsPositive() bool

IsPositive returns true if the amount is positive.

func (Money) IsZero

func (m Money) IsZero() bool

IsZero returns true if the amount is zero.

func (Money) LessThan

func (m Money) LessThan(other Money) bool

LessThan returns true if m < other. Returns false if currencies don't match.

func (Money) LessThanOrEqual

func (m Money) LessThanOrEqual(other Money) bool

LessThanOrEqual returns true if m <= other. Returns false if currencies don't match.

func (Money) MarshalJSON

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

MarshalJSON implements json.Marshaler.

func (Money) Multiply

func (m Money) Multiply(factor int64) Money

Multiply multiplies Money by an integer factor.

func (Money) String

func (m Money) String() string

String returns a human-readable string representation.

func (Money) Subtract

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

Subtract subtracts two Money values. Returns error if currencies don't match.

func (Money) ToFloat

func (m Money) ToFloat() float64

ToFloat converts Money to float64 representation (e.g., 1050 cents = 10.50).

func (*Money) UnmarshalJSON

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

UnmarshalJSON implements json.Unmarshaler.

func (Money) Validate

func (m Money) Validate() error

Validate checks if the Money value is valid.

type Timestamp

type Timestamp struct {
	time.Time
}

Timestamp wraps time.Time with custom JSON marshaling for ISO 8601 format.

func NewTimestamp

func NewTimestamp(t time.Time) Timestamp

NewTimestamp creates a new Timestamp from time.Time.

func Now

func Now() Timestamp

Now returns a Timestamp for the current time.

func (Timestamp) After

func (t Timestamp) After(u Timestamp) bool

After reports whether t is after u.

func (Timestamp) Before

func (t Timestamp) Before(u Timestamp) bool

Before reports whether t is before u.

func (Timestamp) Equal

func (t Timestamp) Equal(u Timestamp) bool

Equal reports whether t and u represent the same time instant.

func (Timestamp) MarshalJSON

func (t Timestamp) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler. Outputs ISO 8601 format: 2006-01-02T15:04:05Z07:00

func (*Timestamp) Scan

func (t *Timestamp) Scan(value interface{}) error

Scan implements sql.Scanner for database retrieval.

func (Timestamp) String

func (t Timestamp) String() string

String returns the ISO 8601 formatted string.

func (*Timestamp) UnmarshalJSON

func (t *Timestamp) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler.

func (Timestamp) Value

func (t Timestamp) Value() (driver.Value, error)

Value implements driver.Valuer for database storage.

Jump to

Keyboard shortcuts

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