Appflowy ai purchase (#1205)

* chore: api endpoint

* chore: clippy

* chore: add new api endpoint

* chore: add auth

* chore: add error code
This commit is contained in:
Nathan.fooo 2025-01-31 10:43:35 +08:00 committed by GitHub
parent 950563dbef
commit fe6d11eb25
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 155 additions and 0 deletions

View File

@ -431,6 +431,8 @@ pub enum ErrorCode {
ApplyUpdateError = 1056,
ActionTimeout = 1057,
AIImageResponseLimitExceeded = 1058,
MailerError = 1059,
LicenseError = 1060,
}
impl ErrorCode {

View File

@ -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<Vec<LicensedProductDetail>, 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::<Vec<LicensedProductDetail>>::from_response(resp)
.await?
.into_data()
}
/// Returns products that user already subscribed to
pub async fn get_user_license_products(&self) -> Result<UserSubscribeProduct, AppResponseError> {
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::<UserSubscribeProduct>::from_response(resp)
.await?
.into_data()
}
pub async fn get_license_product_detail(
&self,
product_id: &str,
) -> Result<Vec<SubscribeProductLicense>, 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::<Vec<SubscribeProductLicense>>::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<String, AppResponseError> {
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::<String>::from_response(resp)
.await?
.into_data()
}
}

View File

@ -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<u32>,
}
#[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<String>,
}
#[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<DateTime<Utc>>,
}
#[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<Self, Self::Error> {
match value {
"1" => Ok(LicensedProductType::AppFlowyAI),
_ => Err(format!("Invalid LicensedProductType value: {}", value)),
}
}
}
#[derive(Serialize, Deserialize)]
pub struct LicenseProductSubscriptionLinkQuery {
pub product_type: LicensedProductType,
}