flamingo.me/flamingo-commerce/v3@v3.11.0/price/domain/price_test.go (about)

     1  package domain_test
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/gob"
     6  	"encoding/json"
     7  	"fmt"
     8  	"math"
     9  	"math/big"
    10  	"testing"
    11  
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  
    15  	"flamingo.me/flamingo-commerce/v3/price/domain"
    16  )
    17  
    18  func TestPrice_IsLessThen(t *testing.T) {
    19  	type fields struct {
    20  		Amount   float64
    21  		Currency string
    22  	}
    23  	type args struct {
    24  		amount big.Float
    25  	}
    26  	tests := []struct {
    27  		name   string
    28  		fields fields
    29  		args   args
    30  		want   bool
    31  	}{
    32  		{
    33  			name: "simple is less",
    34  			fields: fields{
    35  				Amount: 11.0,
    36  			},
    37  			args: args{
    38  				amount: *big.NewFloat(12.2),
    39  			},
    40  			want: true,
    41  		},
    42  		{
    43  			name: "simple is not less",
    44  			fields: fields{
    45  				Amount: 13.0,
    46  			},
    47  			args: args{
    48  				amount: *big.NewFloat(12.2),
    49  			},
    50  			want: false,
    51  		},
    52  	}
    53  	for _, tt := range tests {
    54  		t.Run(tt.name, func(t *testing.T) {
    55  			p := domain.NewFromFloat(tt.fields.Amount, tt.fields.Currency)
    56  
    57  			if got := p.IsLessThenValue(tt.args.amount); got != tt.want {
    58  				t.Errorf("Amount.IsLessThen() = %v, want %v", got, tt.want)
    59  			}
    60  		})
    61  	}
    62  }
    63  
    64  func TestPrice_Multiply(t *testing.T) {
    65  	p := domain.NewFromFloat(2.5, "EUR")
    66  	resultPrice := p.Multiply(3)
    67  	assert.Equal(t, domain.NewFromFloat(7.5, "EUR").GetPayable().Amount(), resultPrice.GetPayable().Amount())
    68  }
    69  
    70  func TestPrice_GetPayable(t *testing.T) {
    71  	t.Parallel()
    72  
    73  	t.Run("rounding with valid currency code", func(t *testing.T) {
    74  		t.Parallel()
    75  
    76  		price := domain.NewFromFloat(12.34567, "EUR")
    77  
    78  		payable := price.GetPayable()
    79  		assert.Equal(t, float64(12.35), payable.FloatAmount())
    80  
    81  		price = domain.NewFromFloat(math.MaxInt64, "EUR").GetPayable()
    82  		assert.Equal(t, float64(math.MaxInt64), price.FloatAmount())
    83  	})
    84  
    85  	t.Run("rounding with miles currency code for back compatibility", func(t *testing.T) {
    86  		t.Parallel()
    87  
    88  		price := domain.NewFromFloat(12.34567, "MILES")
    89  
    90  		payable := price.GetPayable()
    91  		assert.Equal(t, float64(12), payable.FloatAmount())
    92  
    93  		price = domain.NewFromFloat(math.MaxInt64, "MILES").GetPayable()
    94  		assert.Equal(t, float64(math.MaxInt64), price.FloatAmount())
    95  	})
    96  
    97  	t.Run("rounding with points currency code for back compatibility", func(t *testing.T) {
    98  		t.Parallel()
    99  
   100  		price := domain.NewFromFloat(12.34567, "Points")
   101  
   102  		payable := price.GetPayable()
   103  		assert.Equal(t, float64(12), payable.FloatAmount())
   104  
   105  		price = domain.NewFromFloat(math.MaxInt64, "Points").GetPayable()
   106  		assert.Equal(t, float64(math.MaxInt64), price.FloatAmount())
   107  	})
   108  
   109  	t.Run("rounding with random currency code", func(t *testing.T) {
   110  		t.Parallel()
   111  
   112  		price := domain.NewFromFloat(12.34567, "asdasdasd")
   113  
   114  		payable := price.GetPayable()
   115  		assert.Equal(t, float64(12), payable.FloatAmount())
   116  
   117  		price = domain.NewFromFloat(math.MaxInt64, "asdasdasd").GetPayable()
   118  		assert.Equal(t, float64(math.MaxInt64), price.FloatAmount())
   119  	})
   120  
   121  	t.Run("rounding with Bahraini dinar currency code", func(t *testing.T) {
   122  		t.Parallel()
   123  
   124  		price := domain.NewFromFloat(12.34567, "BHD")
   125  
   126  		payable := price.GetPayable()
   127  		assert.Equal(t, 12.346, payable.FloatAmount())
   128  
   129  		price = domain.NewFromFloat(math.MaxInt64, "BHD").GetPayable()
   130  		assert.Equal(t, float64(math.MaxInt64), price.FloatAmount())
   131  	})
   132  
   133  	t.Run("rounding with Chilean Unit of Account currency code", func(t *testing.T) {
   134  		t.Parallel()
   135  
   136  		price := domain.NewFromFloat(12.34567, "CLF")
   137  
   138  		payable := price.GetPayable()
   139  		assert.Equal(t, 12.3457, payable.FloatAmount())
   140  
   141  		price = domain.NewFromFloat(math.MaxInt64, "CLF").GetPayable()
   142  		assert.Equal(t, float64(math.MaxInt64), price.FloatAmount())
   143  	})
   144  }
   145  
   146  func TestNewFromInt(t *testing.T) {
   147  	price1 := domain.NewFromInt(1245, 100, "EUR")
   148  	price2 := domain.NewFromFloat(12.45, "EUR")
   149  	assert.Equal(t, price2.GetPayable().Amount(), price1.GetPayable().Amount())
   150  	pricePayable := price1.GetPayable()
   151  	assert.True(t, price2.GetPayable().Equal(pricePayable))
   152  }
   153  
   154  func TestPrice_SplitInPayables(t *testing.T) {
   155  	originalPrice := domain.NewFromFloat(32.1, "EUR") // float edge case
   156  	payableSplitPrices, _ := originalPrice.SplitInPayables(1)
   157  
   158  	sumPrice := domain.NewZero("EUR")
   159  	for _, price := range payableSplitPrices {
   160  		sumPrice, _ = sumPrice.Add(price)
   161  	}
   162  	// sum of the splitted payable need to match original price payable
   163  	assert.Equal(t, originalPrice.GetPayable().Amount(), sumPrice.GetPayable().Amount())
   164  
   165  	originalPrice = domain.NewFromFloat(12.456, "EUR")
   166  	payableSplitPrices, _ = originalPrice.SplitInPayables(6)
   167  
   168  	sumPrice = domain.NewZero("EUR")
   169  	for _, price := range payableSplitPrices {
   170  		sumPrice, _ = sumPrice.Add(price)
   171  	}
   172  	// sum of the splitted payable need to match original price payable
   173  	assert.Equal(t, originalPrice.GetPayable().Amount(), sumPrice.GetPayable().Amount())
   174  
   175  	// edge case for negative input (happens when discounts are split)
   176  	originalPrice = domain.NewFromFloat(-152.99, "EUR")
   177  	payableSplitPrices, _ = originalPrice.SplitInPayables(3)
   178  
   179  	sumPrice = domain.NewZero("EUR")
   180  	for _, price := range payableSplitPrices {
   181  		sumPrice, _ = sumPrice.Add(price)
   182  	}
   183  	assert.Equal(t, originalPrice.GetPayable().Amount(), sumPrice.GetPayable().Amount())
   184  }
   185  
   186  func TestPrice_Discounted(t *testing.T) {
   187  	originalPrice := domain.NewFromFloat(12.45, "EUR")
   188  	discountedPrice := originalPrice.Discounted(10).GetPayable()
   189  	// 10% of - expected rounded value of 11.21
   190  	assert.Equal(t, domain.NewFromInt(1121, 100, "").Amount(), discountedPrice.Amount())
   191  }
   192  
   193  func TestPrice_IsZero(t *testing.T) {
   194  	var price domain.Price
   195  	assert.Equal(t, domain.NewZero("").Amount(), price.GetPayable().Amount())
   196  }
   197  
   198  func TestSumAll(t *testing.T) {
   199  	price1 := domain.NewFromInt(1200, 100, "EUR")
   200  	price2 := domain.NewFromInt(1200, 100, "EUR")
   201  	price3 := domain.NewFromInt(1200, 100, "EUR")
   202  
   203  	result, err := domain.SumAll(price1, price2, price3)
   204  	assert.NoError(t, err)
   205  	assert.Equal(t, result, domain.NewFromInt(3600, 100, "EUR"))
   206  
   207  }
   208  
   209  func TestPrice_TaxFromGross(t *testing.T) {
   210  	// 119 EUR
   211  	price := domain.NewFromInt(119, 1, "EUR")
   212  	tax := price.TaxFromGross(*new(big.Float).SetInt64(19))
   213  	assert.Equal(t, tax, domain.NewFromInt(19, 1, "EUR"))
   214  }
   215  
   216  func TestPrice_TaxFromNet(t *testing.T) {
   217  	// 100 EUR
   218  	price := domain.NewFromInt(100, 1, "EUR")
   219  	tax := price.TaxFromNet(*new(big.Float).SetInt64(19))
   220  	assert.Equal(t, tax, domain.NewFromInt(19, 1, "EUR"), "expect 19 EUR tax fromm 100EUR")
   221  
   222  	taxedPrice := price.Taxed(*new(big.Float).SetInt64(19))
   223  	assert.Equal(t, taxedPrice, domain.NewFromInt(119, 1, "EUR"))
   224  }
   225  
   226  func TestPrice_LikelyEqual(t *testing.T) {
   227  	price1 := domain.NewFromFloat(100, "EUR")
   228  	price2 := domain.NewFromFloat(100.000000000000001, "EUR")
   229  	price3 := domain.NewFromFloat(100.1, "EUR")
   230  	assert.True(t, price1.LikelyEqual(price2))
   231  	assert.False(t, price1.LikelyEqual(price3))
   232  }
   233  
   234  func TestPrice_MarshalBinaryForGob(t *testing.T) {
   235  	type (
   236  		SomeTypeWithPrice struct {
   237  			Price domain.Price
   238  		}
   239  	)
   240  	gob.Register(SomeTypeWithPrice{})
   241  	var network bytes.Buffer
   242  	enc := gob.NewEncoder(&network) // Will write to network.
   243  	dec := gob.NewDecoder(&network) // Will read from network.
   244  
   245  	err := enc.Encode(&SomeTypeWithPrice{Price: domain.NewFromInt(1111, 100, "EUR")})
   246  	if err != nil {
   247  		t.Fatal("encode error:", err)
   248  	}
   249  	var receivedPrice SomeTypeWithPrice
   250  	err = dec.Decode(&receivedPrice)
   251  	if err != nil {
   252  		t.Fatal("decode error 1:", err)
   253  	}
   254  	float, _ := receivedPrice.Price.Amount().Float64()
   255  	assert.Equal(t, 11.11, float)
   256  }
   257  
   258  func TestPrice_GetPayableByRoundingMode(t *testing.T) {
   259  	price := domain.NewFromFloat(12.34567, "EUR")
   260  
   261  	payable := price.GetPayableByRoundingMode(domain.RoundingModeCeil, 100)
   262  	assert.Equal(t, domain.NewFromInt(1235, 100, "EUR").Amount(), payable.Amount())
   263  
   264  	payable = price.GetPayableByRoundingMode(domain.RoundingModeFloor, 100)
   265  	assert.Equal(t, domain.NewFromInt(1234, 100, "EUR").Amount(), payable.Amount())
   266  
   267  	payable = price.GetPayableByRoundingMode(domain.RoundingModeFloor, 1)
   268  	assert.Equal(t, domain.NewFromInt(12, 1, "EUR").Amount(), payable.Amount())
   269  
   270  	price = domain.NewFromFloat(-0.119, "EUR")
   271  	payable = price.GetPayableByRoundingMode(domain.RoundingModeFloor, 100)
   272  	assert.Equal(t, domain.NewFromInt(-12, 100, "EUR").Amount(), payable.Amount())
   273  }
   274  
   275  func TestPrice_GetPayableByRoundingMode_RoundingModeCeil(t *testing.T) {
   276  	tests := []struct {
   277  		price     float64
   278  		precision int
   279  		expected  int64
   280  	}{
   281  		{price: 12.34567, precision: 100, expected: 1235},
   282  		{price: -12.34567, precision: 100, expected: -1234},
   283  		{price: 5.5, precision: 1, expected: 6},
   284  		{price: 2.5, precision: 1, expected: 3},
   285  		{price: 1.6, precision: 1, expected: 2},
   286  		{price: 1.1, precision: 1, expected: 2},
   287  		{price: 1.0, precision: 1, expected: 1},
   288  		{price: -1.0, precision: 1, expected: -1},
   289  		{price: -1.1, precision: 1, expected: -1},
   290  		{price: -1.6, precision: 1, expected: -1},
   291  		{price: -2.5, precision: 1, expected: -2},
   292  		{price: -5.5, precision: 1, expected: -5},
   293  	}
   294  
   295  	for _, tt := range tests {
   296  		t.Run(fmt.Sprintf("rounding %f", tt.price), func(t *testing.T) {
   297  			price := domain.NewFromFloat(tt.price, "EUR")
   298  
   299  			payable := price.GetPayableByRoundingMode(domain.RoundingModeCeil, tt.precision)
   300  			assert.Equal(t, domain.NewFromInt(tt.expected, tt.precision, "EUR").Amount(), payable.Amount())
   301  		})
   302  	}
   303  }
   304  
   305  func TestPrice_GetPayableByRoundingMode_RoundingModeFloor(t *testing.T) {
   306  	tests := []struct {
   307  		price     float64
   308  		precision int
   309  		expected  int64
   310  	}{
   311  		{price: 12.34567, precision: 100, expected: 1234},
   312  		{price: -12.34567, precision: 100, expected: -1235},
   313  		{price: 5.5, precision: 1, expected: 5},
   314  		{price: 2.5, precision: 1, expected: 2},
   315  		{price: 1.6, precision: 1, expected: 1},
   316  		{price: 1.1, precision: 1, expected: 1},
   317  		{price: 1.0, precision: 1, expected: 1},
   318  		{price: -1.0, precision: 1, expected: -1},
   319  		{price: -1.1, precision: 1, expected: -2},
   320  		{price: -1.6, precision: 1, expected: -2},
   321  		{price: -2.5, precision: 1, expected: -3},
   322  		{price: -5.5, precision: 1, expected: -6},
   323  	}
   324  
   325  	for _, tt := range tests {
   326  		t.Run(fmt.Sprintf("rounding %f", tt.price), func(t *testing.T) {
   327  			price := domain.NewFromFloat(tt.price, "EUR")
   328  
   329  			payable := price.GetPayableByRoundingMode(domain.RoundingModeFloor, tt.precision)
   330  			assert.Equal(t, domain.NewFromInt(tt.expected, tt.precision, "EUR").Amount(), payable.Amount())
   331  		})
   332  	}
   333  }
   334  
   335  func TestPrice_GetPayableByRoundingMode_RoundingModeHalfUp(t *testing.T) {
   336  	tests := []struct {
   337  		price     float64
   338  		precision int
   339  		expected  int64
   340  		msg       string
   341  	}{
   342  		{price: 7.6, precision: 1, expected: 8, msg: "7.6 should be rounded to 8"},
   343  		{price: 7.5, precision: 1, expected: 8, msg: "7.5 should be rounded to 8"},
   344  		{price: 7.4, precision: 1, expected: 7, msg: "7.4 should be rounded to 7"},
   345  		{price: 12.34567, precision: 100, expected: 1235, msg: "12.34567 should be rounded to 12.35"},
   346  		{price: -7.4, precision: 1, expected: -7, msg: "-7.4 should be rounded to -7"},
   347  		{price: -7.45, precision: 1, expected: -7, msg: "-7.45 should be rounded to -7"},
   348  		{price: -7.5, precision: 1, expected: -8, msg: "-7.5 should be rounded to -8"},
   349  		{price: -7.55, precision: 1, expected: -8, msg: "-7.55 should be rounded to -8"},
   350  		{price: -7.6, precision: 1, expected: -8, msg: "-7.6 should be rounded to -8"},
   351  
   352  		{price: 5.5, precision: 1, expected: 6, msg: "5.5 should be rounded to 6"},
   353  		{price: 2.5, precision: 1, expected: 3, msg: "2.5 should be rounded to 3"},
   354  		{price: 1.6, precision: 1, expected: 2, msg: "1.6 should be rounded to 2"},
   355  		{price: 1.1, precision: 1, expected: 1, msg: "1.1 should be rounded to 1"},
   356  		{price: 1.0, precision: 1, expected: 1, msg: "1.0 should be rounded to 1"},
   357  		{price: -1.0, precision: 1, expected: -1, msg: "-1.0 should be rounded to -1"},
   358  		{price: -1.1, precision: 1, expected: -1, msg: "-1.1 should be rounded to -1"},
   359  		{price: -1.6, precision: 1, expected: -2, msg: "-1.6 should be rounded to -2"},
   360  		{price: -2.5, precision: 1, expected: -3, msg: "-2.5 should be rounded to -3"},
   361  		{price: -5.5, precision: 1, expected: -6, msg: "-5.5 should be rounded to -6"},
   362  	}
   363  
   364  	for _, tt := range tests {
   365  		t.Run(fmt.Sprintf("rounding %f", tt.price), func(t *testing.T) {
   366  			price := domain.NewFromFloat(tt.price, "EUR")
   367  
   368  			payable := price.GetPayableByRoundingMode(domain.RoundingModeHalfUp, tt.precision)
   369  			assert.Equal(t, domain.NewFromInt(tt.expected, tt.precision, "EUR").Amount(), payable.Amount(), tt.msg)
   370  		})
   371  	}
   372  }
   373  
   374  func TestPrice_GetPayableByRoundingMode_RoundingModeHalfDown(t *testing.T) {
   375  	tests := []struct {
   376  		price     float64
   377  		precision int
   378  		expected  int64
   379  		msg       string
   380  	}{
   381  		{price: 7.6, precision: 1, expected: 8, msg: "7.6 should be rounded to 8"},
   382  		{price: 7.5, precision: 1, expected: 7, msg: "7.5 should be rounded to 7"},
   383  		{price: 7.4, precision: 1, expected: 7, msg: "7.4 should be rounded to 7"},
   384  		{price: 12.34567, precision: 100, expected: 1235, msg: "12.34567 should be rounded to 12.35"},
   385  
   386  		{price: -7.4, precision: 1, expected: -7, msg: "-7.4 should be rounded to -7"},
   387  		{price: -7.5, precision: 1, expected: -7, msg: "-7.5 should be rounded to -7"},
   388  		{price: -7.6, precision: 1, expected: -8, msg: "-7.6 should be rounded to -8"},
   389  
   390  		{price: 5.5, precision: 1, expected: 5, msg: "5.5 should be rounded to 5"},
   391  		{price: 2.5, precision: 1, expected: 2, msg: "2.5 should be rounded to 2"},
   392  		{price: 1.6, precision: 1, expected: 2, msg: "1.6 should be rounded to 2"},
   393  		{price: 1.1, precision: 1, expected: 1, msg: "1.1 should be rounded to 1"},
   394  		{price: 1.0, precision: 1, expected: 1, msg: "1.0 should be rounded to 1"},
   395  		{price: -1.0, precision: 1, expected: -1, msg: "-1.0 should be rounded to -1"},
   396  		{price: -1.1, precision: 1, expected: -1, msg: "-1.1 should be rounded to -1"},
   397  		{price: -1.6, precision: 1, expected: -2, msg: "-1.6 should be rounded to -2"},
   398  		{price: -2.5, precision: 1, expected: -2, msg: "-2.5 should be rounded to -2"},
   399  		{price: -5.5, precision: 1, expected: -5, msg: "-5.5 should be rounded to -5"},
   400  	}
   401  	for _, tt := range tests {
   402  		t.Run(fmt.Sprintf("rounding %f", tt.price), func(t *testing.T) {
   403  			price := domain.NewFromFloat(tt.price, "EUR")
   404  
   405  			payable := price.GetPayableByRoundingMode(domain.RoundingModeHalfDown, tt.precision)
   406  			assert.Equal(t, domain.NewFromInt(tt.expected, tt.precision, "EUR").Amount(), payable.Amount(), tt.msg)
   407  		})
   408  	}
   409  
   410  }
   411  
   412  func TestCharges_Add(t *testing.T) {
   413  	c1 := domain.Charges{}
   414  
   415  	byType := make(map[string]domain.Charge)
   416  	byType["main"] = domain.Charge{
   417  		Type:  "main",
   418  		Price: domain.NewFromInt(100, 1, "EUR"),
   419  		Value: domain.NewFromInt(50, 1, "EUR"),
   420  	}
   421  	c2 := domain.NewCharges(byType)
   422  
   423  	byType = make(map[string]domain.Charge)
   424  	byType["main"] = domain.Charge{
   425  		Type:  "main",
   426  		Price: domain.NewFromInt(100, 1, "EUR"),
   427  		Value: domain.NewFromInt(100, 1, "EUR"),
   428  	}
   429  	c3 := domain.NewCharges(byType)
   430  
   431  	c1and2 := c1.Add(*c2)
   432  	charge, found := c1and2.GetByType("main")
   433  	assert.True(t, found)
   434  	assert.Equal(t, domain.Charge{
   435  		Price: domain.NewFromInt(100, 1, "EUR"),
   436  		Value: domain.NewFromInt(50, 1, "EUR"),
   437  		Type:  "main",
   438  	}, charge)
   439  
   440  	c2and3 := c2.Add(*c3)
   441  	charge, found = c2and3.GetByType("main")
   442  	assert.True(t, found)
   443  	assert.Equal(t, domain.Charge{
   444  		Price: domain.NewFromInt(200, 1, "EUR"),
   445  		Value: domain.NewFromInt(150, 1, "EUR"),
   446  		Type:  "main",
   447  	}, charge)
   448  }
   449  
   450  func TestCharges_GetAllByType(t *testing.T) {
   451  	charges := domain.Charges{}
   452  	charges = charges.AddCharge(domain.Charge{Type: domain.ChargeTypeMain, Reference: "SJHHQWAXX6HJSDZ82", Price: domain.NewFromInt(200, 1, "EUR")})
   453  	charges = charges.AddCharge(domain.Charge{Type: "type-a", Price: domain.NewFromInt(200, 1, "EUR")})
   454  	charges = charges.AddCharge(domain.Charge{Type: "type-x", Price: domain.NewFromInt(200, 1, "EUR")})
   455  	charges = charges.AddCharge(domain.Charge{Type: domain.ChargeTypeMain, Price: domain.NewFromInt(200, 1, "EUR")})
   456  	charges = charges.AddCharge(domain.Charge{Type: "type-c", Reference: "HUHUWHHUHX", Price: domain.NewFromInt(200, 1, "EUR")})
   457  	charges = charges.AddCharge(domain.Charge{Type: "type-a", Price: domain.NewFromInt(200, 1, "EUR")})
   458  	charges = charges.AddCharge(domain.Charge{Type: domain.ChargeTypeMain, Reference: "ABC123", Price: domain.NewFromInt(200, 1, "EUR")})
   459  
   460  	assert.Len(t, charges.GetAllByType(domain.ChargeTypeMain), 3)
   461  	assert.Len(t, charges.GetAllByType("type-a"), 1)
   462  	assert.Len(t, charges.GetAllByType("type-c"), 1)
   463  	assert.Len(t, charges.GetAllByType("type-x"), 1)
   464  }
   465  
   466  func TestCharges_GetByType(t *testing.T) {
   467  	charges := domain.Charges{}
   468  	charges = charges.AddCharge(domain.Charge{Type: domain.ChargeTypeMain, Reference: "SJHHQWAXX6HJSDZ82", Price: domain.NewFromInt(200, 1, "EUR")})
   469  	charges = charges.AddCharge(domain.Charge{Type: "type-a", Price: domain.NewFromInt(200, 1, "EUR")})
   470  	charges = charges.AddCharge(domain.Charge{Type: "type-x", Price: domain.NewFromInt(200, 1, "EUR")})
   471  	charges = charges.AddCharge(domain.Charge{Type: domain.ChargeTypeMain, Price: domain.NewFromInt(200, 1, "EUR")})
   472  	charges = charges.AddCharge(domain.Charge{Type: "type-c", Reference: "HUHUWHHUHX", Price: domain.NewFromInt(200, 1, "EUR")})
   473  	charges = charges.AddCharge(domain.Charge{Type: "type-a", Price: domain.NewFromInt(200, 1, "EUR")})
   474  	charges = charges.AddCharge(domain.Charge{Type: domain.ChargeTypeMain, Reference: "ABC123", Price: domain.NewFromInt(200, 1, "EUR")})
   475  
   476  	charge, found := charges.GetByType(domain.ChargeTypeMain)
   477  	assert.True(t, found)
   478  	assert.Equal(t, charge, domain.Charge{Type: domain.ChargeTypeMain, Price: domain.NewFromInt(600, 1, "EUR")})
   479  
   480  	charge, found = charges.GetByType("type-a")
   481  	assert.True(t, found)
   482  
   483  	want := domain.Charge{Type: "type-a", Price: domain.NewFromInt(400, 1, "EUR")}
   484  	assert.Equal(t, charge.Price, want.Price)
   485  }
   486  
   487  func TestCharges_GetByTypeForced(t *testing.T) {
   488  	charges := domain.Charges{}
   489  
   490  	charge := charges.GetByTypeForced(domain.ChargeTypeMain)
   491  	assert.Equal(t, charge, domain.Charge{})
   492  
   493  	charges = charges.AddCharge(domain.Charge{Type: domain.ChargeTypeMain, Reference: "SJHHQWAXX6HJSDZ82", Price: domain.NewFromInt(200, 1, "EUR")})
   494  	charge = charges.GetByTypeForced(domain.ChargeTypeMain)
   495  
   496  	assert.Equal(t, charge, domain.Charge{Type: domain.ChargeTypeMain, Price: domain.NewFromInt(200, 1, "EUR")})
   497  }
   498  
   499  func TestCharges_GetByChargeQualifier(t *testing.T) {
   500  	charges := domain.Charges{}
   501  	charges = charges.AddCharge(domain.Charge{Type: domain.ChargeTypeMain, Reference: "SJHHQWAXX6HJSDZ82", Price: domain.NewFromInt(200, 1, "EUR")})
   502  	charges = charges.AddCharge(domain.Charge{Type: domain.ChargeTypeMain, Price: domain.NewFromInt(200, 1, "EUR")})
   503  	charges = charges.AddCharge(domain.Charge{Type: domain.ChargeTypeMain, Reference: "ABC123", Price: domain.NewFromInt(200, 1, "EUR")})
   504  
   505  	charge, found := charges.GetByChargeQualifier(domain.ChargeQualifier{Type: domain.ChargeTypeMain, Reference: "SJHHQWAXX6HJSDZ82"})
   506  	assert.True(t, found)
   507  	assert.Equal(t, charge, domain.Charge{Type: domain.ChargeTypeMain, Reference: "SJHHQWAXX6HJSDZ82", Price: domain.NewFromInt(200, 1, "EUR")})
   508  
   509  }
   510  
   511  func TestCharges_GetByChargeQualifierForced(t *testing.T) {
   512  	charges := domain.Charges{}
   513  	charge := charges.GetByChargeQualifierForced(domain.ChargeQualifier{Type: domain.ChargeTypeMain, Reference: "SJHHQWAXX6HJSDZ82"})
   514  	assert.Equal(t, charge, domain.Charge{})
   515  
   516  	charges = charges.AddCharge(domain.Charge{Type: domain.ChargeTypeMain, Reference: "SJHHQWAXX6HJSDZ82", Price: domain.NewFromInt(200, 1, "EUR")})
   517  	charge = charges.GetByChargeQualifierForced(domain.ChargeQualifier{Type: domain.ChargeTypeMain, Reference: "SJHHQWAXX6HJSDZ82"})
   518  	assert.Equal(t, charge, domain.Charge{Type: domain.ChargeTypeMain, Reference: "SJHHQWAXX6HJSDZ82", Price: domain.NewFromInt(200, 1, "EUR")})
   519  
   520  }
   521  
   522  func TestPrice_MarshalJSON(t *testing.T) {
   523  	t.Run("JSON marshalling works", func(t *testing.T) {
   524  		price := domain.NewFromFloat(55.111111, "USD")
   525  
   526  		priceJSON, err := price.MarshalJSON()
   527  		require.NoError(t, err)
   528  		assert.Equal(t, `{"Amount":"55.11","Currency":"USD"}`, string(priceJSON))
   529  	})
   530  
   531  	t.Run("JSON price is rounded", func(t *testing.T) {
   532  		price := domain.NewFromFloat(55.119, "USD")
   533  
   534  		priceJSON, err := price.MarshalJSON()
   535  		require.NoError(t, err)
   536  		assert.Equal(t, `{"Amount":"55.12","Currency":"USD"}`, string(priceJSON))
   537  	})
   538  }
   539  
   540  func TestPrice_UnmarshalJSON(t *testing.T) {
   541  	var p domain.Price
   542  
   543  	err := json.Unmarshal([]byte(`{"Amount":"55.12","Currency":"USD"}`), &p)
   544  	require.NoError(t, err)
   545  	assert.Equal(t, domain.NewFromFloat(55.12, "USD").GetPayable(), p.GetPayable())
   546  }