forked from coracle/caravel
Sync frontend and backend
This commit is contained in:
+1
-1
@@ -178,7 +178,7 @@ Handlers take `State<Arc<Api>>`, an optional `AuthedPubkey`, then path/query/bod
|
|||||||
|
|
||||||
- Admin or matching tenant
|
- Admin or matching tenant
|
||||||
- Looks up the tenant, then lists invoices from Stripe by `stripe_customer_id`
|
- 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`
|
## `get_invoice` — `GET /invoices/:id`
|
||||||
|
|
||||||
|
|||||||
@@ -95,4 +95,4 @@ Verifies the `Stripe-Signature` header against `env.stripe_webhook_secret` and p
|
|||||||
- `StripeSubscription { id, status, items: Vec<StripeSubscriptionItem> }` (`items` flattened from Stripe's `{ data: [...] }` list)
|
- `StripeSubscription { id, status, items: Vec<StripeSubscriptionItem> }` (`items` flattened from Stripe's `{ data: [...] }` list)
|
||||||
- `StripeSubscriptionItem { id, price: StripePrice, quantity }` (`quantity` defaults to 1 when absent)
|
- `StripeSubscriptionItem { id, price: StripePrice, quantity }` (`quantity` defaults to 1 when absent)
|
||||||
- `StripePrice { id }`
|
- `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`)
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ pub struct StripeInvoice {
|
|||||||
pub status: String,
|
pub status: String,
|
||||||
pub amount_due: i64,
|
pub amount_due: i64,
|
||||||
pub currency: String,
|
pub currency: String,
|
||||||
|
pub period_start: i64,
|
||||||
|
pub period_end: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ export type UpdateRelayInput = {
|
|||||||
|
|
||||||
export type Tenant = {
|
export type Tenant = {
|
||||||
pubkey: string
|
pubkey: string
|
||||||
nwc_url: string
|
nwc_is_set: boolean
|
||||||
created_at: number
|
created_at: number
|
||||||
stripe_customer_id: string
|
stripe_customer_id: string
|
||||||
stripe_subscription_id: string | null
|
stripe_subscription_id: string | null
|
||||||
@@ -107,10 +107,10 @@ export type Tenant = {
|
|||||||
|
|
||||||
export type Invoice = {
|
export type Invoice = {
|
||||||
id: string
|
id: string
|
||||||
|
customer: string
|
||||||
status: string
|
status: string
|
||||||
amount_due: number
|
amount_due: number
|
||||||
currency: string
|
currency: string
|
||||||
hosted_invoice_url: string
|
|
||||||
period_start: number
|
period_start: number
|
||||||
period_end: number
|
period_end: number
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ export const reactivateRelayById = (id: string) => reactivateRelay(id)
|
|||||||
|
|
||||||
export async function tenantNeedsPaymentSetup(): Promise<boolean> {
|
export async function tenantNeedsPaymentSetup(): Promise<boolean> {
|
||||||
const tenant = await getTenant(account()!.pubkey)
|
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<Invoice | null> {
|
export async function getLatestOpenInvoice(): Promise<Invoice | null> {
|
||||||
|
|||||||
@@ -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 PageContainer from "@/components/PageContainer"
|
||||||
import LoadingState from "@/components/LoadingState"
|
import LoadingState from "@/components/LoadingState"
|
||||||
import PaymentDialog from "@/components/PaymentDialog"
|
import PaymentDialog from "@/components/PaymentDialog"
|
||||||
@@ -17,15 +17,9 @@ export default function Account() {
|
|||||||
const [portalLoading, setPortalLoading] = createSignal(false)
|
const [portalLoading, setPortalLoading] = createSignal(false)
|
||||||
const invoicesLoading = useMinLoading(() => invoices.loading)
|
const invoicesLoading = useMinLoading(() => invoices.loading)
|
||||||
|
|
||||||
const hasBillingChanges = createMemo(() => {
|
// The backend never returns the stored nwc_url (it's private), so the input is
|
||||||
const current = tenant()?.nwc_url?.trim() ?? ""
|
// write-only: we can only act on a newly entered URL, not prefill the saved one.
|
||||||
const next = nwcUrl().trim()
|
const hasBillingChanges = createMemo(() => nwcUrl().trim().length > 0)
|
||||||
return current !== next
|
|
||||||
})
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
setNwcUrl(tenant()?.nwc_url ?? "")
|
|
||||||
})
|
|
||||||
|
|
||||||
async function saveBilling() {
|
async function saveBilling() {
|
||||||
setError("")
|
setError("")
|
||||||
@@ -33,6 +27,7 @@ export default function Account() {
|
|||||||
try {
|
try {
|
||||||
const next = nwcUrl().trim()
|
const next = nwcUrl().trim()
|
||||||
await updateActiveTenant({ nwc_url: next })
|
await updateActiveTenant({ nwc_url: next })
|
||||||
|
setNwcUrl("")
|
||||||
await refetchTenant()
|
await refetchTenant()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(e instanceof Error ? e.message : "Failed to update billing")
|
setError(e instanceof Error ? e.message : "Failed to update billing")
|
||||||
@@ -111,6 +106,9 @@ export default function Account() {
|
|||||||
<p class="text-sm text-gray-600 mb-4">
|
<p class="text-sm text-gray-600 mb-4">
|
||||||
Enable automatic payments by providing your Nostr Wallet Connect URL.
|
Enable automatic payments by providing your Nostr Wallet Connect URL.
|
||||||
</p>
|
</p>
|
||||||
|
<Show when={tenant()?.nwc_is_set}>
|
||||||
|
<p class="text-sm text-green-700 mb-4">A wallet is connected. Enter a new URL to replace it.</p>
|
||||||
|
</Show>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export default function RelayDetail() {
|
|||||||
if (!isPaidRelay()) return false
|
if (!isPaidRelay()) return false
|
||||||
const t = tenant()
|
const t = tenant()
|
||||||
if (!t) return false
|
if (!t) return false
|
||||||
return !t.nwc_url
|
return !t.nwc_is_set
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user