feat: plan+billing (#5518)

* feat: billing client

* feat: subscribe workspace default impl

* feat: added create subscription

* feat: add get workspace subs

* feat: added subscription cancellation

* feat: add workspace limits api

* fix: update client api

* feat: user billing portal

* feat: billing UI (#5455)

* feat: plan ui

* feat: billing ui

* feat: settings plan comparison dialog

* feat: complete plan+billing ui

* feat: backend integration

* chore: cleaning

* chore: fixes after merge

* fix: dependency issue

* feat: added subscription plan cancellation information

* feat: subscription callback + canceled date

* feat: put behind feature flag

* feat: downgrade/upgrade dialogs

* feat: update limit error codes

* fix: billing refresh + downgrade dialog

* fix: some minor improvements to settings

* chore: use patch for client-api in tauri

* fix: add shared-entity to patch

* fix: compile

* ci: try to add back maximize build space step

* test: increase timeout in failing test

---------

Co-authored-by: Zack Fu Zi Xiang <speed2exe@live.com.sg>
This commit is contained in:
Mathias Mogensen
2024-06-12 17:08:55 +02:00
committed by GitHub
parent 3d7a500550
commit 4708c0f779
52 changed files with 2769 additions and 83 deletions

View File

@ -1,7 +1,11 @@
use appflowy_cloud_billing_client::entities::{
RecurringInterval, SubscriptionPlan, WorkspaceSubscriptionStatus,
};
use std::collections::HashMap;
use std::sync::Arc;
use anyhow::anyhow;
use appflowy_cloud_billing_client::BillingClient;
use client_api::entity::workspace_dto::{
CreateWorkspaceMember, CreateWorkspaceParam, PatchWorkspaceParam, WorkspaceMemberChangeset,
WorkspaceMemberInvitation,
@ -20,6 +24,7 @@ use flowy_user_pub::cloud::{UserCloudService, UserCollabParams, UserUpdate, User
use flowy_user_pub::entities::{
AFCloudOAuthParams, AuthResponse, Role, UpdateUserProfileParams, UserCredentials, UserProfile,
UserWorkspace, WorkspaceInvitation, WorkspaceInvitationStatus, WorkspaceMember,
WorkspaceSubscription, WorkspaceUsage,
};
use lib_infra::box_any::BoxAny;
use lib_infra::future::FutureResult;
@ -473,6 +478,82 @@ where
Ok(())
})
}
fn subscribe_workspace(
&self,
workspace_id: String,
recurring_interval: flowy_user_pub::entities::RecurringInterval,
workspace_subscription_plan: flowy_user_pub::entities::SubscriptionPlan,
success_url: String,
) -> FutureResult<String, FlowyError> {
let try_get_client = self.server.try_get_client();
let workspace_id = workspace_id.to_string();
FutureResult::new(async move {
let subscription_plan = to_workspace_subscription_plan(workspace_subscription_plan)?;
let client = try_get_client?;
let payment_link = BillingClient::from(client.as_ref())
.create_subscription(
&workspace_id,
to_recurring_interval(recurring_interval),
subscription_plan,
&success_url,
)
.await?;
Ok(payment_link)
})
}
fn get_workspace_subscriptions(&self) -> FutureResult<Vec<WorkspaceSubscription>, FlowyError> {
let try_get_client = self.server.try_get_client();
FutureResult::new(async move {
let client = try_get_client?;
let workspace_subscriptions = BillingClient::from(client.as_ref())
.list_subscription()
.await?
.into_iter()
.map(to_workspace_subscription)
.collect();
Ok(workspace_subscriptions)
})
}
fn cancel_workspace_subscription(&self, workspace_id: String) -> FutureResult<(), FlowyError> {
let try_get_client = self.server.try_get_client();
FutureResult::new(async move {
let client = try_get_client?;
BillingClient::from(client.as_ref())
.cancel_subscription(&workspace_id)
.await?;
Ok(())
})
}
fn get_workspace_usage(&self, workspace_id: String) -> FutureResult<WorkspaceUsage, FlowyError> {
let try_get_client = self.server.try_get_client();
FutureResult::new(async move {
let client = try_get_client?;
let usage = BillingClient::from(client.as_ref())
.get_workspace_usage(&workspace_id)
.await?;
Ok(WorkspaceUsage {
member_count: usage.member_count,
member_count_limit: usage.member_count_limit,
total_blob_bytes: usage.total_blob_bytes,
total_blob_bytes_limit: usage.total_blob_bytes_limit,
})
})
}
fn get_billing_portal_url(&self) -> FutureResult<String, FlowyError> {
let try_get_client = self.server.try_get_client();
FutureResult::new(async move {
let client = try_get_client?;
let url = BillingClient::from(client.as_ref())
.get_portal_session_link()
.await?;
Ok(url)
})
}
}
async fn get_admin_client(client: &Arc<AFCloudClient>) -> FlowyResult<Client> {
@ -569,3 +650,47 @@ fn oauth_params_from_box_any(any: BoxAny) -> Result<AFCloudOAuthParams, FlowyErr
sign_in_url: sign_in_url.to_string(),
})
}
fn to_recurring_interval(r: flowy_user_pub::entities::RecurringInterval) -> RecurringInterval {
match r {
flowy_user_pub::entities::RecurringInterval::Month => RecurringInterval::Month,
flowy_user_pub::entities::RecurringInterval::Year => RecurringInterval::Year,
}
}
fn to_workspace_subscription_plan(
s: flowy_user_pub::entities::SubscriptionPlan,
) -> Result<SubscriptionPlan, FlowyError> {
match s {
flowy_user_pub::entities::SubscriptionPlan::Pro => Ok(SubscriptionPlan::Pro),
flowy_user_pub::entities::SubscriptionPlan::Team => Ok(SubscriptionPlan::Team),
flowy_user_pub::entities::SubscriptionPlan::None => Err(FlowyError::new(
ErrorCode::InvalidParams,
"Invalid subscription plan",
)),
}
}
fn to_workspace_subscription(s: WorkspaceSubscriptionStatus) -> WorkspaceSubscription {
WorkspaceSubscription {
workspace_id: s.workspace_id,
subscription_plan: match s.workspace_plan {
appflowy_cloud_billing_client::entities::WorkspaceSubscriptionPlan::Pro => {
flowy_user_pub::entities::SubscriptionPlan::Pro
},
appflowy_cloud_billing_client::entities::WorkspaceSubscriptionPlan::Team => {
flowy_user_pub::entities::SubscriptionPlan::Team
},
_ => flowy_user_pub::entities::SubscriptionPlan::None,
},
recurring_interval: match s.recurring_interval {
RecurringInterval::Month => flowy_user_pub::entities::RecurringInterval::Month,
RecurringInterval::Year => flowy_user_pub::entities::RecurringInterval::Year,
},
is_active: matches!(
s.subscription_status,
appflowy_cloud_billing_client::entities::SubscriptionStatus::Active
),
canceled_at: s.canceled_at,
}
}