chore: move billing client (#618)
* chore: move billing client * chore: cargo fmt
This commit is contained in:
parent
ee16f428c9
commit
6471831561
|
|
@ -1212,6 +1212,18 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "billing"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"client-api",
|
||||||
|
"reqwest 0.11.27",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"shared-entity",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bincode"
|
name = "bincode"
|
||||||
version = "1.3.3"
|
version = "1.3.3"
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,7 @@ members = [
|
||||||
# xtask
|
# xtask
|
||||||
"xtask",
|
"xtask",
|
||||||
"libs/tonic-proto",
|
"libs/tonic-proto",
|
||||||
|
"libs/billing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "billing"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
client-api = { path = "../client-api" }
|
||||||
|
shared-entity = { path = "../shared-entity" }
|
||||||
|
|
||||||
|
reqwest = { workspace = true }
|
||||||
|
serde = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
tokio = { workspace = true }
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum RecurringInterval {
|
||||||
|
Month,
|
||||||
|
Year,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RecurringInterval {
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
RecurringInterval::Month => "month",
|
||||||
|
RecurringInterval::Year => "year",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum SubscriptionPlan {
|
||||||
|
Pro,
|
||||||
|
Team,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SubscriptionPlan {
|
||||||
|
pub fn as_str(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
SubscriptionPlan::Pro => "pro",
|
||||||
|
SubscriptionPlan::Team => "team",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
#[repr(i16)]
|
||||||
|
pub enum WorkspaceSubscriptionPlan {
|
||||||
|
Unknown = -1,
|
||||||
|
|
||||||
|
Free = 0,
|
||||||
|
Pro = 1,
|
||||||
|
Team = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum SubscriptionStatus {
|
||||||
|
Active,
|
||||||
|
Canceled,
|
||||||
|
Incomplete,
|
||||||
|
IncompleteExpired,
|
||||||
|
PastDue,
|
||||||
|
Paused,
|
||||||
|
Trialing,
|
||||||
|
Unpaid,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct WorkspaceSubscriptionStatus {
|
||||||
|
pub workspace_id: String,
|
||||||
|
pub workspace_plan: WorkspaceSubscriptionPlan,
|
||||||
|
pub recurring_interval: RecurringInterval,
|
||||||
|
pub subscription_status: SubscriptionStatus,
|
||||||
|
pub subscription_quantity: u64,
|
||||||
|
pub canceled_at: Option<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct WorkspaceUsage {
|
||||||
|
pub member_count: usize,
|
||||||
|
pub member_count_limit: usize,
|
||||||
|
pub total_blob_bytes: usize,
|
||||||
|
pub total_blob_bytes_limit: usize,
|
||||||
|
// TODO(AI):
|
||||||
|
// pub ai_responses: String,
|
||||||
|
// pub ai_responses_limit: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct WorkspaceUsageLimit {
|
||||||
|
pub total_blob_size: usize,
|
||||||
|
pub single_blob_size: usize,
|
||||||
|
pub member_count: usize,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,160 @@
|
||||||
|
pub mod entities;
|
||||||
|
use crate::entities::WorkspaceUsageLimit;
|
||||||
|
use client_api::error::AppResponseError;
|
||||||
|
use entities::{RecurringInterval, SubscriptionPlan, WorkspaceSubscriptionStatus, WorkspaceUsage};
|
||||||
|
use reqwest::Method;
|
||||||
|
use serde_json::json;
|
||||||
|
use shared_entity::response::AppResponse;
|
||||||
|
|
||||||
|
pub struct BillingClient<'a> {
|
||||||
|
billing_base_url: String,
|
||||||
|
client: &'a client_api::Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a client_api::Client> for BillingClient<'a> {
|
||||||
|
fn from(client: &'a client_api::Client) -> Self {
|
||||||
|
Self {
|
||||||
|
billing_base_url: client.base_url.clone(),
|
||||||
|
client,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BillingClient<'_> {
|
||||||
|
pub fn set_billing_base_url(&mut self, billing_base_url: String) {
|
||||||
|
self.billing_base_url = billing_base_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn customer_id(&self) -> Result<String, AppResponseError> {
|
||||||
|
let url = format!("{}/billing/api/v1/customer-id", &self.billing_base_url,);
|
||||||
|
|
||||||
|
let resp = self
|
||||||
|
.client
|
||||||
|
.http_client_with_auth(Method::GET, &url)
|
||||||
|
.await?
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
AppResponse::<String>::from_response(resp)
|
||||||
|
.await?
|
||||||
|
.into_data()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_subscription(
|
||||||
|
&self,
|
||||||
|
workspace_id: &str,
|
||||||
|
recurring_interval: RecurringInterval,
|
||||||
|
workspace_subscription_plan: SubscriptionPlan,
|
||||||
|
success_url: &str,
|
||||||
|
) -> Result<String, AppResponseError> {
|
||||||
|
let url = format!(
|
||||||
|
"{}/billing/api/v1/subscription-link",
|
||||||
|
&self.billing_base_url,
|
||||||
|
);
|
||||||
|
|
||||||
|
let resp = self
|
||||||
|
.client
|
||||||
|
.http_client_with_auth(Method::GET, &url)
|
||||||
|
.await?
|
||||||
|
.query(&[
|
||||||
|
("workspace_id", workspace_id),
|
||||||
|
("recurring_interval", recurring_interval.as_str()),
|
||||||
|
(
|
||||||
|
"workspace_subscription_plan",
|
||||||
|
&workspace_subscription_plan.as_str(),
|
||||||
|
),
|
||||||
|
("success_url", success_url),
|
||||||
|
])
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
AppResponse::<String>::from_response(resp)
|
||||||
|
.await?
|
||||||
|
.into_data()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn cancel_subscription(&self, workspace_id: &str) -> Result<(), AppResponseError> {
|
||||||
|
let url = format!(
|
||||||
|
"{}/billing/api/v1/cancel-subscription",
|
||||||
|
&self.billing_base_url,
|
||||||
|
);
|
||||||
|
let resp = self
|
||||||
|
.client
|
||||||
|
.http_client_with_auth(Method::POST, &url)
|
||||||
|
.await?
|
||||||
|
.json(&json!({ "workspace_id": workspace_id }))
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
AppResponse::<()>::from_response(resp).await?.into_error()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list_subscription(
|
||||||
|
&self,
|
||||||
|
) -> Result<Vec<WorkspaceSubscriptionStatus>, AppResponseError> {
|
||||||
|
let url = format!(
|
||||||
|
"{}/billing/api/v1/subscription-status",
|
||||||
|
&self.billing_base_url
|
||||||
|
);
|
||||||
|
let resp = self
|
||||||
|
.client
|
||||||
|
.http_client_with_auth(Method::GET, &url)
|
||||||
|
.await?
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
AppResponse::<Vec<WorkspaceSubscriptionStatus>>::from_response(resp)
|
||||||
|
.await?
|
||||||
|
.into_data()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_workspace_usage(
|
||||||
|
&self,
|
||||||
|
workspace_id: &str,
|
||||||
|
) -> Result<WorkspaceUsage, AppResponseError> {
|
||||||
|
let num_members = self.client.get_workspace_members(workspace_id).await?.len();
|
||||||
|
let limits = get_workspace_limits(self.client, workspace_id).await?;
|
||||||
|
let doc_usage = self.client.get_workspace_usage(workspace_id).await?;
|
||||||
|
|
||||||
|
let workspace_usage = WorkspaceUsage {
|
||||||
|
member_count: num_members,
|
||||||
|
member_count_limit: limits.member_count,
|
||||||
|
total_blob_bytes: doc_usage.consumed_capacity as _,
|
||||||
|
total_blob_bytes_limit: limits.total_blob_size,
|
||||||
|
};
|
||||||
|
Ok(workspace_usage)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_portal_session_link(&self) -> Result<String, AppResponseError> {
|
||||||
|
let url = format!(
|
||||||
|
"{}/billing/api/v1/portal-session-link",
|
||||||
|
&self.billing_base_url,
|
||||||
|
);
|
||||||
|
let portal_url = self
|
||||||
|
.client
|
||||||
|
.http_client_with_auth(Method::GET, &url)
|
||||||
|
.await?
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.error_for_status()?
|
||||||
|
.json::<AppResponse<String>>()
|
||||||
|
.await?
|
||||||
|
.into_data()?;
|
||||||
|
Ok(portal_url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_workspace_limits(
|
||||||
|
client: &client_api::Client,
|
||||||
|
workspace_id: &str,
|
||||||
|
) -> Result<WorkspaceUsageLimit, AppResponseError> {
|
||||||
|
let url = format!("{}/api/workspace/{}/limit", &client.base_url, workspace_id);
|
||||||
|
client
|
||||||
|
.http_client_with_auth(Method::GET, &url)
|
||||||
|
.await?
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.error_for_status()?
|
||||||
|
.json::<AppResponse<WorkspaceUsageLimit>>()
|
||||||
|
.await?
|
||||||
|
.into_data()
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue