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}