Skip to main content

stripe_pay_core/money/
impl.rs

1use super::*;
2
3impl Currency {
4    /// Return the ISO 4217 alphabetic code Stripe expects.
5    ///
6    /// # Returns
7    ///
8    /// - `&'static str` - the three-letter uppercase currency code.
9    pub const fn code(&self) -> &'static str {
10        match self {
11            Currency::Usd => "USD",
12            Currency::Eur => "EUR",
13            Currency::Gbp => "GBP",
14            Currency::Jpy => "JPY",
15            Currency::Chf => "CHF",
16        }
17    }
18
19    /// Return the number of decimal places this currency stores.
20    ///
21    /// Stripe's zero-decimal currencies such as JPY report amounts in
22    /// the major denomination rather than in minor units, so the
23    /// exponent is the single source of truth for the conversion.
24    ///
25    /// # Returns
26    ///
27    /// - `u32` - the exponent Stripe uses when serializing amounts.
28    pub const fn exponent(&self) -> u32 {
29        match self {
30            Currency::Usd | Currency::Eur | Currency::Gbp | Currency::Chf => {
31                DEFAULT_CURRENCY_EXPONENT
32            }
33            Currency::Jpy => ZERO_DECIMAL_EXPONENT,
34        }
35    }
36
37    /// Return the divisor converting a major amount to minor units.
38    ///
39    /// # Returns
40    ///
41    /// - `u32` - `100` for two-decimal currencies and `1` for JPY.
42    pub const fn minor_units_divisor(&self) -> u32 {
43        match self {
44            Currency::Usd | Currency::Eur | Currency::Gbp | Currency::Chf => MINOR_UNITS_DIVISOR,
45            Currency::Jpy => 1,
46        }
47    }
48}
49
50impl Display for Currency {
51    /// Render the ISO 4217 code.
52    ///
53    /// # Arguments
54    ///
55    /// - `&mut Formatter<'_>` - the formatter to write the value into.
56    ///
57    /// # Returns
58    ///
59    /// A `Formatter` writing the uppercase currency code.
60    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
61        write!(formatter, "{}", self.code())
62    }
63}
64
65impl FromStr for Currency {
66    type Err = StripeParseError;
67
68    /// Parse an ISO 4217 code, case-insensitively.
69    ///
70    /// # Arguments
71    ///
72    /// - `&str` - the currency code to parse.
73    ///
74    /// # Returns
75    ///
76    /// - `Result<Self, Self::Err>` - the parsed currency, or the
77    ///   reason the code was not recognised.
78    fn from_str(text: &str) -> Result<Self, Self::Err> {
79        match text.to_ascii_uppercase().as_str() {
80            "USD" => Ok(Currency::Usd),
81            "EUR" => Ok(Currency::Eur),
82            "GBP" => Ok(Currency::Gbp),
83            "JPY" => Ok(Currency::Jpy),
84            "CHF" => Ok(Currency::Chf),
85            other => Err(StripeParseError::UnknownCurrency(String::from(other))),
86        }
87    }
88}
89
90impl Money {
91    /// Build a zero amount in the given currency.
92    ///
93    /// # Arguments
94    ///
95    /// - `Currency` - the currency of the zero amount.
96    ///
97    /// # Returns
98    ///
99    /// - `Self` - a zero-valued amount.
100    pub const fn zero(currency: Currency) -> Self {
101        Self {
102            amount: 0,
103            currency,
104        }
105    }
106
107    /// Build an amount directly from a minor-unit value.
108    ///
109    /// This is the constructor that matches the wire format: Stripe
110    /// sends and accepts `amount` in the smallest denomination.
111    ///
112    /// # Arguments
113    ///
114    /// - `i64` - the amount in the currency's smallest denomination.
115    /// - `Currency` - the currency the amount is denominated in.
116    ///
117    /// # Returns
118    ///
119    /// - `Self` - the amount, unchanged.
120    pub const fn from_minor(amount: i64, currency: Currency) -> Self {
121        Self { amount, currency }
122    }
123
124    /// Build an amount from a major-unit value.
125    ///
126    /// `1_099` USD becomes `109_900` minor units; `1_099` JPY stays
127    /// `1_099` because JPY has no minor unit.
128    ///
129    /// # Arguments
130    ///
131    /// - `i64` - the amount in major units.
132    /// - `Currency` - the currency the amount is denominated in.
133    ///
134    /// # Returns
135    ///
136    /// - `Self` - the amount converted into minor units.
137    pub const fn from_major(amount: i64, currency: Currency) -> Self {
138        let scale: i64 = currency.minor_units_divisor() as i64;
139        Self {
140            amount: amount * scale,
141            currency,
142        }
143    }
144
145    /// Return the ISO 4217 code Stripe expects for this amount.
146    ///
147    /// # Returns
148    ///
149    /// - `&'static str` - the three-letter uppercase currency code.
150    pub fn currency_code(&self) -> &'static str {
151        self.get_currency().code()
152    }
153
154    /// Return the amount converted back to major units.
155    ///
156    /// # Returns
157    ///
158    /// - `Decimal` - the minor-unit count rescaled by the currency's
159    ///   exponent, so `200` USD reads back as `2.00`.
160    pub fn to_major(&self) -> Decimal {
161        Decimal::new(self.get_amount(), self.get_currency().exponent())
162    }
163
164    /// Return the same minor-unit count labelled with another currency.
165    ///
166    /// Stripe never converts between currencies; this only rewrites the
167    /// label and exists for presentation code, not for sending a charge.
168    ///
169    /// # Arguments
170    ///
171    /// - `Currency` - the currency to relabel the amount with.
172    ///
173    /// # Returns
174    ///
175    /// - `Self` - the same minor-unit count under a new currency.
176    pub fn with_currency(&self, currency: Currency) -> Self {
177        Self {
178            amount: self.get_amount(),
179            currency,
180        }
181    }
182
183    /// Return whether the amount is negative, which Stripe rejects.
184    ///
185    /// # Returns
186    ///
187    /// - `bool` - `true` when the amount is below zero.
188    pub fn is_negative(&self) -> bool {
189        self.get_amount() < 0
190    }
191
192    /// Return the amount with any leading minus sign removed.
193    ///
194    /// # Returns
195    ///
196    /// - `Self` - the absolute value of the amount.
197    pub fn abs(&self) -> Self {
198        Self {
199            amount: self.get_amount().abs(),
200            currency: self.get_currency(),
201        }
202    }
203
204    /// Return this amount plus another of the same currency.
205    ///
206    /// # Arguments
207    ///
208    /// - `Self` - the amount to add.
209    ///
210    /// # Returns
211    ///
212    /// - `Result<Self, StripeParseError>` - the sum, or an error when
213    ///   the currencies differ or the addition overflows `i64`.
214    pub fn checked_add(&self, other: Self) -> Result<Self, StripeParseError> {
215        if self.get_currency() != other.get_currency() {
216            return Err(StripeParseError::CurrencyMismatch {
217                left: self.get_currency(),
218                right: other.get_currency(),
219            });
220        }
221        let sum: i64 = self
222            .get_amount()
223            .checked_add(other.get_amount())
224            .ok_or(StripeParseError::AmountOverflow)?;
225        Ok(Self {
226            amount: sum,
227            currency: self.get_currency(),
228        })
229    }
230}
231
232impl Display for Money {
233    /// Render the amount as a decimal in major units.
234    ///
235    /// # Arguments
236    ///
237    /// - `&mut Formatter<'_>` - the formatter to write the value into.
238    ///
239    /// # Returns
240    ///
241    /// A `Formatter` writing the decimal amount and its currency code.
242    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
243        write!(formatter, "{} {}", self.to_major(), self.currency_code())
244    }
245}