snippe 0.1.0

Async Rust client for the Snippe payments API (Tanzania) — collections, hosted checkout sessions, disbursements, and verified webhooks.
Documentation
//! Tanzanian bank codes accepted by the Disbursements API.

use serde::{Deserialize, Serialize};

/// Bank codes accepted by `POST /v1/payouts/send` with `channel: "bank"`.
///
/// The variants enumerate the most-used Tanzanian banks. Snippe supports
/// 25+ banks; the canonical, fully-current list lives at
/// <https://snippe.sh/docs/2026-01-25/disbursements/bank-transfer>.
///
/// Use [`BankCode::Other`] to pass codes that this SDK version doesn't yet
/// enumerate. Snippe will validate the code server-side.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum BankCode {
    /// Absa Bank Tanzania (`ABSA`).
    Absa,
    /// Access Bank Tanzania (`ACCESS`).
    Access,
    /// Akiba Commercial Bank (`AKIBA`).
    Akiba,
    /// Amana Bank (`AMANA`).
    Amana,
    /// Azania Bank (`AZANIA`).
    Azania,
    /// Bank of Baroda (`BARODA`).
    Baroda,
    /// Bank of Africa (`BOA`).
    Boa,
    /// Citibank Tanzania (`CITI`).
    Citi,
    /// CRDB Bank (`CRDB`).
    Crdb,
    /// Diamond Trust Bank (`DTB`).
    Dtb,
    /// Ecobank Tanzania (`ECOBANK`).
    Ecobank,
    /// Equity Bank Tanzania (`EQUITY`).
    Equity,
    /// Exim Bank Tanzania (`EXIM`).
    Exim,
    /// First National Bank Tanzania (`FNB`).
    Fnb,
    /// Habib African Bank (`HABIB`).
    Habib,
    /// I&M Bank (`IMBANK`).
    ImBank,
    /// Kenya Commercial Bank Tanzania (`KCB`).
    Kcb,
    /// National Bank of Commerce (`NBC`).
    Nbc,
    /// NCBA Bank Tanzania (`NCBA`).
    Ncba,
    /// National Microfinance Bank (`NMB`).
    Nmb,
    /// People's Bank of Zanzibar (`PBZ`).
    Pbz,
    /// Standard Chartered Bank Tanzania (`SCB`).
    Scb,
    /// Stanbic Bank Tanzania (`STANBIC`).
    Stanbic,
    /// Tanzania Commercial Bank (`TCB`).
    Tcb,
    /// United Bank for Africa Tanzania (`UBA`).
    Uba,
    /// A bank code not enumerated by this SDK version. Use this to pass
    /// future bank codes through unchanged.
    Other(String),
}

impl BankCode {
    /// Return the wire string representation.
    pub fn as_str(&self) -> &str {
        match self {
            Self::Absa => "ABSA",
            Self::Access => "ACCESS",
            Self::Akiba => "AKIBA",
            Self::Amana => "AMANA",
            Self::Azania => "AZANIA",
            Self::Baroda => "BARODA",
            Self::Boa => "BOA",
            Self::Citi => "CITI",
            Self::Crdb => "CRDB",
            Self::Dtb => "DTB",
            Self::Ecobank => "ECOBANK",
            Self::Equity => "EQUITY",
            Self::Exim => "EXIM",
            Self::Fnb => "FNB",
            Self::Habib => "HABIB",
            Self::ImBank => "IMBANK",
            Self::Kcb => "KCB",
            Self::Nbc => "NBC",
            Self::Ncba => "NCBA",
            Self::Nmb => "NMB",
            Self::Pbz => "PBZ",
            Self::Scb => "SCB",
            Self::Stanbic => "STANBIC",
            Self::Tcb => "TCB",
            Self::Uba => "UBA",
            Self::Other(s) => s.as_str(),
        }
    }

    /// Parse a wire-format bank code string into a [`BankCode`].
    ///
    /// Unknown codes preserve the raw string in [`BankCode::Other`] — this
    /// parser never fails.
    pub fn parse(s: &str) -> Self {
        match s {
            "ABSA" => Self::Absa,
            "ACCESS" => Self::Access,
            "AKIBA" => Self::Akiba,
            "AMANA" => Self::Amana,
            "AZANIA" => Self::Azania,
            "BARODA" => Self::Baroda,
            "BOA" => Self::Boa,
            "CITI" => Self::Citi,
            "CRDB" => Self::Crdb,
            "DTB" => Self::Dtb,
            "ECOBANK" => Self::Ecobank,
            "EQUITY" => Self::Equity,
            "EXIM" => Self::Exim,
            "FNB" => Self::Fnb,
            "HABIB" => Self::Habib,
            "IMBANK" => Self::ImBank,
            "KCB" => Self::Kcb,
            "NBC" => Self::Nbc,
            "NCBA" => Self::Ncba,
            "NMB" => Self::Nmb,
            "PBZ" => Self::Pbz,
            "SCB" => Self::Scb,
            "STANBIC" => Self::Stanbic,
            "TCB" => Self::Tcb,
            "UBA" => Self::Uba,
            other => Self::Other(other.to_string()),
        }
    }
}

impl std::fmt::Display for BankCode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(self.as_str())
    }
}

impl std::str::FromStr for BankCode {
    type Err = std::convert::Infallible;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Self::parse(s))
    }
}

impl Serialize for BankCode {
    fn serialize<S>(&self, ser: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        ser.serialize_str(self.as_str())
    }
}

impl<'de> Deserialize<'de> for BankCode {
    fn deserialize<D>(d: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let s = String::deserialize(d)?;
        Ok(Self::parse(&s))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn known_codes_round_trip() {
        for code in [
            "ABSA", "CRDB", "NMB", "NBC", "STANBIC", "EQUITY", "KCB", "UBA",
        ] {
            let parsed = BankCode::parse(code);
            assert!(!matches!(parsed, BankCode::Other(_)), "{}", code);
            assert_eq!(parsed.as_str(), code);
        }
    }

    #[test]
    fn unknown_codes_preserve_raw() {
        let code = BankCode::parse("FUTURE_BANK");
        assert!(matches!(code, BankCode::Other(_)));
        assert_eq!(code.as_str(), "FUTURE_BANK");
    }

    #[test]
    fn fromstr_works_via_parse() {
        let code: BankCode = "CRDB".parse().unwrap();
        assert_eq!(code, BankCode::Crdb);
    }

    #[test]
    fn serialises_as_string() {
        let json = serde_json::to_string(&BankCode::Crdb).unwrap();
        assert_eq!(json, r#""CRDB""#);
    }
}