diff --git a/libs/app-error/src/lib.rs b/libs/app-error/src/lib.rs index ae4355b4..100e570e 100644 --- a/libs/app-error/src/lib.rs +++ b/libs/app-error/src/lib.rs @@ -431,6 +431,8 @@ pub enum ErrorCode { ApplyUpdateError = 1056, ActionTimeout = 1057, AIImageResponseLimitExceeded = 1058, + MailerError = 1059, + LicenseError = 1060, } impl ErrorCode { diff --git a/libs/client-api/src/http_billing.rs b/libs/client-api/src/http_billing.rs index cefa53f9..361a5e3b 100644 --- a/libs/client-api/src/http_billing.rs +++ b/libs/client-api/src/http_billing.rs @@ -4,6 +4,10 @@ use client_api_entity::billing_dto::{ SubscriptionPlanDetail, WorkspaceUsageAndLimit, }; use reqwest::Method; +use shared_entity::dto::billing_dto::{ + LicenseProductSubscriptionLinkQuery, LicensedProductDetail, LicensedProductType, + SubscribeProductLicense, UserSubscribeProduct, +}; use shared_entity::{ dto::billing_dto::{RecurringInterval, SubscriptionPlan, WorkspaceSubscriptionStatus}, response::{AppResponse, AppResponseError}, @@ -222,4 +226,96 @@ impl Client { .await? .into_data() } + + /// Return all licensed products. + pub async fn get_license_product_subscriptions( + &self, + ) -> Result, AppResponseError> { + let url = format!( + "{}/billing/api/v1/license/products", + self.base_billing_url(), + ); + let resp = self + .http_client_with_auth(Method::GET, &url) + .await? + .send() + .await?; + + AppResponse::>::from_response(resp) + .await? + .into_data() + } + + /// Returns products that user already subscribed to + pub async fn get_user_license_products(&self) -> Result { + let url = format!( + "{}/billing/api/v1/license/user/products", + self.base_billing_url(), + ); + let resp = self + .http_client_with_auth(Method::GET, &url) + .await? + .send() + .await?; + + AppResponse::::from_response(resp) + .await? + .into_data() + } + + pub async fn get_license_product_detail( + &self, + product_id: &str, + ) -> Result, AppResponseError> { + let url = format!( + "{}/billing/api/v1/license/user/product/{}", + self.base_billing_url(), + product_id + ); + let resp = self + .http_client_with_auth(Method::GET, &url) + .await? + .send() + .await?; + + AppResponse::>::from_response(resp) + .await? + .into_data() + } + + pub async fn active_license_product(&self, product_id: &str) -> Result<(), AppResponseError> { + let url = format!( + "{}/billing/api/v1/license/user/product/{}/active", + self.base_billing_url(), + product_id + ); + let resp = self + .http_client_without_auth(Method::POST, &url) + .await? + .send() + .await?; + + AppResponse::<()>::from_response(resp).await?.into_data() + } + + pub async fn get_license_product_subscription_link( + &self, + product_type: LicensedProductType, + ) -> Result { + let query = LicenseProductSubscriptionLinkQuery { product_type }; + let url = format!( + "{}/billing/api/v1/license/subscription-link", + self.base_billing_url(), + ); + let resp = self + .http_client_without_auth(Method::GET, &url) + .await? + .query(&query) + .send() + .await?; + + AppResponse::::from_response(resp) + .await? + .into_data() + } } diff --git a/libs/shared-entity/src/dto/billing_dto.rs b/libs/shared-entity/src/dto/billing_dto.rs index 9794371e..efbd199a 100644 --- a/libs/shared-entity/src/dto/billing_dto.rs +++ b/libs/shared-entity/src/dto/billing_dto.rs @@ -1,4 +1,6 @@ +use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +use std::fmt::Display; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "snake_case")] @@ -171,3 +173,58 @@ pub struct SubscriptionTrialRequest { #[serde(skip_serializing_if = "Option::is_none")] pub period_days: Option, } + +#[derive(Serialize, Deserialize, Debug)] +pub struct LicensedProductDetail { + pub name: String, + pub currency: Currency, + pub price_cents: i64, + pub recurring_interval: RecurringInterval, + pub product_type: LicensedProductType, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct UserSubscribeProduct { + pub email: String, + pub product_ids: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct SubscribeProductLicense { + pub product_id: String, + pub policy_id: String, + pub license_id: String, + pub metadata: serde_json::Value, + pub max_machines: i32, + pub unlimited_devices: bool, + pub expires_at: Option>, +} + +#[derive(Debug, Clone, serde_repr::Serialize_repr, serde_repr::Deserialize_repr)] +#[repr(u8)] +pub enum LicensedProductType { + Unknown = 0, + AppFlowyAI = 1, +} + +impl Display for LicensedProductType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", (self.clone() as u8)) + } +} + +impl TryFrom<&str> for LicensedProductType { + type Error = String; + + fn try_from(value: &str) -> Result { + match value { + "1" => Ok(LicensedProductType::AppFlowyAI), + _ => Err(format!("Invalid LicensedProductType value: {}", value)), + } + } +} + +#[derive(Serialize, Deserialize)] +pub struct LicenseProductSubscriptionLinkQuery { + pub product_type: LicensedProductType, +}