1use crate::{CountryCode, PaymentError, PhoneNumber};
2
3#[derive(Debug, Clone, PartialEq, Eq)]
5#[non_exhaustive]
6pub enum PaymentMethod {
7 Card(CardPaymentMethod),
9 Stablecoin(StablecoinPaymentMethod),
11 Crypto(CryptoPaymentMethod),
13 PayPal(PayPalPaymentMethod),
15 MobileMoney(MobileMoneyPaymentMethod),
17}
18
19impl PaymentMethod {
20 #[inline]
22 #[must_use]
23 pub const fn card() -> Self {
24 Self::Card(CardPaymentMethod)
25 }
26
27 #[inline]
29 #[must_use]
30 pub const fn paypal() -> Self {
31 Self::PayPal(PayPalPaymentMethod)
32 }
33
34 #[inline]
36 #[must_use]
37 pub fn stablecoin(asset: StablecoinAsset) -> Self {
38 Self::Stablecoin(StablecoinPaymentMethod {
39 preferred_asset: Some(asset),
40 })
41 }
42
43 #[inline]
45 #[must_use]
46 pub fn stablecoin_usdc() -> Self {
47 Self::stablecoin(StablecoinAsset::Usdc)
48 }
49
50 #[inline]
52 #[must_use]
53 pub fn stablecoin_usdt() -> Self {
54 Self::stablecoin(StablecoinAsset::Usdt)
55 }
56
57 #[inline]
59 #[must_use]
60 pub const fn crypto(asset: CryptoAsset) -> Self {
61 Self::Crypto(CryptoPaymentMethod {
62 asset,
63 network: None,
64 })
65 }
66
67 #[inline]
69 #[must_use]
70 pub const fn crypto_on(asset: CryptoAsset, network: CryptoNetwork) -> Self {
71 Self::Crypto(CryptoPaymentMethod {
72 asset,
73 network: Some(network),
74 })
75 }
76
77 #[inline]
79 #[must_use]
80 pub const fn usdc_on(network: CryptoNetwork) -> Self {
81 Self::crypto_on(CryptoAsset::Usdc, network)
82 }
83
84 #[inline]
86 #[must_use]
87 pub const fn usdt_on(network: CryptoNetwork) -> Self {
88 Self::crypto_on(CryptoAsset::Usdt, network)
89 }
90
91 pub fn mobile_money_zambia(phone_number: impl AsRef<str>) -> Result<Self, PaymentError> {
97 Ok(Self::MobileMoney(MobileMoneyPaymentMethod {
98 country: CountryCode::new("ZM")?,
99 phone_number: PhoneNumber::new(phone_number)?,
100 operator: None,
101 }))
102 }
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
107pub struct CardPaymentMethod;
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
111pub struct PayPalPaymentMethod;
112
113#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
115pub struct StablecoinPaymentMethod {
116 pub preferred_asset: Option<StablecoinAsset>,
118}
119
120#[derive(Debug, Clone, PartialEq, Eq, Hash)]
122#[non_exhaustive]
123pub enum StablecoinAsset {
124 Usdc,
126 Usdt,
128 Usdp,
130 Usdg,
132 Other(String),
134}
135
136#[derive(Debug, Clone, PartialEq, Eq, Hash)]
138pub struct CryptoPaymentMethod {
139 pub asset: CryptoAsset,
141 pub network: Option<CryptoNetwork>,
143}
144
145#[derive(Debug, Clone, PartialEq, Eq, Hash)]
147#[non_exhaustive]
148pub enum CryptoAsset {
149 Btc,
151 Eth,
153 Sol,
155 Usdc,
157 Usdt,
159 Usdp,
161 Usdg,
163 Other(String),
165}
166
167#[derive(Debug, Clone, PartialEq, Eq, Hash)]
169#[non_exhaustive]
170pub enum CryptoNetwork {
171 Bitcoin,
173 Ethereum,
175 Solana,
177 Polygon,
179 Base,
181 Bsc,
183 Arbitrum,
185 Optimism,
187 Other(String),
189}
190
191impl From<&StablecoinAsset> for CryptoAsset {
192 fn from(asset: &StablecoinAsset) -> Self {
193 match asset {
194 StablecoinAsset::Usdc => Self::Usdc,
195 StablecoinAsset::Usdt => Self::Usdt,
196 StablecoinAsset::Usdp => Self::Usdp,
197 StablecoinAsset::Usdg => Self::Usdg,
198 StablecoinAsset::Other(asset) => Self::Other(asset.clone()),
199 }
200 }
201}
202
203#[derive(Debug, Clone, PartialEq, Eq)]
205pub struct MobileMoneyPaymentMethod {
206 pub country: CountryCode,
208 pub phone_number: PhoneNumber,
210 pub operator: Option<MobileMoneyOperator>,
212}
213
214#[derive(Debug, Clone, PartialEq, Eq)]
216#[non_exhaustive]
217pub enum MobileMoneyOperator {
218 Mtn,
220 Airtel,
222 Zamtel,
224 Mpesa,
226 Orange,
228 Other(String),
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 #[test]
237 fn mobile_money_zambia_sets_country() {
238 let method =
239 PaymentMethod::mobile_money_zambia("260971234567").expect("method should be valid");
240
241 match method {
242 PaymentMethod::MobileMoney(method) => {
243 assert_eq!(method.country.as_str(), "ZM");
244 assert_eq!(method.phone_number.digits(), "260971234567");
245 }
246 PaymentMethod::Card(_)
247 | PaymentMethod::Stablecoin(_)
248 | PaymentMethod::Crypto(_)
249 | PaymentMethod::PayPal(_) => panic!("expected mobile money"),
250 }
251 }
252
253 #[test]
254 fn helper_constructors_create_expected_variants() {
255 assert!(matches!(PaymentMethod::card(), PaymentMethod::Card(_)));
256 assert!(matches!(PaymentMethod::paypal(), PaymentMethod::PayPal(_)));
257 assert!(matches!(
258 PaymentMethod::stablecoin_usdc(),
259 PaymentMethod::Stablecoin(StablecoinPaymentMethod {
260 preferred_asset: Some(StablecoinAsset::Usdc)
261 })
262 ));
263 assert!(matches!(
264 PaymentMethod::stablecoin_usdt(),
265 PaymentMethod::Stablecoin(StablecoinPaymentMethod {
266 preferred_asset: Some(StablecoinAsset::Usdt)
267 })
268 ));
269 assert!(matches!(
270 PaymentMethod::usdc_on(CryptoNetwork::Base),
271 PaymentMethod::Crypto(CryptoPaymentMethod {
272 asset: CryptoAsset::Usdc,
273 network: Some(CryptoNetwork::Base)
274 })
275 ));
276 assert!(matches!(
277 PaymentMethod::usdt_on(CryptoNetwork::Solana),
278 PaymentMethod::Crypto(CryptoPaymentMethod {
279 asset: CryptoAsset::Usdt,
280 network: Some(CryptoNetwork::Solana)
281 })
282 ));
283 }
284
285 #[test]
286 fn stablecoin_assets_convert_to_crypto_assets() {
287 assert_eq!(CryptoAsset::from(&StablecoinAsset::Usdc), CryptoAsset::Usdc);
288 assert_eq!(CryptoAsset::from(&StablecoinAsset::Usdt), CryptoAsset::Usdt);
289 assert_eq!(
290 CryptoAsset::from(&StablecoinAsset::Other("eurc".to_owned())),
291 CryptoAsset::Other("eurc".to_owned())
292 );
293 }
294}