flowglad 0.1.1

(Unofficial) Rust SDK for FlowGlad - Open source billing infrastructure
Documentation
//! Customer types and request builders

use crate::types::{CustomerId, Metadata};
use serde::{Deserialize, Serialize};

/// Wrapper for customer creation requests (API expects `{"customer": {...}}`)
#[derive(Debug, Clone, Serialize)]
pub struct CreateCustomerRequest {
    /// The customer data to create
    pub customer: CreateCustomer,
}

/// Wrapper for customer update requests (API expects `{"customer": {...}}`)
#[derive(Debug, Clone, Serialize)]
pub struct UpdateCustomerRequest {
    /// The customer data to update
    pub customer: UpdateCustomer,
}

/// Wrapper for customer responses from CREATE (API returns `{"data": {"customer": {...}}}`)
#[derive(Debug, Clone, Deserialize)]
pub struct CustomerResponse {
    /// The response data wrapper
    pub data: CustomerData,
}

/// Customer data wrapper
#[derive(Debug, Clone, Deserialize)]
pub struct CustomerData {
    /// The customer object
    pub customer: Customer,
}

/// Wrapper for customer responses from GET (API returns `{"customer": {...}}`)
#[derive(Debug, Clone, Deserialize)]
pub struct SingleCustomerResponse {
    /// The customer object
    pub customer: Customer,
}

/// Wrapper for list responses
#[derive(Debug, Clone, Deserialize)]
pub struct ListCustomersResponse {
    /// The list of customers
    pub data: Vec<Customer>,
}

/// A FlowGlad customer
///
/// Customers represent individuals or organizations that purchase from you.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Customer {
    /// Unique identifier for the customer
    pub id: CustomerId,

    /// External identifier for the customer (your system's ID)
    pub external_id: String,

    /// Customer's email address
    #[serde(skip_serializing_if = "Option::is_none")]
    pub email: Option<String>,

    /// Customer's name
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,

    /// Customer's phone number
    #[serde(skip_serializing_if = "Option::is_none")]
    pub phone: Option<String>,

    /// Organization ID
    #[serde(skip_serializing_if = "Option::is_none")]
    pub organization_id: Option<String>,

    /// Whether this is a live mode customer
    #[serde(default)]
    pub livemode: bool,

    /// Whether the customer is archived
    #[serde(default)]
    pub archived: bool,

    /// Set of key-value pairs for storing additional information
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub metadata: Option<Metadata>,

    /// Timestamp when the customer was created (in milliseconds)
    #[serde(rename = "createdAt")]
    pub created_at: i64,

    /// Timestamp when the customer was last updated (in milliseconds)
    #[serde(rename = "updatedAt")]
    pub updated_at: i64,
}

/// Parameters for creating a customer
///
/// # Example
///
/// ```
/// use flowglad::types::customer::CreateCustomer;
///
/// let params = CreateCustomer::new("user_123", "Jane Doe")
///     .email("customer@example.com")
///     .phone("+1-555-0123");
/// ```
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateCustomer {
    /// External ID from your system (required)
    pub external_id: String,

    /// Customer's name (required)
    pub name: String,

    /// Customer's email address (optional)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub email: Option<String>,

    /// Customer's phone number (optional)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub phone: Option<String>,

    /// Additional metadata key-value pairs (optional)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub metadata: Option<Metadata>,
}

impl CreateCustomer {
    /// Create a new customer creation request
    ///
    /// # Arguments
    ///
    /// * `external_id` - Your system's unique identifier for this customer
    /// * `name` - The customer's name
    pub fn new(external_id: impl Into<String>, name: impl Into<String>) -> Self {
        Self {
            external_id: external_id.into(),
            name: name.into(),
            email: None,
            phone: None,
            metadata: None,
        }
    }

    /// Set the customer's email address
    pub fn email(mut self, email: impl Into<String>) -> Self {
        self.email = Some(email.into());
        self
    }

    /// Set the customer's phone number
    pub fn phone(mut self, phone: impl Into<String>) -> Self {
        self.phone = Some(phone.into());
        self
    }

    /// Add a metadata key-value pair
    pub fn metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
        self.metadata
            .get_or_insert_with(Metadata::new)
            .insert(key.into(), value);
        self
    }

    /// Set all metadata at once
    pub fn with_metadata(mut self, metadata: Metadata) -> Self {
        self.metadata = Some(metadata);
        self
    }
}

/// Parameters for updating a customer
///
/// # Example
///
/// ```
/// use flowglad::types::customer::UpdateCustomer;
///
/// let params = UpdateCustomer::new()
///     .email("newemail@example.com")
///     .name("Jane Smith");
/// ```
#[derive(Debug, Clone, Default, Serialize)]
pub struct UpdateCustomer {
    /// Updated email address
    #[serde(skip_serializing_if = "Option::is_none")]
    pub email: Option<String>,

