use crate::{CountryCode, PaymentError, PhoneNumber};
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum PaymentMethod {
Card(CardPaymentMethod),
Stablecoin(StablecoinPaymentMethod),
Crypto(CryptoPaymentMethod),
PayPal(PayPalPaymentMethod),
MobileMoney(MobileMoneyPaymentMethod),
}
impl PaymentMethod {
#[inline]
#[must_use]
pub const fn card() -> Self {
Self::Card(CardPaymentMethod)
}
#[inline]
#[must_use]
pub const fn paypal() -> Self {
Self::PayPal(PayPalPaymentMethod)
}
#[inline]
#[must_use]
pub fn stablecoin(asset: StablecoinAsset) -> Self {
Self::Stablecoin(StablecoinPaymentMethod {
preferred_asset: Some(asset),
})
}
#[inline]
#[must_use]
pub fn stablecoin_usdc() -> Self {
Self::stablecoin(StablecoinAsset::Usdc)
}
#[inline]
#[must_use]
pub fn stablecoin_usdt() -> Self {
Self::stablecoin(StablecoinAsset::Usdt)
}
#[inline]
#[must_use]
pub const fn crypto(asset: CryptoAsset) -> Self {
Self::Crypto(CryptoPaymentMethod {
asset,
network: None,
})
}
#[inline]
#[must_use]
pub const fn crypto_on(asset: CryptoAsset, network: CryptoNetwork) -> Self {
Self::Crypto(CryptoPaymentMethod {
asset,
network: Some(network),
})
}
#[inline]
#[must_use]
pub const fn usdc_on(network: CryptoNetwork) -> Self {
Self::crypto_on(CryptoAsset::Usdc, network)
}
#[inline]
#[must_use]
pub const fn usdt_on(network: CryptoNetwork) -> Self {
Self::crypto_on(CryptoAsset::Usdt, network)
}
pub fn mobile_money_zambia(phone_number: impl AsRef<str>) -> Result<Self, PaymentError> {
Ok(Self::MobileMoney(MobileMoneyPaymentMethod {
country: CountryCode::new("ZM")?,
phone_number: PhoneNumber::new(phone_number)?,
operator: None,
}))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct CardPaymentMethod;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct PayPalPaymentMethod;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub struct StablecoinPaymentMethod {
pub preferred_asset: Option<StablecoinAsset>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum StablecoinAsset {
Usdc,
Usdt,
Usdp,
Usdg,
Other(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CryptoPaymentMethod {
pub asset: CryptoAsset,
pub network: Option<CryptoNetwork>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum CryptoAsset {
Btc,
Eth,
Sol,
Usdc,
Usdt,
Usdp,
Usdg,
Other(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum CryptoNetwork {
Bitcoin,
Ethereum,
Solana,
Polygon,
Base,
Bsc,
Arbitrum,
Optimism,
Other(String),
}
impl From<&StablecoinAsset> for CryptoAsset {
fn from(asset: &StablecoinAsset) -> Self {
match asset {
StablecoinAsset::Usdc => Self::Usdc,
StablecoinAsset::Usdt => Self::Usdt,
StablecoinAsset::Usdp => Self::Usdp,
StablecoinAsset::Usdg => Self::Usdg,
StablecoinAsset::Other(asset) => Self::Other(asset.clone()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MobileMoneyPaymentMethod {
pub country: CountryCode,
pub phone_number: PhoneNumber,
pub operator: Option<MobileMoneyOperator>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum MobileMoneyOperator {
Mtn,
Airtel,
Zamtel,
Mpesa,
Orange,
Other(String),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mobile_money_zambia_sets_country() {
let method =
PaymentMethod::mobile_money_zambia("260971234567").expect("method should be valid");
match method {
PaymentMethod::MobileMoney(method) => {
assert_eq!(method.country.as_str(), "ZM");
assert_eq!(method.phone_number.digits(), "260971234567");
}
PaymentMethod::Card(_)
| PaymentMethod::Stablecoin(_)
| PaymentMethod::Crypto(_)
| PaymentMethod::PayPal(_) => panic!("expected mobile money"),
}
}
#[test]
fn helper_constructors_create_expected_variants() {
assert!(matches!(PaymentMethod::card(), PaymentMethod::Card(_)));
assert!(matches!(PaymentMethod::paypal(), PaymentMethod::PayPal(_)));
assert!(matches!(
PaymentMethod::stablecoin_usdc(),
PaymentMethod::Stablecoin(StablecoinPaymentMethod {
preferred_asset: Some(StablecoinAsset::Usdc)
})
));
assert!(matches!(
PaymentMethod::stablecoin_usdt(),
PaymentMethod::Stablecoin(StablecoinPaymentMethod {
preferred_asset: Some(StablecoinAsset::Usdt)
})
));
assert!(matches!(
PaymentMethod::usdc_on(CryptoNetwork::Base),
PaymentMethod::Crypto(CryptoPaymentMethod {
asset: CryptoAsset::Usdc,
network: Some(CryptoNetwork::Base)
})
));
assert!(matches!(
PaymentMethod::usdt_on(CryptoNetwork::Solana),
PaymentMethod::Crypto(CryptoPaymentMethod {
asset: CryptoAsset::Usdt,
network: Some(CryptoNetwork::Solana)
})
));
}
#[test]
fn stablecoin_assets_convert_to_crypto_assets() {
assert_eq!(CryptoAsset::from(&StablecoinAsset::Usdc), CryptoAsset::Usdc);
assert_eq!(CryptoAsset::from(&StablecoinAsset::Usdt), CryptoAsset::Usdt);
assert_eq!(
CryptoAsset::from(&StablecoinAsset::Other("eurc".to_owned())),
CryptoAsset::Other("eurc".to_owned())
);
}
}