collab: Add GET /billing/subscriptions
endpoint (#15516)
This PR adds a new `GET /billing/subscriptions` endpoint to collab for retrieving the subscriptions to display on the account settings page. Release Notes: - N/A
This commit is contained in:
parent
a08e020e32
commit
2b019ff9e2
2 changed files with 73 additions and 5 deletions
|
@ -3,7 +3,11 @@ use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Context};
|
use anyhow::{anyhow, bail, Context};
|
||||||
use axum::{extract, routing::post, Extension, Json, Router};
|
use axum::{
|
||||||
|
extract::{self, Query},
|
||||||
|
routing::{get, post},
|
||||||
|
Extension, Json, Router,
|
||||||
|
};
|
||||||
use reqwest::StatusCode;
|
use reqwest::StatusCode;
|
||||||
use sea_orm::ActiveValue;
|
use sea_orm::ActiveValue;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -27,13 +31,60 @@ use crate::{AppState, Error, Result};
|
||||||
|
|
||||||
pub fn router() -> Router {
|
pub fn router() -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/billing/subscriptions", post(create_billing_subscription))
|
.route(
|
||||||
|
"/billing/subscriptions",
|
||||||
|
get(list_billing_subscriptions).post(create_billing_subscription),
|
||||||
|
)
|
||||||
.route(
|
.route(
|
||||||
"/billing/subscriptions/manage",
|
"/billing/subscriptions/manage",
|
||||||
post(manage_billing_subscription),
|
post(manage_billing_subscription),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct ListBillingSubscriptionsParams {
|
||||||
|
github_user_id: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct BillingSubscriptionJson {
|
||||||
|
id: BillingSubscriptionId,
|
||||||
|
name: String,
|
||||||
|
status: StripeSubscriptionStatus,
|
||||||
|
/// Whether this subscription can be canceled.
|
||||||
|
is_cancelable: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct ListBillingSubscriptionsResponse {
|
||||||
|
subscriptions: Vec<BillingSubscriptionJson>,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list_billing_subscriptions(
|
||||||
|
Extension(app): Extension<Arc<AppState>>,
|
||||||
|
Query(params): Query<ListBillingSubscriptionsParams>,
|
||||||
|
) -> Result<Json<ListBillingSubscriptionsResponse>> {
|
||||||
|
let user = app
|
||||||
|
.db
|
||||||
|
.get_user_by_github_user_id(params.github_user_id)
|
||||||
|
.await?
|
||||||
|
.ok_or_else(|| anyhow!("user not found"))?;
|
||||||
|
|
||||||
|
let subscriptions = app.db.get_billing_subscriptions(user.id).await?;
|
||||||
|
|
||||||
|
Ok(Json(ListBillingSubscriptionsResponse {
|
||||||
|
subscriptions: subscriptions
|
||||||
|
.into_iter()
|
||||||
|
.map(|subscription| BillingSubscriptionJson {
|
||||||
|
id: subscription.id,
|
||||||
|
name: "Zed Pro".to_string(),
|
||||||
|
status: subscription.stripe_subscription_status,
|
||||||
|
is_cancelable: subscription.stripe_subscription_status.is_cancelable(),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct CreateBillingSubscriptionBody {
|
struct CreateBillingSubscriptionBody {
|
||||||
github_user_id: i32,
|
github_user_id: i32,
|
||||||
|
@ -179,7 +230,7 @@ async fn manage_billing_subscription(
|
||||||
after_completion: Some(CreateBillingPortalSessionFlowDataAfterCompletion {
|
after_completion: Some(CreateBillingPortalSessionFlowDataAfterCompletion {
|
||||||
type_: stripe::CreateBillingPortalSessionFlowDataAfterCompletionType::Redirect,
|
type_: stripe::CreateBillingPortalSessionFlowDataAfterCompletionType::Redirect,
|
||||||
redirect: Some(CreateBillingPortalSessionFlowDataAfterCompletionRedirect {
|
redirect: Some(CreateBillingPortalSessionFlowDataAfterCompletionRedirect {
|
||||||
return_url: "https://zed.dev/billing".into(),
|
return_url: "https://zed.dev/settings".into(),
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
|
@ -195,7 +246,7 @@ async fn manage_billing_subscription(
|
||||||
|
|
||||||
let mut params = CreateBillingPortalSession::new(customer_id);
|
let mut params = CreateBillingPortalSession::new(customer_id);
|
||||||
params.flow_data = Some(flow);
|
params.flow_data = Some(flow);
|
||||||
params.return_url = Some("https://zed.dev/billing");
|
params.return_url = Some("https://zed.dev/settings");
|
||||||
|
|
||||||
let session = BillingPortalSession::create(&stripe_client, params).await?;
|
let session = BillingPortalSession::create(&stripe_client, params).await?;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::db::{BillingCustomerId, BillingSubscriptionId};
|
use crate::db::{BillingCustomerId, BillingSubscriptionId};
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
/// A billing subscription.
|
/// A billing subscription.
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, DeriveEntityModel)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq, DeriveEntityModel)]
|
||||||
|
@ -34,8 +35,11 @@ impl ActiveModelBehavior for ActiveModel {}
|
||||||
/// The status of a Stripe subscription.
|
/// The status of a Stripe subscription.
|
||||||
///
|
///
|
||||||
/// [Stripe docs](https://docs.stripe.com/api/subscriptions/object#subscription_object-status)
|
/// [Stripe docs](https://docs.stripe.com/api/subscriptions/object#subscription_object-status)
|
||||||
#[derive(Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum, Default, Hash)]
|
#[derive(
|
||||||
|
Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum, Default, Hash, Serialize,
|
||||||
|
)]
|
||||||
#[sea_orm(rs_type = "String", db_type = "String(None)")]
|
#[sea_orm(rs_type = "String", db_type = "String(None)")]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum StripeSubscriptionStatus {
|
pub enum StripeSubscriptionStatus {
|
||||||
#[default]
|
#[default]
|
||||||
#[sea_orm(string_value = "incomplete")]
|
#[sea_orm(string_value = "incomplete")]
|
||||||
|
@ -55,3 +59,16 @@ pub enum StripeSubscriptionStatus {
|
||||||
#[sea_orm(string_value = "paused")]
|
#[sea_orm(string_value = "paused")]
|
||||||
Paused,
|
Paused,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl StripeSubscriptionStatus {
|
||||||
|
pub fn is_cancelable(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Trialing | Self::Active | Self::PastDue => true,
|
||||||
|
Self::Incomplete
|
||||||
|
| Self::IncompleteExpired
|
||||||
|
| Self::Canceled
|
||||||
|
| Self::Unpaid
|
||||||
|
| Self::Paused => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue