diff --git a/backend/spec/api.md b/backend/spec/api.md index 6b5a11d..f18ffd9 100644 --- a/backend/spec/api.md +++ b/backend/spec/api.md @@ -178,7 +178,7 @@ Handlers take `State>`, an optional `AuthedPubkey`, then path/query/bod - Admin or matching tenant - Looks up the tenant, then lists invoices from Stripe by `stripe_customer_id` -- `data` is a list of `StripeInvoice` objects: `{ id, customer, status, amount_due, currency }` +- `data` is a list of `StripeInvoice` objects: `{ id, customer, status, amount_due, currency, period_start, period_end }` ## `get_invoice` — `GET /invoices/:id` diff --git a/backend/spec/stripe.md b/backend/spec/stripe.md index 05b8158..9660c09 100644 --- a/backend/spec/stripe.md +++ b/backend/spec/stripe.md @@ -95,4 +95,4 @@ Verifies the `Stripe-Signature` header against `env.stripe_webhook_secret` and p - `StripeSubscription { id, status, items: Vec }` (`items` flattened from Stripe's `{ data: [...] }` list) - `StripeSubscriptionItem { id, price: StripePrice, quantity }` (`quantity` defaults to 1 when absent) - `StripePrice { id }` -- `StripeInvoice { id, customer, status, amount_due, currency }` (the subset of invoice fields the API surfaces; `Serialize` + `Clone`) +- `StripeInvoice { id, customer, status, amount_due, currency, period_start, period_end }` (the subset of invoice fields the API surfaces; `Serialize` + `Clone`) diff --git a/backend/src/stripe.rs b/backend/src/stripe.rs index 3591525..e107efe 100644 --- a/backend/src/stripe.rs +++ b/backend/src/stripe.rs @@ -59,6 +59,8 @@ pub struct StripeInvoice { pub status: String, pub amount_due: i64, pub currency: String, + pub period_start: i64, + pub period_end: i64, } #[derive(serde::Deserialize)] diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index db1f847..cc9957c 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -97,7 +97,7 @@ export type UpdateRelayInput = { export type Tenant = { pubkey: string - nwc_url: string + nwc_is_set: boolean created_at: number stripe_customer_id: string stripe_subscription_id: string | null @@ -107,10 +107,10 @@ export type Tenant = { export type Invoice = { id: string + customer: string status: string amount_due: number currency: string - hosted_invoice_url: string period_start: number period_end: number } diff --git a/frontend/src/lib/hooks.ts b/frontend/src/lib/hooks.ts index 87e8c81..abda743 100644 --- a/frontend/src/lib/hooks.ts +++ b/frontend/src/lib/hooks.ts @@ -135,7 +135,7 @@ export const reactivateRelayById = (id: string) => reactivateRelay(id) export async function tenantNeedsPaymentSetup(): Promise { const tenant = await getTenant(account()!.pubkey) - return !tenant.nwc_url && !tenant.stripe_subscription_id + return !tenant.nwc_is_set && !tenant.stripe_subscription_id } export async function getLatestOpenInvoice(): Promise { diff --git a/frontend/src/pages/Account.tsx b/frontend/src/pages/Account.tsx index 76610ef..6815a26 100644 --- a/frontend/src/pages/Account.tsx +++ b/frontend/src/pages/Account.tsx @@ -1,4 +1,4 @@ -import { createEffect, createMemo, createResource, createSignal, For, Show } from "solid-js" +import { createMemo, createResource, createSignal, For, Show } from "solid-js" import PageContainer from "@/components/PageContainer" import LoadingState from "@/components/LoadingState" import PaymentDialog from "@/components/PaymentDialog" @@ -17,15 +17,9 @@ export default function Account() { const [portalLoading, setPortalLoading] = createSignal(false) const invoicesLoading = useMinLoading(() => invoices.loading) - const hasBillingChanges = createMemo(() => { - const current = tenant()?.nwc_url?.trim() ?? "" - const next = nwcUrl().trim() - return current !== next - }) - - createEffect(() => { - setNwcUrl(tenant()?.nwc_url ?? "") - }) + // The backend never returns the stored nwc_url (it's private), so the input is + // write-only: we can only act on a newly entered URL, not prefill the saved one. + const hasBillingChanges = createMemo(() => nwcUrl().trim().length > 0) async function saveBilling() { setError("") @@ -33,6 +27,7 @@ export default function Account() { try { const next = nwcUrl().trim() await updateActiveTenant({ nwc_url: next }) + setNwcUrl("") await refetchTenant() } catch (e) { setError(e instanceof Error ? e.message : "Failed to update billing") @@ -111,6 +106,9 @@ export default function Account() {

Enable automatic payments by providing your Nostr Wallet Connect URL.

+ +

A wallet is connected. Enter a new URL to replace it.

+