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
|
package monetary
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
@ -43,6 +44,9 @@ var (
|
|||||||
// USDollars is the currency of United States dollars, where fractional
|
// USDollars is the currency of United States dollars, where fractional
|
||||||
// cents are not supported.
|
// cents are not supported.
|
||||||
USDollars = NewCurrency("US dollars", "USD", 2)
|
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
|
// Bitcoin is the currency for the well-known cryptocurrency Bitcoin
|
||||||
// (a.k.a. BTC).
|
// (a.k.a. BTC).
|
||||||
Bitcoin = NewCurrency("Bitcoin (BTC)", "BTC", 8)
|
Bitcoin = NewCurrency("Bitcoin (BTC)", "BTC", 8)
|
||||||
@ -54,6 +58,24 @@ var (
|
|||||||
Error = errs.Class("monetary error")
|
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.
|
// 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
|
// 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
|
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
|
// AmountFromBaseUnits creates a new Amount instance from the given count of
|
||||||
// base units and in the given currency.
|
// base units and in the given currency.
|
||||||
func AmountFromBaseUnits(units int64, currency *Currency) Amount {
|
func AmountFromBaseUnits(units int64, currency *Currency) Amount {
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
package monetary
|
package monetary
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"testing"
|
"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),
|
From: blockchain2.Address(accs[0].Address),
|
||||||
To: blockchain2.Address(receiver),
|
To: blockchain2.Address(receiver),
|
||||||
TokenValue: monetary.AmountFromBaseUnits(10000, monetary.StorjToken),
|
TokenValue: monetary.AmountFromBaseUnits(10000, monetary.StorjToken),
|
||||||
|
USDValue: monetary.AmountFromBaseUnits(1000000, monetary.USDollars),
|
||||||
Status: payments.PaymentStatusPending,
|
Status: payments.PaymentStatusPending,
|
||||||
BlockHash: blockchain2.Hash(block.Hash()),
|
BlockHash: blockchain2.Hash(block.Hash()),
|
||||||
BlockNumber: block.Number().Int64(),
|
BlockNumber: block.Number().Int64(),
|
||||||
|
Loading…
Reference in New Issue
Block a user