satellite/payments/monetary: add USDMicro and json marshaling
Adds USDMicro currency which support fraction of a cent with decimal places for better billing amounts accuracy. Adds JSON marshaling and unmarshaling for monetary.Amount, so that it can be converted to/from JSON. Change-Id: I034eba120ed23b6ba00b2d81a4f1b9db5f9a203f
This commit is contained in:
parent
e0d3e48b66
commit
7a9b2a707b
@ -4,6 +4,7 @@
|
||||
package monetary
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
@ -43,6 +44,9 @@ var (
|
||||
// USDollars is the currency of United States dollars, where fractional
|
||||
// cents are not supported.
|
||||
USDollars = NewCurrency("US dollars", "USD", 2)
|
||||
// USDollarsMicro is the currency of United States dollars, where fractional
|
||||
// cents are supported with 2 decimal places.
|
||||
USDollarsMicro = NewCurrency("US dollars", "USDMicro", 6)
|
||||
// Bitcoin is the currency for the well-known cryptocurrency Bitcoin
|
||||
// (a.k.a. BTC).
|
||||
Bitcoin = NewCurrency("Bitcoin (BTC)", "BTC", 8)
|
||||
@ -54,6 +58,24 @@ var (
|
||||
Error = errs.Class("monetary error")
|
||||
)
|
||||
|
||||
// CurrencyFromSymbol returns currency based on symbol.
|
||||
func CurrencyFromSymbol(symbol string) (*Currency, error) {
|
||||
switch symbol {
|
||||
case "STORJ":
|
||||
return StorjToken, nil
|
||||
case "BTC":
|
||||
return Bitcoin, nil
|
||||
case "USD":
|
||||
return USDollars, nil
|
||||
case "USDMicro":
|
||||
return USDollarsMicro, nil
|
||||
case "goats":
|
||||
return LiveGoats, nil
|
||||
default:
|
||||
return nil, errs.New("invalid currency symbol")
|
||||
}
|
||||
}
|
||||
|
||||
// Amount represents a monetary amount, encapsulating a value and a currency.
|
||||
//
|
||||
// The value of the Amount is represented in "base units", meaning units of the
|
||||
@ -115,6 +137,38 @@ func (a Amount) Equal(other Amount) bool {
|
||||
return a.currency == other.currency && a.baseUnits == other.baseUnits
|
||||
}
|
||||
|
||||
// amountJSON is amount json data structure.
|
||||
type amountJSON struct {
|
||||
Value decimal.Decimal `json:"value"`
|
||||
Currency string `json:"currency"`
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals json bytes into amount.
|
||||
func (a *Amount) UnmarshalJSON(data []byte) error {
|
||||
var amountJSON amountJSON
|
||||
if err := json.Unmarshal(data, &amountJSON); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
curr, err := CurrencyFromSymbol(amountJSON.Currency)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*a = AmountFromDecimal(amountJSON.Value, curr)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON marshals amount into json.
|
||||
func (a Amount) MarshalJSON() ([]byte, error) {
|
||||
amountJSON := amountJSON{
|
||||
Value: a.AsDecimal(),
|
||||
Currency: a.currency.symbol,
|
||||
}
|
||||
|
||||
return json.Marshal(amountJSON)
|
||||
}
|
||||
|
||||
// AmountFromBaseUnits creates a new Amount instance from the given count of
|
||||
// base units and in the given currency.
|
||||
func AmountFromBaseUnits(units int64, currency *Currency) Amount {
|
||||
|
@ -4,6 +4,8 @@
|
||||
package monetary
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
@ -97,3 +99,79 @@ func TestAmountFromDecimalAndAmountAsDecimal(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAmountJSONMarshal(t *testing.T) {
|
||||
tests := []struct {
|
||||
Amount Amount
|
||||
JSON string
|
||||
}{
|
||||
{
|
||||
Amount: AmountFromBaseUnits(100000000000, StorjToken),
|
||||
JSON: fmt.Sprintf(`{"value":"1000","currency":"%s"}`, StorjToken.Symbol()),
|
||||
},
|
||||
{
|
||||
Amount: AmountFromBaseUnits(10055, USDollars),
|
||||
JSON: fmt.Sprintf(`{"value":"100.55","currency":"%s"}`, USDollars.Symbol()),
|
||||
},
|
||||
{
|
||||
Amount: AmountFromBaseUnits(100555500, USDollarsMicro),
|
||||
JSON: fmt.Sprintf(`{"value":"100.5555","currency":"%s"}`, USDollarsMicro.Symbol()),
|
||||
},
|
||||
{
|
||||
Amount: AmountFromBaseUnits(100555555, USDollarsMicro),
|
||||
JSON: fmt.Sprintf(`{"value":"100.555555","currency":"%s"}`, USDollarsMicro.Symbol()),
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
b, err := json.Marshal(test.Amount)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.JSON, string(b))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAmountJSONUnmarshal(t *testing.T) {
|
||||
tests := []struct {
|
||||
JSON string
|
||||
BaseUnits int64
|
||||
Currency *Currency
|
||||
}{
|
||||
{
|
||||
JSON: fmt.Sprintf(`{"value":"100","currency":"%s"}`, StorjToken.Symbol()),
|
||||
BaseUnits: 10000000000,
|
||||
Currency: StorjToken,
|
||||
},
|
||||
{
|
||||
JSON: fmt.Sprintf(`{"value":"50","currency":"%s"}`, Bitcoin.Symbol()),
|
||||
BaseUnits: 5000000000,
|
||||
Currency: Bitcoin,
|
||||
},
|
||||
{
|
||||
JSON: fmt.Sprintf(`{"value":"100.55","currency":"%s"}`, USDollars.Symbol()),
|
||||
BaseUnits: 10055,
|
||||
Currency: USDollars,
|
||||
},
|
||||
{
|
||||
JSON: fmt.Sprintf(`{"value":"100.5555","currency":"%s"}`, USDollarsMicro.Symbol()),
|
||||
BaseUnits: 100555500,
|
||||
Currency: USDollarsMicro,
|
||||
},
|
||||
{
|
||||
JSON: fmt.Sprintf(`{"value":"100.555555","currency":"%s"}`, USDollarsMicro.Symbol()),
|
||||
BaseUnits: 100555555,
|
||||
Currency: USDollarsMicro,
|
||||
},
|
||||
{
|
||||
JSON: fmt.Sprintf(`{"value":"10","currency":"%s"}`, LiveGoats.Symbol()),
|
||||
BaseUnits: 10,
|
||||
Currency: LiveGoats,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
var amount Amount
|
||||
|
||||
err := json.Unmarshal([]byte(test.JSON), &amount)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.BaseUnits, amount.BaseUnits())
|
||||
require.Equal(t, test.Currency, amount.Currency())
|
||||
}
|
||||
}
|
||||
|
@ -62,6 +62,7 @@ func TestChore(t *testing.T) {
|
||||
From: blockchain2.Address(accs[0].Address),
|
||||
To: blockchain2.Address(receiver),
|
||||
TokenValue: monetary.AmountFromBaseUnits(10000, monetary.StorjToken),
|
||||
USDValue: monetary.AmountFromBaseUnits(1000000, monetary.USDollars),
|
||||
Status: payments.PaymentStatusPending,
|
||||
BlockHash: blockchain2.Hash(block.Hash()),
|
||||
BlockNumber: block.Number().Int64(),
|
||||
|
Loading…
Reference in New Issue
Block a user