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

     1  package domain
     2  
     3  import (
     4  	"encoding"
     5  	"encoding/json"
     6  	"errors"
     7  	"math"
     8  	"math/big"
     9  	"strconv"
    10  
    11  	"github.com/Rhymond/go-money"
    12  )
    13  
    14  type (
    15  	// Price is a Type that represents a Amount - it is immutable
    16  	// DevHint: We use Amount and Charge as Value - so we do not pass pointers. (According to Go Wiki's code review comments page suggests passing by value when structs are small and likely to stay that way)
    17  	Price struct {
    18  		amount   big.Float `swaggertype:"string"`
    19  		currency string
    20  	}
    21  
    22  	// Charge is a Amount of a certain Type. Charge is used as value object
    23  	Charge struct {
    24  		// Price that is paid, can be in a certain currency
    25  		Price Price
    26  		// Value of the "Price" in another (base) currency
    27  		Value Price
    28  		// Type of the charge - can be ChargeTypeMain or something else. Used to differentiate between different charges of a single thing
    29  		Type string
    30  		// Reference contains further information to distinguish charges of the same type
    31  		Reference string
    32  	}
    33  
    34  	// Charges - Represents the Charges the product need to be paid with
    35  	Charges struct {
    36  		chargesByQualifier map[ChargeQualifier]Charge
    37  	}
    38  
    39  	// ChargeQualifier distinguishes charges by type and reference
    40  	ChargeQualifier struct {
    41  		// Type represents charge type
    42  		Type string
    43  		// Reference contains further information to distinguish charges of the same type
    44  		Reference string
    45  	}
    46  
    47  	// priceEncodeAble is a type that we need to allow marshalling the price values. The type itself is unexported
    48  	priceEncodeAble struct {
    49  		Amount   big.Float
    50  		Currency string
    51  	}
    52  )
    53  
    54  var (
    55  	_ encoding.BinaryMarshaler   = Price{}
    56  	_ encoding.BinaryUnmarshaler = &Price{}
    57  )
    58  
    59  const (
    60  	// ChargeTypeGiftCard  used as a charge type for gift cards
    61  	ChargeTypeGiftCard = "giftcard"
    62  	// ChargeTypeMain used as default for a Charge
    63  	ChargeTypeMain = "main"
    64  
    65  	// RoundingModeFloor use if you want to cut (round down)
    66  	RoundingModeFloor = "floor"
    67  	// RoundingModeCeil use if you want to round up always
    68  	RoundingModeCeil = "ceil"
    69  	// RoundingModeHalfUp round up if the discarded fraction is ≥ 0.5, otherwise round down. Default for GetPayable()
    70  	RoundingModeHalfUp = "halfup"
    71  	// RoundingModeHalfDown round up if the discarded fraction is > 0.5, otherwise round down.
    72  	RoundingModeHalfDown = "halfdown"
    73  )
    74  
    75  // NewFromFloat - factory method
    76  func NewFromFloat(amount float64, currency string) Price {
    77  	return Price{
    78  		amount:   *big.NewFloat(amount),
    79  		currency: currency,
    80  	}
    81  }
    82  
    83  // NewFromBigFloat - factory method
    84  func NewFromBigFloat(amount big.Float, currency string) Price {
    85  	return Price{
    86  		amount:   amount,
    87  		currency: currency,
    88  	}
    89  }
    90  
    91  // NewZero Zero price
    92  func NewZero(currency string) Price {
    93  	return Price{
    94  		amount:   *new(big.Float).SetInt64(0),
    95  		currency: currency,
    96  	}
    97  }
    98  
    99  // NewFromInt use to set money by smallest payable unit - e.g. to set 2.45 EUR you should use NewFromInt(245, 100, "EUR")
   100  func NewFromInt(amount int64, precision int, currency string) Price {
   101  	amountF := new(big.Float).SetInt64(amount)
   102  	if precision == 0 {
   103  		return Price{
   104  			amount:   *new(big.Float).SetInt64(0),
   105  			currency: currency,
   106  		}
   107  	}
   108  	precicionF := new(big.Float).SetInt64(int64(precision))
   109  	return Price{
   110  		amount:   *new(big.Float).Quo(amountF, precicionF),
   111  		currency: currency,
   112  	}
   113  }
   114  
   115  // Add the given price to the current price and returns a new price
   116  func (p Price) Add(add Price) (Price, error) {
   117  	newPrice, err := p.currencyGuard(add)
   118  	if err != nil {
   119  		return newPrice, err
   120  	}
   121  	newPrice.amount.Add(&p.amount, &add.amount)
   122  	return newPrice, nil
   123  }
   124  
   125  // ForceAdd tries to add the given price to the current price - will not return errors
   126  func (p Price) ForceAdd(add Price) Price {
   127  	newPrice, err := p.currencyGuard(add)
   128  	if err != nil {
   129  		return p
   130  	}
   131  	newPrice.amount.Add(&p.amount, &add.amount)
   132  	return newPrice
   133  }
   134  
   135  // currencyGuard is a common Guard that protects price calculations of prices with different currency.
   136  // Robust: if original is Zero and the currencies are different we take the given currency
   137  func (p Price) currencyGuard(check Price) (Price, error) {
   138  	if p.currency == check.currency {
   139  		return Price{
   140  			currency: check.currency,
   141  		}, nil
   142  	}
   143  	if p.IsZero() {
   144  		return Price{
   145  			currency: check.currency,
   146  		}, nil
   147  	}
   148  
   149  	if check.IsZero() {
   150  		return Price{
   151  			currency: p.currency,
   152  		}, nil
   153  	}
   154  	return NewZero(p.currency), errors.New("cannot calculate prices in different currencies")
   155  }
   156  
   157  // Discounted returns new price reduced by given percent
   158  func (p Price) Discounted(percent float64) Price {
   159  	newPrice := Price{
   160  		currency: p.currency,
   161  		amount:   *new(big.Float).Mul(&p.amount, big.NewFloat((100-percent)/100)),
   162  	}
   163  	return newPrice
   164  }
   165  
   166  // Taxed returns new price added with Tax (assuming current price is net)
   167  func (p Price) Taxed(percent big.Float) Price {
   168  	newPrice := Price{
   169  		currency: p.currency,
   170  		amount:   *new(big.Float).Add(&p.amount, p.TaxFromNet(percent).Amount()),
   171  	}
   172  	return newPrice
   173  }
   174  
   175  // TaxFromNet returns new price representing the tax amount (assuming the current price is net 100%)
   176  func (p Price) TaxFromNet(percent big.Float) Price {
   177  	quo := new(big.Float).Mul(&percent, &p.amount)
   178  	newPrice := Price{
   179  		currency: p.currency,
   180  		amount:   *new(big.Float).Quo(quo, new(big.Float).SetInt64(100)),
   181  	}
   182  	return newPrice
   183  }
   184  
   185  // TaxFromGross returns new price representing the tax amount (assuming the current price is gross 100+percent)
   186  func (p Price) TaxFromGross(percent big.Float) Price {
   187  	quo := new(big.Float).Mul(&percent, &p.amount)
   188  	percent100 := new(big.Float).Add(&percent, new(big.Float).SetInt64(100))
   189  	newPrice := Price{
   190  		currency: p.currency,
   191  		amount:   *new(big.Float).Quo(quo, percent100),
   192  	}
   193  	return newPrice
   194  }
   195  
   196  // Sub the given price from the current price and returns a new price
   197  func (p Price) Sub(sub Price) (Price, error) {
   198  	newPrice, err := p.currencyGuard(sub)
   199  	if err != nil {
   200  		return newPrice, err
   201  	}
   202  	newPrice.amount.Sub(&p.amount, &sub.amount)
   203  	return newPrice, nil
   204  }
   205  
   206  // Inverse returns the price multiplied with -1
   207  func (p Price) Inverse() Price {
   208  	p.amount = *new(big.Float).Mul(&p.amount, big.NewFloat(-1))
   209  	return p
   210  }
   211  
   212  // Multiply returns a new price with the amount Multiply
   213  func (p Price) Multiply(qty int) Price {
   214  	newPrice := Price{
   215  		currency: p.currency,
   216  	}
   217  	newPrice.amount.Mul(&p.amount, new(big.Float).SetInt64(int64(qty)))
   218  	return newPrice
   219  }
   220  
   221  // Divided returns a new price with the amount Divided
   222  func (p Price) Divided(qty int) Price {
   223  	newPrice := Price{
   224  		currency: p.currency,
   225  	}
   226  	if qty == 0 {
   227  		return NewZero(p.currency)
   228  	}
   229  	newPrice.amount.Quo(&p.amount, new(big.Float).SetInt64(int64(qty)))
   230  	return newPrice
   231  }
   232  
   233  // Equal compares the prices exact
   234  func (p Price) Equal(cmp Price) bool {
   235  	if p.currency != cmp.currency {
   236  		return false
   237  	}
   238  	return p.amount.Cmp(&cmp.amount) == 0
   239  }
   240  
   241  // LikelyEqual compares the prices with some tolerance
   242  func (p Price) LikelyEqual(cmp Price) bool {
   243  	if p.currency != cmp.currency {
   244  		return false
   245  	}
   246  	diff := new(big.Float).Sub(&p.amount, &cmp.amount)
   247  	absDiff := new(big.Float).Abs(diff)
   248  	return absDiff.Cmp(big.NewFloat(0.000000001)) == -1
   249  }
   250  
   251  // IsLessThen compares the current price with a given one
   252  func (p Price) IsLessThen(cmp Price) bool {
   253  	if p.currency != cmp.currency {
   254  		return false
   255  	}
   256  	return p.amount.Cmp(&cmp.amount) == -1
   257  }
   258  
   259  // IsGreaterThen compares the current price with a given one
   260  func (p Price) IsGreaterThen(cmp Price) bool {
   261  	if p.currency != cmp.currency {
   262  		return false
   263  	}
   264  	return p.amount.Cmp(&cmp.amount) == 1
   265  }
   266  
   267  // IsLessThenValue compares the price with a given amount value (assuming same currency)
   268  func (p Price) IsLessThenValue(amount big.Float) bool {
   269  	return p.amount.Cmp(&amount) == -1
   270  }
   271  
   272  // IsGreaterThenValue compares the price with a given amount value (assuming same currency)
   273  func (p Price) IsGreaterThenValue(amount big.Float) bool {
   274  	return p.amount.Cmp(&amount) == 1
   275  }
   276  
   277  // IsNegative returns true if the price represents a negative value
   278  func (p Price) IsNegative() bool {
   279  	return p.IsLessThenValue(*big.NewFloat(0.0))
   280  }
   281  
   282  // IsPositive returns true if the price represents a positive value
   283  func (p Price) IsPositive() bool {
   284  	return p.IsGreaterThenValue(*big.NewFloat(0.0))
   285  }
   286  
   287  // IsPayable returns true if the price represents a payable (rounded) value
   288  func (p Price) IsPayable() bool {
   289  	return p.GetPayable().Equal(p)
   290  }
   291  
   292  // IsZero returns true if the price represents zero value
   293  func (p Price) IsZero() bool {
   294  	return p.LikelyEqual(NewZero(p.Currency())) || p.LikelyEqual(NewFromFloat(0, p.Currency()))
   295  }
   296  
   297  // FloatAmount gets the current amount as float
   298  func (p Price) FloatAmount() float64 {
   299  	a, _ := p.amount.Float64()
   300  	return a
   301  }
   302  
   303  // GetPayable rounds the price with the precision required by the currency in a price that can actually be paid
   304  // e.g. an internal amount of 1,23344 will get rounded to 1,23
   305  func (p Price) GetPayable() Price {
   306  	mode, precision := p.payableRoundingPrecision()
   307  	return p.GetPayableByRoundingMode(mode, precision)
   308  }
   309  
   310  // GetPayableByRoundingMode returns the price rounded you can pass the used rounding mode and precision
   311  // Example for precision 100:
   312  //
   313  //	1.115 >  1.12 (RoundingModeHalfUp)  / 1.11 (RoundingModeFloor)
   314  //	-1.115 > -1.11 (RoundingModeHalfUp) / -1.12 (RoundingModeFloor)
   315  func (p Price) GetPayableByRoundingMode(mode string, precision int) Price {
   316  	newPrice := Price{
   317  		currency: p.currency,
   318  	}
   319  
   320  	amountForRound := new(big.Float).Copy(&p.amount)
   321  	negative := int64(1)
   322  	if p.IsNegative() {
   323  		negative = -1
   324  	}
   325  
   326  	amountTruncatedFloat, _ := new(big.Float).Mul(amountForRound, p.precisionF(precision)).Float64()
   327  	integerPart, fractionalPart := math.Modf(amountTruncatedFloat)
   328  	amountTruncatedInt := int64(integerPart)
   329  	valueAfterPrecision := (math.Round(fractionalPart*1000) / 100) * float64(negative)
   330  	if amountTruncatedFloat >= float64(math.MaxInt64) {
   331  		// will not work if we are already above MaxInt - so we return unrounded price:
   332  		newPrice.amount = p.amount
   333  		return newPrice
   334  	}
   335  
   336  	switch mode {
   337  	case RoundingModeCeil:
   338  		if negative == 1 && valueAfterPrecision > 0 {
   339  			amountTruncatedInt = amountTruncatedInt + negative
   340  		}
   341  	case RoundingModeHalfUp:
   342  		if valueAfterPrecision >= 5 {
   343  			amountTruncatedInt = amountTruncatedInt + negative
   344  		}
   345  	case RoundingModeHalfDown:
   346  		if valueAfterPrecision > 5 {
   347  			amountTruncatedInt = amountTruncatedInt + negative
   348  		}
   349  	case RoundingModeFloor:
   350  		if negative == -1 && valueAfterPrecision > 0 {
   351  			amountTruncatedInt = amountTruncatedInt + negative
   352  		}
   353  	default:
   354  		// nothing to round
   355  	}
   356  
   357  	amountRounded := new(big.Float).Quo(new(big.Float).SetInt64(amountTruncatedInt), p.precisionF(precision))
   358  	newPrice.amount = *amountRounded
   359  	return newPrice
   360  }
   361  
   362  // precisionF returns big.Float from int
   363  func (p Price) precisionF(precision int) *big.Float {
   364  	return new(big.Float).SetInt64(int64(precision))
   365  }
   366  
   367  // precisionF - 10 * n - n is the amount of decimal numbers after comma
   368  func (p Price) payableRoundingPrecision() (string, int) {
   369  	currency := money.GetCurrency(p.currency)
   370  	if currency == nil {
   371  		return RoundingModeFloor, 1
   372  	}
   373  
   374  	precision := 1
   375  	for i := 1; i <= currency.Fraction; i++ {
   376  		precision *= 10
   377  	}
   378  
   379  	return RoundingModeHalfUp, precision
   380  }
   381  
   382  // SplitInPayables returns "count" payable prices (each rounded) that in sum matches the given price
   383  //   - Given a price of 12.456 (Payable 12,46)  - Splitted in 6 will mean: 6 * 2.076
   384  //   - but having them payable requires rounding them each (e.g. 2.07) which would mean we have 0.03 difference (=12,45-6*2.07)
   385  //   - so that the sum is as close as possible to the original value   in this case the correct return will be:
   386  //   - 2.07 + 2.07+2.08 +2.08 +2.08 +2.08
   387  func (p Price) SplitInPayables(count int) ([]Price, error) {
   388  	if count <= 0 {
   389  		return nil, errors.New("split must be higher than zero")
   390  	}
   391  	// guard clause invert negative values
   392  	_, precision := p.payableRoundingPrecision()
   393  	amount := p.GetPayable().Amount()
   394  	// we have to invert negative numbers, otherwise split is not correct
   395  	if p.IsNegative() {
   396  		amount = p.GetPayable().Inverse().Amount()
   397  	}
   398  	amountToMatchFloat, _ := new(big.Float).Mul(amount, p.precisionF(precision)).Float64()
   399  	amountToMatchInt := int64(amountToMatchFloat)
   400  
   401  	splittedAmountModulo := amountToMatchInt % int64(count)
   402  	splittedAmount := amountToMatchInt / int64(count)
   403  
   404  	splittedAmounts := make([]int64, count)
   405  	for i := 0; i < count; i++ {
   406  		splittedAmounts[i] = splittedAmount
   407  	}
   408  
   409  	for i := 0; i < int(splittedAmountModulo); i++ {
   410  		splittedAmounts[i] = splittedAmounts[i] + 1
   411  	}
   412  
   413  	prices := make([]Price, count)
   414  	for i := 0; i < count; i++ {
   415  		_, precision := p.payableRoundingPrecision()
   416  		splittedAmount := splittedAmounts[i]
   417  		// invert prices again to keep negative values
   418  		if p.IsNegative() {
   419  			splittedAmount *= -1
   420  		}
   421  		prices[i] = NewFromInt(splittedAmount, precision, p.Currency())
   422  	}
   423  
   424  	return prices, nil
   425  }
   426  
   427  // Clone returns a copy of the price - the amount gets Excat acc
   428  func (p Price) Clone() Price {
   429  	return Price{
   430  		amount:   *new(big.Float).Set(&p.amount),
   431  		currency: p.currency,
   432  	}
   433  }
   434  
   435  // Currency returns currency
   436  func (p Price) Currency() string {
   437  	return p.currency
   438  }
   439  
   440  // Amount returns exact amount as bigFloat
   441  func (p Price) Amount() *big.Float {
   442  	return &p.amount
   443  }
   444  
   445  // SumAll returns new price with sum of all given prices
   446  func SumAll(prices ...Price) (Price, error) {
   447  	if len(prices) == 0 {
   448  		return NewZero(""), errors.New("no price given")
   449  	}
   450  	result := prices[0].Clone()
   451  	var err error
   452  	for _, price := range prices[1:] {
   453  		result, err = result.Add(price)
   454  		if err != nil {
   455  			return result, err
   456  		}
   457  	}
   458  	return result, nil
   459  }
   460  
   461  // MarshalJSON implements interface required by json marshal
   462  func (p Price) MarshalJSON() (data []byte, err error) {
   463  	type priceJSON struct {
   464  		Amount   string
   465  		Currency string
   466  	}
   467  
   468  	pn := priceJSON{
   469  		Amount:   strconv.FormatFloat(p.GetPayable().FloatAmount(), 'f', 2, 64),
   470  		Currency: p.currency,
   471  	}
   472  
   473  	r, e := json.Marshal(&pn)
   474  	return r, e
   475  }
   476  
   477  // MarshalBinary implements interface required by gob
   478  func (p Price) MarshalBinary() (data []byte, err error) {
   479  	return json.Marshal(p)
   480  }
   481  
   482  // UnmarshalBinary implements interface required by gob.
   483  // Modifies the receiver so it must take a pointer receiver!
   484  func (p *Price) UnmarshalBinary(data []byte) error {
   485  	var pe priceEncodeAble
   486  	err := json.Unmarshal(data, &pe)
   487  	if err != nil {
   488  		return err
   489  	}
   490  	p.amount = pe.Amount
   491  	p.currency = pe.Currency
   492  	return nil
   493  }
   494  
   495  // UnmarshalJSON implements encode Unmarshaler
   496  func (p *Price) UnmarshalJSON(data []byte) error {
   497  	return p.UnmarshalBinary(data)
   498  }
   499  
   500  // Add the given Charge to the current Charge and returns a new Charge
   501  func (p Charge) Add(add Charge) (Charge, error) {
   502  	if p.Type != add.Type {
   503  		return Charge{}, errors.New("charge type mismatch")
   504  	}
   505  	newPrice, err := p.Price.Add(add.Price)
   506  	if err != nil {
   507  		return Charge{}, err
   508  	}
   509  	p.Price = newPrice
   510  
   511  	newPrice, err = p.Value.Add(add.Value)
   512  	if err != nil {
   513  		return Charge{}, err
   514  	}
   515  	p.Value = newPrice
   516  	return p, nil
   517  }
   518  
   519  // GetPayable rounds the charge
   520  func (p Charge) GetPayable() Charge {
   521  	p.Value = p.Value.GetPayable()
   522  	p.Price = p.Price.GetPayable()
   523  	return p
   524  }
   525  
   526  // Mul the given Charge and returns a new Charge
   527  func (p Charge) Mul(qty int) Charge {
   528  	p.Price = p.Price.Multiply(qty)
   529  	p.Value = p.Value.Multiply(qty)
   530  	return p
   531  }
   532  
   533  // NewCharges creates a new Charges object
   534  func NewCharges(chargesByType map[string]Charge) *Charges {
   535  	charges := addChargeQualifier(chargesByType)
   536  	return &charges
   537  }
   538  
   539  // HasType returns a true if any charges include a charge with given type
   540  func (c Charges) HasType(ctype string) bool {
   541  	for qualifier := range c.chargesByQualifier {
   542  		if qualifier.Type == ctype {
   543  			return true
   544  		}
   545  	}
   546  	return false
   547  }
   548  
   549  // GetByType returns a charge of given type. If it was not found a Zero amount
   550  // is returned and the second return value is false
   551  // sums up charges by a certain type if there are multiple
   552  func (c Charges) GetByType(ctype string) (Charge, bool) {
   553  	// guard in case type is not available
   554  	if !c.HasType(ctype) {
   555  		return Charge{}, false
   556  	}
   557  	result := Charge{
   558  		Type: ctype,
   559  	}
   560  	// sum up all charges with certain type to one charge
   561  	for qualifier, charge := range c.chargesByQualifier {
   562  		if qualifier.Type == ctype {
   563  			result, _ = result.Add(charge)
   564  		}
   565  	}
   566  	return result, true
   567  }
   568  
   569  // HasChargeQualifier returns a true if any charges include a charge with given type
   570  // and concrete key values provided by additional
   571  func (c Charges) HasChargeQualifier(qualifier ChargeQualifier) bool {
   572  	if _, ok := c.chargesByQualifier[qualifier]; ok {
   573  		return true
   574  	}
   575  	return false
   576  }
   577  
   578  // GetByChargeQualifier returns a charge of given qualifier.
   579  // If it was not found a Zero amount is returned and the second return value is false
   580  func (c Charges) GetByChargeQualifier(qualifier ChargeQualifier) (Charge, bool) {
   581  	// guard in case type is not available
   582  	if !c.HasChargeQualifier(qualifier) {
   583  		return Charge{}, false
   584  	}
   585  
   586  	if charge, ok := c.chargesByQualifier[qualifier]; ok {
   587  		return charge, true
   588  	}
   589  	return Charge{}, false
   590  }
   591  
   592  // GetByChargeQualifierForced returns a charge of given qualifier.
   593  // If it was not found a Zero amount is returned. This method might be useful to call in View (template) directly.
   594  func (c Charges) GetByChargeQualifierForced(qualifier ChargeQualifier) Charge {
   595  	result, ok := c.GetByChargeQualifier(qualifier)
   596  	if !ok {
   597  		return Charge{}
   598  	}
   599  	return result
   600  }
   601  
   602  // GetByTypeForced returns a charge of given type. If it was not found a Zero amount is returned.
   603  // This method might be useful to call in View (template) directly where you need one return value
   604  // sums up charges by a certain type if there are multiple
   605  func (c Charges) GetByTypeForced(ctype string) Charge {
   606  	result, ok := c.GetByType(ctype)
   607  	if !ok {
   608  		return Charge{}
   609  	}
   610  	return result
   611  }
   612  
   613  // GetAllCharges returns all charges
   614  func (c Charges) GetAllCharges() map[ChargeQualifier]Charge {
   615  	return c.chargesByQualifier
   616  }
   617  
   618  // GetAllByType returns all charges of type
   619  func (c Charges) GetAllByType(ctype string) map[ChargeQualifier]Charge {
   620  	chargesByType := make(map[ChargeQualifier]Charge)
   621  
   622  	for qualifier, charge := range c.chargesByQualifier {
   623  		if qualifier.Type == ctype {
   624  			chargesByType[ChargeQualifier{
   625  				qualifier.Type,
   626  				qualifier.Reference,
   627  			}] = charge
   628  		}
   629  	}
   630  
   631  	return chargesByType
   632  }
   633  
   634  // Add returns new Charges with the given added
   635  func (c Charges) Add(toadd Charges) Charges {
   636  	if c.chargesByQualifier == nil {
   637  		c.chargesByQualifier = make(map[ChargeQualifier]Charge)
   638  	}
   639  	for addk, addCharge := range toadd.chargesByQualifier {
   640  		if existingCharge, ok := c.chargesByQualifier[addk]; ok {
   641  			chargeSum, _ := existingCharge.Add(addCharge)
   642  			c.chargesByQualifier[addk] = chargeSum.GetPayable()
   643  		} else {
   644  			c.chargesByQualifier[addk] = addCharge
   645  		}
   646  	}
   647  	return c
   648  }
   649  
   650  // AddCharge returns new Charges with the given Charge added
   651  func (c Charges) AddCharge(toadd Charge) Charges {
   652  	if c.chargesByQualifier == nil {
   653  		c.chargesByQualifier = make(map[ChargeQualifier]Charge)
   654  	}
   655  	qualifier := ChargeQualifier{
   656  		Type:      toadd.Type,
   657  		Reference: toadd.Reference,
   658  	}
   659  	if existingCharge, ok := c.chargesByQualifier[qualifier]; ok {
   660  		chargeSum, _ := existingCharge.Add(toadd)
   661  		c.chargesByQualifier[qualifier] = chargeSum.GetPayable()
   662  	} else {
   663  		c.chargesByQualifier[qualifier] = toadd
   664  	}
   665  
   666  	return c
   667  }
   668  
   669  // Mul returns new Charges with the given multiplied
   670  func (c Charges) Mul(qty int) Charges {
   671  	if c.chargesByQualifier == nil {
   672  		return c
   673  	}
   674  	for t, charge := range c.chargesByQualifier {
   675  		c.chargesByQualifier[t] = charge.Mul(qty)
   676  	}
   677  	return c
   678  }
   679  
   680  // Items returns all charges items
   681  func (c Charges) Items() []Charge {
   682  	var charges []Charge
   683  
   684  	for _, charge := range c.chargesByQualifier {
   685  		charges = append(charges, charge)
   686  	}
   687  
   688  	return charges
   689  }
   690  
   691  // addChargeQualifier parse string keys to charge qualifier for backwards compatibility
   692  func addChargeQualifier(chargesByType map[string]Charge) Charges {
   693  	withQualifier := make(map[ChargeQualifier]Charge)
   694  	for chargeType, charge := range chargesByType {
   695  		qualifier := ChargeQualifier{
   696  			Type:      chargeType,
   697  			Reference: charge.Reference,
   698  		}
   699  		withQualifier[qualifier] = charge
   700  	}
   701  	return Charges{chargesByQualifier: withQualifier}
   702  }