use codes_iso_3166::part_1::CountryCode;
use codes_iso_4217::CurrencyCode;
use futures_core::Stream;
use futures_util::stream::TryStreamExt;
use reqwest::Method;
use serde::{Deserialize, Serialize};
use serde_enum_str::{Deserialize_enum_str, Serialize_enum_str};
use time::OffsetDateTime;
use crate::client::taxes::{TaxId, TaxIdRequest};
use crate::client::Client;
use crate::config::ListParams;
use crate::error::Error;
use crate::serde::Empty;
use crate::util::StrIteratorExt;
const CUSTOMERS_PATH: [&str; 1] = ["customers"];
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
pub enum CustomerId<'a> {
#[serde(rename = "customer_id")]
Orb(&'a str),
#[serde(rename = "external_customer_id")]
External(&'a str),
}
impl<'a> Default for CustomerId<'a> {
fn default() -> CustomerId<'a> {
CustomerId::Orb("")
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize)]
pub struct CreateCustomerRequest<'a> {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "external_customer_id")]
pub external_id: Option<&'a str>,
pub name: &'a str,
pub email: &'a str,
#[serde(skip_serializing_if = "Option::is_none")]
pub timezone: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(flatten)]
pub payment_provider: Option<CustomerPaymentProviderRequest<'a>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub shipping_address: Option<AddressRequest<'a>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub billing_address: Option<AddressRequest<'a>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub currency: Option<CurrencyCode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tax_id: Option<TaxIdRequest<'a>>,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize)]
pub struct UpdateCustomerRequest<'a> {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(flatten)]
pub payment_provider: Option<CustomerPaymentProviderRequest<'a>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub shipping_address: Option<AddressRequest<'a>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub billing_address: Option<AddressRequest<'a>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tax_id: Option<TaxIdRequest<'a>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
pub struct CustomerPaymentProviderRequest<'a> {
#[serde(rename = "payment_provider")]
pub kind: PaymentProvider,
#[serde(rename = "payment_provider_id")]
pub id: &'a str,
}
#[allow(clippy::large_enum_variant)]
#[derive(Deserialize)]
#[serde(untagged)]
pub(crate) enum CustomerResponse {
Normal(Customer),
Deleted { id: String, deleted: bool },
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)]
pub struct Customer {
pub id: String,
#[serde(rename = "external_customer_id")]
pub external_id: Option<String>,
pub name: String,
pub email: String,
pub timezone: String,
pub payment_provider_id: Option<String>,
pub payment_provider: Option<PaymentProvider>,
pub shipping_address: Option<Address>,
pub billing_address: Option<Address>,
pub currency: Option<CurrencyCode>,
pub tax_id: Option<TaxId>,
pub auto_collection: bool,
pub balance: String,
#[serde(with = "time::serde::rfc3339")]
pub created_at: OffsetDateTime,
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize_enum_str, Serialize_enum_str)]
pub enum PaymentProvider {
#[serde(rename = "quickbooks")]
Quickbooks,
#[serde(rename = "bill.com")]
BillDotCom,
#[serde(rename = "stripe")]
Stripe,
#[serde(rename = "stripe_charge")]
StripeCharge,
#[serde(rename = "stripe_invoice")]
StripeInvoice,
#[serde(other)]
Other(String),
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct AddressRequest<'a> {
#[serde(skip_serializing_if = "Option::is_none")]
pub city: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub country: Option<CountryCode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub line1: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub line2: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub postal_code: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub state: Option<&'a str>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct Address {
pub city: Option<String>,
pub country: Option<CountryCode>,
pub line1: Option<String>,
pub line2: Option<String>,
pub postal_code: Option<String>,
pub state: Option<String>,
}
impl Client {
pub fn list_customers(
&self,
params: &ListParams,
) -> impl Stream<Item = Result<Customer, Error>> + '_ {
let req = self.build_request(Method::GET, CUSTOMERS_PATH);
self.stream_paginated_request(params, req)
.try_filter_map(|res| async {
match res {
CustomerResponse::Normal(c) => Ok(Some(c)),
CustomerResponse::Deleted {
id: _,
deleted: true,
} => Ok(None),
CustomerResponse::Deleted { id, deleted: false } => {
Err(Error::UnexpectedResponse {
detail: format!(
"customer {id} used deleted response shape \
but deleted field was `false`"
),
})
}
}
})
}
pub async fn create_customer(
&self,
customer: &CreateCustomerRequest<'_>,
) -> Result<Customer, Error> {
let req = self.build_request(Method::POST, CUSTOMERS_PATH);
let req = req.json(customer);
let res = self.send_request(req).await?;
Ok(res)
}
pub async fn get_customer(&self, id: &str) -> Result<Customer, Error> {
let req = self.build_request(Method::GET, CUSTOMERS_PATH.chain_one(id));
let res = self.send_request(req).await?;
Ok(res)
}
pub async fn get_customer_by_external_id(&self, external_id: &str) -> Result<Customer, Error> {
let req = self.build_request(
Method::GET,
CUSTOMERS_PATH
.chain_one("external_customer_id")
.chain_one(external_id),
);
let res = self.send_request(req).await?;
Ok(res)
}
pub async fn update_customer(
&self,
id: &str,
customer: &UpdateCustomerRequest<'_>,
) -> Result<Customer, Error> {
let req = self.build_request(Method::PUT, CUSTOMERS_PATH.chain_one(id));
let req = req.json(customer);
let res = self.send_request(req).await?;
Ok(res)
}
pub async fn update_customer_by_external_id(
&self,
external_id: &str,
customer: &UpdateCustomerRequest<'_>,
) -> Result<Customer, Error> {
let req = self.build_request(
Method::PUT,
CUSTOMERS_PATH
.chain_one("external_customer_id")
.chain_one(external_id),
);
let req = req.json(customer);
let res = self.send_request(req).await?;
Ok(res)
}
pub async fn delete_customer(&self, id: &str) -> Result<(), Error> {
let req = self.build_request(Method::DELETE, CUSTOMERS_PATH.chain_one(id));
let _: Empty = self.send_request(req).await?;
Ok(())
}
}