Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/monetize

crates/monetize-pay/src/shapes.rs

//! ★ **What differs per payment vendor KIND, as data** (A32-15: the core is no vendor's shape).
//!
//! The pay core, the server and the ONE box's site book editor branch on no vendor's name: what a
//! vendor kind's ids say about its mode, and what its two account secrets look like, are this
//! table's rows — one per kind a ONE box may hold an account of. A new kind is a new row (and
//! its signed guest), never a new branch in the core. The rows are the vendors' own published
//! facts (Stripe's `cs_live_` / `cs_test_` ids and `sk_` / `rk_` / `whsec_` secrets).

/// One vendor kind's shape.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Shape {
    /// The kind, as an instance names it (`<kind>/<account>`).
    pub kind: &'static str,
    /// The vendor's word for this kind, for a person ("Stripe").
    pub title: &'static str,
    /// The marker a LIVE object's id carries (empty: its ids carry none).
    pub live_marker: &'static str,
    /// The marker a TEST object's id carries.
    pub test_marker: &'static str,
    /// Play money only: every payment of this kind is test money.
    pub always_test: bool,
    /// The prefixes an account's secret key may start with (empty: any).
    pub key_prefixes: &'static [&'static str],
    /// The prefixes an account's webhook secret may start with (empty: any).
    pub hook_prefixes: &'static [&'static str],
    /// What the secret key is, for a person.
    pub key_words: &'static str,
    /// What the webhook secret is, for a person.
    pub hook_words: &'static str,
}

/// The kinds a ONE box knows the shape of.
pub const SHAPES: &[Shape] = &[
    Shape {
        kind: "stripe",
        title: "Stripe",
        live_marker: "_live_",
        test_marker: "_test_",
        always_test: false,
        key_prefixes: &["sk_test_", "sk_live_", "rk_test_", "rk_live_"],
        hook_prefixes: &["whsec_"],
        key_words: "the account's `sk_…` (or restricted `rk_…`) key",
        hook_words: "the endpoint's `whsec_…` signing secret",
    },
    Shape {
        kind: "sandbox",
        title: "Sandbox",
        live_marker: "",
        test_marker: "",
        always_test: true,
        key_prefixes: &[],
        hook_prefixes: &[],
        key_words: "its key",
        hook_words: "its webhook secret",
    },
];

/// The shape of `kind` (an instance's `<kind>/…` or the bare kind), if a ONE box knows it.
pub fn of(kind_or_instance: &str) -> Option<&'static Shape> {
    let kind = crate::instance::kind_of(kind_or_instance);
    SHAPES.iter().find(|s| s.kind == kind)
}

impl Shape {
    /// Pure: **the mode an object's `id` says** — `Some(true)` live, `Some(false)` test, `None`
    /// when it carries no marker (never a guess).
    pub fn mode_of(&self, id: &str) -> Option<bool> {
        if self.always_test {
            return Some(false);
        }
        if !self.live_marker.is_empty() && id.contains(self.live_marker) {
            return Some(true);
        }
        if !self.test_marker.is_empty() && id.contains(self.test_marker) {
            return Some(false);
        }
        None
    }

    /// Pure: is `secret` a LIVE secret of this kind?
    pub fn is_live(&self, secret: &str) -> bool {
        !self.always_test && !self.live_marker.is_empty() && secret.contains(self.live_marker)
    }

    /// Pure: `Ok` when `key` (empty: none given) looks like this kind's secret key.
    pub fn key_ok(&self, key: &str) -> bool {
        key.is_empty()
            || self.key_prefixes.is_empty()
            || self.key_prefixes.iter().any(|p| key.starts_with(p))
    }

    /// Pure: `Ok` when `hook` (empty: none given) looks like this kind's webhook secret.
    pub fn hook_ok(&self, hook: &str) -> bool {
        hook.is_empty()
            || self.hook_prefixes.is_empty()
            || self.hook_prefixes.iter().any(|p| hook.starts_with(p))
    }
}

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

    #[test]
    fn a_kinds_shape_reads_its_ids_and_its_secrets() {
        let s = of("stripe/gunnar").expect("a known kind");
        assert_eq!(
            (
                s.mode_of("cs_live_a1"),
                s.mode_of("cs_test_a1"),
                s.mode_of("pi_3Nx")
            ),
            (Some(true), Some(false), None)
        );
        assert!(s.is_live("sk_live_x") && !s.is_live("sk_test_x"));
        assert!(s.key_ok("rk_test_1") && !s.key_ok("pk_live_1") && s.key_ok(""));
        assert!(s.hook_ok("whsec_1") && !s.hook_ok("sk_test_1"));
        assert_eq!(
            of("sandbox/play").unwrap().mode_of("sbx-1"),
            Some(false),
            "play money is never real"
        );
        assert!(of("invoice").is_none(), "a kind with no row has no shape");
    }
}