Files
caravel/frontend/src/lib/usePaymentSetup.ts
T
2026-06-03 10:02:43 -07:00

98 lines
3.0 KiB
TypeScript

import { createSignal } from "solid-js"
import { updateActiveTenant } from "@/lib/hooks"
import { createInvoiceCheckout, createPortalSession } from "@/lib/api"
import { account } from "@/lib/state"
// Lightning/NWC save state machine, shared by the combined and focused setup
// dialogs. `onSaved` fires once the wallet URL is persisted.
export function useNwcSetup(onSaved?: () => void) {
const [nwcUrl, setNwcUrl] = createSignal("")
const [saving, setSaving] = createSignal(false)
const [saved, setSaved] = createSignal(false)
const [error, setError] = createSignal("")
async function save() {
const url = nwcUrl().trim()
if (!url) return
setSaving(true)
setError("")
try {
await updateActiveTenant({ nwc_url: url })
setSaved(true)
onSaved?.()
} catch (e) {
setError(e instanceof Error ? e.message : "Failed to save wallet connection")
} finally {
setSaving(false)
}
}
function reset() {
setNwcUrl("")
setSaved(false)
setError("")
}
return { nwcUrl, setNwcUrl, saving, saved, error, save, reset }
}
export type NwcSetup = ReturnType<typeof useNwcSetup>
// Card setup is a full-page redirect to the Stripe billing portal (which returns
// to wherever it was opened from), so there's no local "saved" state — only the
// in-flight redirect and any failure to open the portal.
export function useCardPortal() {
const [redirecting, setRedirecting] = createSignal(false)
const [error, setError] = createSignal("")
async function openPortal() {
setRedirecting(true)
setError("")
try {
const { url } = await createPortalSession(account()!.pubkey, window.location.href)
window.location.href = url
} catch (e) {
setError(e instanceof Error ? e.message : "Failed to open billing portal")
setRedirecting(false)
}
}
function reset() {
setError("")
}
return { redirecting, error, openPortal, reset }
}
export type CardPortal = ReturnType<typeof useCardPortal>
// Paying one specific invoice by card is a full-page redirect to a Stripe
// Checkout session scoped to that invoice (so a 3D Secure challenge can be
// completed) — distinct from the billing-portal redirect that manages the
// recurring card on file. Like the portal, there's no local "saved" state, only
// the in-flight redirect and any failure to open the session.
export function useInvoiceCheckout(invoiceId: () => string) {
const [redirecting, setRedirecting] = createSignal(false)
const [error, setError] = createSignal("")
async function openCheckout() {
setRedirecting(true)
setError("")
try {
const { url } = await createInvoiceCheckout(invoiceId())
window.location.href = url
} catch (e) {
setError(e instanceof Error ? e.message : "Failed to open checkout")
setRedirecting(false)
}
}
function reset() {
setError("")
}
return { redirecting, error, openCheckout, reset }
}
export type InvoiceCheckout = ReturnType<typeof useInvoiceCheckout>