snippe 0.1.0

Async Rust client for the Snippe payments API (Tanzania) — collections, hosted checkout sessions, disbursements, and verified webhooks.
Documentation
//! Disbursements — `/v1/payouts`.

use serde::Serialize;

use crate::client::{encode_path_segment, Client};
use crate::idempotency::IdempotencyKey;
use crate::models::common::ListParams;
use crate::models::payout::{Payout, PayoutFee, SendPayoutRequest};

/// Handle returned by [`Client::payouts`](crate::Client::payouts).
#[derive(Debug, Clone, Copy)]
pub struct Payouts<'c> {
    client: &'c Client,
}

#[derive(Debug, Clone, Serialize)]
struct FeeQuery {
    amount: u64,
}

impl<'c> Payouts<'c> {
    pub(crate) fn new(client: &'c Client) -> Self {
        Self { client }
    }

    /// Send a payout — `POST /v1/payouts/send`.
    ///
    /// Always pass an [`IdempotencyKey`]. Payroll runs and refund retries are
    /// the textbook case for idempotency — a missing key plus a network
    /// hiccup is exactly how merchants double-pay employees.
    ///
    /// **Preflight pattern**: call [`Self::fee`] for the fee, then
    /// [`crate::api::Payments::balance`] to confirm
    /// `available.value ≥ total_amount`, *then* call this. See the
    /// `references/disbursements.md` skill file for the rationale.
    pub async fn send(
        &self,
        request: &SendPayoutRequest,
        idempotency_key: Option<&IdempotencyKey>,
    ) -> crate::Result<Payout> {
        let mut req = self.client.post("/v1/payouts/send").json(request);
        if let Some(k) = idempotency_key {
            req = req.idempotency_key(k);
        }
        req.send().await
    }

    /// Retrieve a payout — `GET /v1/payouts/{reference}`.
    pub async fn get(&self, reference: &str) -> crate::Result<Payout> {
        self.client
            .get(&format!("/v1/payouts/{}", encode_path_segment(reference)))
            .send()
            .await
    }

    /// List payouts — `GET /v1/payouts`.
    pub async fn list(&self, params: &ListParams) -> crate::Result<Vec<Payout>> {
        self.client.get("/v1/payouts").query(params).send().await
    }

    /// Calculate the fee for a payout amount — `GET /v1/payouts/fee`.
    pub async fn fee(&self, amount: u64) -> crate::Result<PayoutFee> {
        self.client
            .get("/v1/payouts/fee")
            .query(&FeeQuery { amount })
            .send()
            .await
    }
}