    /// Updated name
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,

    /// Updated phone number
    #[serde(skip_serializing_if = "Option::is_none")]
    pub phone: Option<String>,

    /// Updated metadata
    #[serde(skip_serializing_if = "Option::is_none")]
    pub metadata: Option<Metadata>,
}

impl UpdateCustomer {
    /// Create a new customer update request
    pub fn new() -> Self {
        Self::default()
    }

    /// Update the customer's email address
    pub fn email(mut self, email: impl Into<String>) -> Self {
        self.email = Some(email.into());
        self
    }

    /// Update the customer's name
    pub fn name(mut self, name: impl Into<String>) -> Self {
        self.name = Some(name.into());
        self
    }

    /// Update the customer's phone number
    pub fn phone(mut self, phone: impl Into<String>) -> Self {
        self.phone = Some(phone.into());
        self
    }

    /// Add a metadata key-value pair
    pub fn metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
        self.metadata
            .get_or_insert_with(Metadata::new)
            .insert(key.into(), value);
        self
    }

    /// Set all metadata at once
    pub fn with_metadata(mut self, metadata: Metadata) -> Self {
        self.metadata = Some(metadata);
        self
    }
}

/// Billing details for a customer
///
/// Contains comprehensive information about a customer's billing state,
/// including subscriptions, features, and usage.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BillingDetails {
    /// The customer object
    pub customer: Customer,

    /// Active subscriptions
    #[serde(default)]
    pub subscriptions: Vec<serde_json::Value>,

    /// Current subscriptions (appears to be same as subscriptions)
    #[serde(default)]
    pub current_subscriptions: Vec<serde_json::Value>,

    /// Customer invoices
    #[serde(default)]
    pub invoices: Vec<serde_json::Value>,

    /// Payment methods
    #[serde(default)]
    pub payment_methods: Vec<serde_json::Value>,

    /// Purchases
    #[serde(default)]
    pub purchases: Vec<serde_json::Value>,

    /// Pricing model
    #[serde(skip_serializing_if = "Option::is_none")]
    pub pricing_model: Option<serde_json::Value>,

    /// Catalog
    #[serde(skip_serializing_if = "Option::is_none")]
    pub catalog: Option<serde_json::Value>,

    /// Billing portal URL
    #[serde(skip_serializing_if = "Option::is_none")]
    pub billing_portal_url: Option<String>,
}

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

    #[test]
    fn test_create_customer_builder() {
        let params = CreateCustomer::new("user_123", "Test User")
            .email("test@example.com")
            .phone("+1-555-0123");

        assert_eq!(params.external_id, "user_123");
        assert_eq!(params.name, "Test User");
        assert_eq!(params.email.as_deref(), Some("test@example.com"));
        assert_eq!(params.phone.as_deref(), Some("+1-555-0123"));
    }

    #[test]
    fn test_create_customer_with_metadata() {
        let params = CreateCustomer::new("user_456", "Metadata User")
            .email("test@example.com")
            .metadata("plan", serde_json::json!("premium"))
            .metadata("tier", serde_json::json!(3));

        assert_eq!(params.external_id, "user_456");
        assert_eq!(params.name, "Metadata User");
        assert!(params.metadata.is_some());
        let metadata = params.metadata.unwrap();
        assert_eq!(metadata.len(), 2);
        assert_eq!(metadata["plan"], "premium");
        assert_eq!(metadata["tier"], 3);
    }

    #[test]
    fn test_update_customer_builder() {
        let params = UpdateCustomer::new()
            .name("Updated Name")
            .email("updated@example.com");

        assert_eq!(params.name.as_deref(), Some("Updated Name"));
        assert_eq!(params.email.as_deref(), Some("updated@example.com"));
    }

    #[test]
    fn test_customer_serialization() {
        let customer_json = r#"{
            "id": "cus_123",
            "externalId": "user_123",
            "email": "test@example.com",
            "name": "Test User",
            "metadata": {},
            "createdAt": 1640995200000,
            "updatedAt": 1640995200000
        }"#;

        let customer: Customer = serde_json::from_str(customer_json).unwrap();
        assert_eq!(customer.id.as_str(), "cus_123");
        assert_eq!(customer.external_id, "user_123");
        assert_eq!(customer.email.as_deref(), Some("test@example.com"));
        assert_eq!(customer.name.as_deref(), Some("Test User"));
        assert_eq!(customer.created_at, 1640995200000);
        assert_eq!(customer.updated_at, 1640995200000);
    }
}