Compare commits

..

1 Commits

Author SHA1 Message Date
userAdityaa 0775f867e8 feat: open payment modal immediately on relay plan upgrade 2026-05-07 19:34:13 +05:45
6 changed files with 14 additions and 51 deletions
+2 -8
View File
@@ -3,7 +3,7 @@ use std::sync::Arc;
use anyhow::{Result, anyhow}; use anyhow::{Result, anyhow};
use axum::{ use axum::{
Json, Router, Json, Router,
extract::{Path, Query as QueryParams, State}, extract::{Path, State},
http::{HeaderMap, StatusCode}, http::{HeaderMap, StatusCode},
response::{IntoResponse, Response}, response::{IntoResponse, Response},
routing::{get, post}, routing::{get, post},
@@ -1101,16 +1101,10 @@ async fn get_invoice_bolt11(
} }
} }
#[derive(serde::Deserialize)]
struct StripeSessionParams {
return_url: Option<String>,
}
async fn create_stripe_session( async fn create_stripe_session(
State(state): State<AppState>, State(state): State<AppState>,
headers: HeaderMap, headers: HeaderMap,
Path(pubkey): Path<String>, Path(pubkey): Path<String>,
QueryParams(params): QueryParams<StripeSessionParams>,
) -> std::result::Result<Response, ApiError> { ) -> std::result::Result<Response, ApiError> {
let auth = state.api.extract_auth_pubkey(&headers)?; let auth = state.api.extract_auth_pubkey(&headers)?;
state.api.require_admin_or_tenant(&auth, &pubkey)?; state.api.require_admin_or_tenant(&auth, &pubkey)?;
@@ -1119,7 +1113,7 @@ async fn create_stripe_session(
match state match state
.api .api
.billing .billing
.stripe_create_portal_session(&tenant.stripe_customer_id, params.return_url.as_deref()) .stripe_create_portal_session(&tenant.stripe_customer_id)
.await .await
{ {
Ok(url) => Ok(ok(StatusCode::OK, serde_json::json!({ "url": url }))), Ok(url) => Ok(ok(StatusCode::OK, serde_json::json!({ "url": url }))),
+8 -19
View File
@@ -151,11 +151,7 @@ impl Billing {
return Ok(()); return Ok(());
} }
tracing::info!( tracing::info!(source, relay_count = relays.len(), "reconciling relay billing state");
source,
relay_count = relays.len(),
"reconciling relay billing state"
);
for relay in relays { for relay in relays {
if let Err(error) = self.sync_relay_subscription_for_relay(&relay).await { if let Err(error) = self.sync_relay_subscription_for_relay(&relay).await {
@@ -769,9 +765,8 @@ impl Billing {
} }
pub async fn stripe_create_customer(&self, tenant_pubkey: &str) -> Result<String> { pub async fn stripe_create_customer(&self, tenant_pubkey: &str) -> Result<String> {
let short_pubkey: String = tenant_pubkey.chars().take(8).collect(); let short_pubkey: String = tenant_pubkey.chars().take(12).collect();
let nostr_name = self.robot.fetch_nostr_name(tenant_pubkey).await; let display_name = format!("Caravel tenant {short_pubkey}");
let display_name = nostr_name.unwrap_or_else(|| short_pubkey.clone());
let idempotency_key = self.idempotency_key(&["create_customer", tenant_pubkey]); let idempotency_key = self.idempotency_key(&["create_customer", tenant_pubkey]);
let resp = self let resp = self
@@ -966,20 +961,12 @@ impl Billing {
Ok(()) Ok(())
} }
pub async fn stripe_create_portal_session( pub async fn stripe_create_portal_session(&self, customer_id: &str) -> Result<String> {
&self,
customer_id: &str,
return_url: Option<&str>,
) -> Result<String> {
let mut params = vec![("customer", customer_id.to_string())];
if let Some(url) = return_url {
params.push(("return_url", url.to_string()));
}
let resp = self let resp = self
.http .http
.post(format!("{STRIPE_API}/billing_portal/sessions")) .post(format!("{STRIPE_API}/billing_portal/sessions"))
.bearer_auth(&self.stripe_secret_key) .bearer_auth(&self.stripe_secret_key)
.form(&params) .form(&[("customer", customer_id)])
.send() .send()
.await?; .await?;
@@ -1011,7 +998,8 @@ impl Billing {
customer_id: &str, customer_id: &str,
price_id: &str, price_id: &str,
) -> Result<(String, String)> { ) -> Result<(String, String)> {
let idempotency_key = self.idempotency_key(&["create_subscription", customer_id, price_id]); let idempotency_key =
self.idempotency_key(&["create_subscription", customer_id, price_id]);
let resp = self let resp = self
.http .http
.post(format!("{STRIPE_API}/subscriptions")) .post(format!("{STRIPE_API}/subscriptions"))
@@ -1738,4 +1726,5 @@ mod tests {
assert_eq!(billing.stripe_secret_key, "sk_test_dummy"); assert_eq!(billing.stripe_secret_key, "sk_test_dummy");
assert_eq!(billing.stripe_webhook_secret, "whsec_test_dummy"); assert_eq!(billing.stripe_webhook_secret, "whsec_test_dummy");
} }
} }
-19
View File
@@ -160,25 +160,6 @@ impl Robot {
Ok(relays) Ok(relays)
} }
pub async fn fetch_nostr_name(&self, pubkey: &str) -> Option<String> {
let pubkey = PublicKey::parse(pubkey).ok()?;
let filter = Filter::new().author(pubkey).kind(Kind::Metadata).limit(1);
let events = self
.indexer_client
.fetch_events(filter, Duration::from_secs(5))
.await
.ok()?;
let event = events.into_iter().max_by_key(|e| e.created_at)?;
let content: serde_json::Value = serde_json::from_str(&event.content).ok()?;
let name = content
.get("display_name")
.or_else(|| content.get("name"))
.and_then(|v| v.as_str())
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())?;
Some(name)
}
async fn fetch_messaging_relays_from_outbox( async fn fetch_messaging_relays_from_outbox(
&self, &self,
recipient: &str, recipient: &str,
+1 -1
View File
@@ -40,7 +40,7 @@ export default function PaymentSetup(props: PaymentSetupProps) {
setRedirecting(true) setRedirecting(true)
setError("") setError("")
try { try {
const { url } = await createPortalSession(account()!.pubkey, window.location.href) const { url } = await createPortalSession(account()!.pubkey)
window.location.href = url window.location.href = url
} catch (e) { } catch (e) {
setError(e instanceof Error ? e.message : "Failed to open billing portal") setError(e instanceof Error ? e.message : "Failed to open billing portal")
+2 -3
View File
@@ -253,9 +253,8 @@ export function reactivateRelay(id: string) {
return callApi<undefined, void>("POST", `/relays/${id}/reactivate`) return callApi<undefined, void>("POST", `/relays/${id}/reactivate`)
} }
export function createPortalSession(pubkey: string, returnUrl?: string) { export function createPortalSession(pubkey: string) {
const query = returnUrl ? `?return_url=${encodeURIComponent(returnUrl)}` : "" return callApi<undefined, { url: string }>("GET", `/tenants/${pubkey}/stripe/session`)
return callApi<undefined, { url: string }>("GET", `/tenants/${pubkey}/stripe/session${query}`)
} }
export function getInvoiceBolt11(invoiceId: string) { export function getInvoiceBolt11(invoiceId: string) {
+1 -1
View File
@@ -49,7 +49,7 @@ export default function Account() {
async function openPortal() { async function openPortal() {
setPortalLoading(true) setPortalLoading(true)
try { try {
const { url } = await createPortalSession(account()!.pubkey, window.location.href) const { url } = await createPortalSession(account()!.pubkey)
window.location.href = url window.location.href = url
} catch (e) { } catch (e) {
setError(e instanceof Error ? e.message : "Failed to open billing portal") setError(e instanceof Error ? e.message : "Failed to open billing portal")