forked from coracle/caravel
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 41602e21c2 |
+1
-1
@@ -132,7 +132,7 @@ Notes:
|
|||||||
|
|
||||||
- Serves `POST /relays/:id/deactivate`
|
- Serves `POST /relays/:id/deactivate`
|
||||||
- Authorizes admin or relay owner
|
- Authorizes admin or relay owner
|
||||||
- If relay status is `inactive` or `delinquent`, return a `400` with `code=relay-is-inactive`
|
- If relay is already inactive, return a `400` with `code=relay-is-inactive`
|
||||||
- Call `command.deactivate_relay`
|
- Call `command.deactivate_relay`
|
||||||
- Return `data` is empty
|
- Return `data` is empty
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ Manages the Stripe subscription and subscription items for a relay's tenant. Onl
|
|||||||
|
|
||||||
- Fetch the relay and tenant associated with the `activity`
|
- Fetch the relay and tenant associated with the `activity`
|
||||||
- **If relay plan is `free`**: if the relay has a `stripe_subscription_item_id`, delete it via the Stripe API and call `command.delete_relay_subscription_item`. Then check cleanup (below). Return early.
|
- **If relay plan is `free`**: if the relay has a `stripe_subscription_item_id`, delete it via the Stripe API and call `command.delete_relay_subscription_item`. Then check cleanup (below). Return early.
|
||||||
- **If relay is `inactive` or `delinquent`**: if the relay has a `stripe_subscription_item_id`, delete it via the Stripe API and call `command.delete_relay_subscription_item`. Then check cleanup (below). Return early.
|
- **If relay is `inactive`**: if the relay has a `stripe_subscription_item_id`, delete it via the Stripe API and call `command.delete_relay_subscription_item`. Then check cleanup (below). Return early.
|
||||||
- **If relay is `active` and on a paid plan**:
|
- **If relay is `active` and on a paid plan**:
|
||||||
- **Ensure subscription exists**: If the tenant has no `stripe_subscription_id`, create a Stripe subscription for the customer with `collection_method: "charge_automatically"` and the relay's price as the first item. Save the subscription ID via `command.set_tenant_subscription` and the item ID via `command.set_relay_subscription_item`. Return early.
|
- **Ensure subscription exists**: If the tenant has no `stripe_subscription_id`, create a Stripe subscription for the customer with `collection_method: "charge_automatically"` and the relay's price as the first item. Save the subscription ID via `command.set_tenant_subscription` and the item ID via `command.set_relay_subscription_item`. Return early.
|
||||||
- **Sync the subscription item**: If the tenant already has a subscription, create or update the relay's Stripe subscription item to the plan's `stripe_price_id` via the Stripe API, then call `command.set_relay_subscription_item`.
|
- **Sync the subscription item**: If the tenant already has a subscription, create or update the relay's Stripe subscription item to the plan's `stripe_price_id` via the Stripe API, then call `command.set_relay_subscription_item`.
|
||||||
@@ -85,7 +85,7 @@ Skip invoices with `amount_due` of 0.
|
|||||||
- Look up tenant by `stripe_customer_id`
|
- Look up tenant by `stripe_customer_id`
|
||||||
- If tenant has `past_due_at` set:
|
- If tenant has `past_due_at` set:
|
||||||
- Clear `past_due_at` via `command.clear_tenant_past_due`
|
- Clear `past_due_at` via `command.clear_tenant_past_due`
|
||||||
- Find all `delinquent` relays on paid plans for the tenant (relays marked delinquent by the billing system due to non-payment)
|
- Find all `inactive` relays for the tenant that were deactivated due to non-payment (i.e. relays on paid plans that are inactive)
|
||||||
- Reactivate each one via `command.activate_relay`
|
- Reactivate each one via `command.activate_relay`
|
||||||
|
|
||||||
## `fn handle_invoice_payment_failed(&self, invoice: &Invoice)`
|
## `fn handle_invoice_payment_failed(&self, invoice: &Invoice)`
|
||||||
@@ -98,7 +98,7 @@ Skip invoices with `amount_due` of 0.
|
|||||||
## `fn handle_invoice_overdue(&self, invoice: &Invoice)`
|
## `fn handle_invoice_overdue(&self, invoice: &Invoice)`
|
||||||
|
|
||||||
- Look up tenant by `stripe_customer_id`
|
- Look up tenant by `stripe_customer_id`
|
||||||
- Mark all active relays on paid plans as delinquent via `command.mark_relay_delinquent` (sets status to `delinquent`, distinct from user-initiated `deactivate_relay`)
|
- Deactivate all active relays on paid plans via `command.deactivate_relay`
|
||||||
- Send a DM via `robot.send_dm` notifying the tenant that their paid relays have been deactivated due to non-payment
|
- Send a DM via `robot.send_dm` notifying the tenant that their paid relays have been deactivated due to non-payment
|
||||||
|
|
||||||
## `fn handle_subscription_updated(&self, subscription: &Subscription)`
|
## `fn handle_subscription_updated(&self, subscription: &Subscription)`
|
||||||
@@ -106,7 +106,7 @@ Skip invoices with `amount_due` of 0.
|
|||||||
- Look up tenant by `stripe_customer_id`
|
- Look up tenant by `stripe_customer_id`
|
||||||
- If subscription status is `canceled` or `unpaid`:
|
- If subscription status is `canceled` or `unpaid`:
|
||||||
- Clear `stripe_subscription_id` via `command.clear_tenant_subscription`
|
- Clear `stripe_subscription_id` via `command.clear_tenant_subscription`
|
||||||
- Mark all active paid relays as delinquent via `command.mark_relay_delinquent`
|
- Deactivate all active paid relays for the tenant via `command.deactivate_relay`
|
||||||
|
|
||||||
## `fn handle_subscription_deleted(&self, subscription: &Subscription)`
|
## `fn handle_subscription_deleted(&self, subscription: &Subscription)`
|
||||||
|
|
||||||
|
|||||||
@@ -44,14 +44,6 @@ Notes:
|
|||||||
|
|
||||||
- Sets relay status to `inactive`
|
- Sets relay status to `inactive`
|
||||||
- Logs activity as `(deactivate_relay, relay_id)`
|
- Logs activity as `(deactivate_relay, relay_id)`
|
||||||
- Used for user/admin-initiated deactivation only
|
|
||||||
|
|
||||||
## `pub fn mark_relay_delinquent(&self, relay: &Relay) -> Result<()>`
|
|
||||||
|
|
||||||
- Sets relay status to `delinquent`
|
|
||||||
- Logs activity as `(deactivate_relay, relay_id)`
|
|
||||||
- Used exclusively by the billing system when a relay's subscription becomes past due
|
|
||||||
- `delinquent` relays are automatically reactivated via `activate_relay` when payment is received
|
|
||||||
|
|
||||||
## `pub fn activate_relay(&self, relay: &Relay) -> Result<()>`
|
## `pub fn activate_relay(&self, relay: &Relay) -> Result<()>`
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ A relay is a nostr relay owned by a `tenant` and hosted by the attached zooid in
|
|||||||
- `subdomain` - the relay's subdomain
|
- `subdomain` - the relay's subdomain
|
||||||
- `plan` - the relay's plan
|
- `plan` - the relay's plan
|
||||||
- `stripe_subscription_item_id` (nullable) - the Stripe subscription item id. Only set for relays on paid plans.
|
- `stripe_subscription_item_id` (nullable) - the Stripe subscription item id. Only set for relays on paid plans.
|
||||||
- `status` - one of `active|inactive|delinquent`. Only `active` relays count toward billing. `delinquent` is set by the billing system when a relay's subscription becomes past due; `inactive` is set when a user or admin manually deactivates a relay.
|
- `status` - `active|inactive`. Only `active` relays count toward billing.
|
||||||
- `synced` - whether the relay has been successfully synced to zooid at least once.
|
- `synced` - whether the relay has been successfully synced to zooid at least once.
|
||||||
- `sync_error` - a string indicating any errors encountered when synchronizing.
|
- `sync_error` - a string indicating any errors encountered when synchronizing.
|
||||||
- `info_name` - the relay's name
|
- `info_name` - the relay's name
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export default function PaymentSetup(props: PaymentSetupProps) {
|
|||||||
<div class="flex items-start justify-between gap-3">
|
<div class="flex items-start justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-lg font-semibold text-gray-900">Set Up Payments</h2>
|
<h2 class="text-lg font-semibold text-gray-900">Set Up Payments</h2>
|
||||||
<p class="text-sm text-gray-500 mt-1">Choose how you'd like to pay once invoices are issued for your relay.</p>
|
<p class="text-sm text-gray-500 mt-1">Choose how you'd like to pay for your relay.</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -144,7 +144,7 @@ export default function PaymentSetup(props: PaymentSetupProps) {
|
|||||||
<line x1="1" y1="10" x2="23" y2="10" />
|
<line x1="1" y1="10" x2="23" y2="10" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm text-gray-600">Add a payment card via Stripe to enable automatic billing once invoices are issued.</p>
|
<p class="text-sm text-gray-600">Add a payment card via Stripe to enable automatic billing.</p>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={openPortal}
|
onClick={openPortal}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useParams } from "@solidjs/router"
|
import { useParams } from "@solidjs/router"
|
||||||
import { createMemo, createResource, createSignal, Show } from "solid-js"
|
import { createMemo, createResource, Show } from "solid-js"
|
||||||
import BackLink from "@/components/BackLink"
|
import BackLink from "@/components/BackLink"
|
||||||
import PageContainer from "@/components/PageContainer"
|
import PageContainer from "@/components/PageContainer"
|
||||||
import PaymentSetup from "@/components/PaymentSetup"
|
import PaymentSetup from "@/components/PaymentSetup"
|
||||||
@@ -7,9 +7,8 @@ import RelayDetailCard from "@/components/RelayDetailCard"
|
|||||||
import ResourceState from "@/components/ResourceState"
|
import ResourceState from "@/components/ResourceState"
|
||||||
import useMinLoading from "@/components/useMinLoading"
|
import useMinLoading from "@/components/useMinLoading"
|
||||||
import ActivityFeed from "@/components/ActivityFeed"
|
import ActivityFeed from "@/components/ActivityFeed"
|
||||||
import { getRelayMembers, useRelay, useRelayActivity, useTenant } from "@/lib/hooks"
|
import { getRelayMembers, useRelay, useRelayActivity } from "@/lib/hooks"
|
||||||
import useRelayToggles from "@/lib/useRelayToggles"
|
import useRelayToggles from "@/lib/useRelayToggles"
|
||||||
import { plans } from "@/lib/state"
|
|
||||||
|
|
||||||
export default function RelayDetail() {
|
export default function RelayDetail() {
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
@@ -24,21 +23,6 @@ export default function RelayDetail() {
|
|||||||
const [activity] = useRelayActivity(relayId)
|
const [activity] = useRelayActivity(relayId)
|
||||||
const { busy, handleDeactivate, handleReactivate, handleUpdatePlan, needsPaymentSetup, clearNeedsPaymentSetup, toggles } = useRelayToggles(relayId, relay, { refetch, mutate })
|
const { busy, handleDeactivate, handleReactivate, handleUpdatePlan, needsPaymentSetup, clearNeedsPaymentSetup, toggles } = useRelayToggles(relayId, relay, { refetch, mutate })
|
||||||
|
|
||||||
const [tenant, { refetch: refetchTenant }] = useTenant()
|
|
||||||
const [paymentSetupOpen, setPaymentSetupOpen] = createSignal(false)
|
|
||||||
const [paymentBannerDismissed, setPaymentBannerDismissed] = createSignal(false)
|
|
||||||
|
|
||||||
const showPaymentNudge = createMemo(() => {
|
|
||||||
if (paymentBannerDismissed()) return false
|
|
||||||
const r = relay()
|
|
||||||
if (!r) return false
|
|
||||||
const plan = plans().find(p => p.id === r.plan)
|
|
||||||
if (!plan || plan.amount === 0) return false
|
|
||||||
const t = tenant()
|
|
||||||
if (!t) return false
|
|
||||||
return !t.nwc_url && !t.stripe_subscription_id
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
<BackLink href="/relays" label="Relays" />
|
<BackLink href="/relays" label="Relays" />
|
||||||
@@ -46,35 +30,6 @@ export default function RelayDetail() {
|
|||||||
<Show when={!loading() && relay()}>
|
<Show when={!loading() && relay()}>
|
||||||
{(r) => (
|
{(r) => (
|
||||||
<div class="space-y-6 mb-6">
|
<div class="space-y-6 mb-6">
|
||||||
<Show when={showPaymentNudge()}>
|
|
||||||
<div class="rounded-lg border border-amber-200 bg-amber-50 p-4 flex items-start justify-between gap-4">
|
|
||||||
<div class="min-w-0">
|
|
||||||
<p class="text-sm font-medium text-amber-800">Payment setup recommended</p>
|
|
||||||
<p class="text-sm text-amber-700 mt-1">
|
|
||||||
This relay is on a paid plan. Invoices are generated at the end of the billing period, so you cannot pay in advance. You can set up NWC or Stripe now to be ready when an invoice is issued.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-3 shrink-0">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setPaymentSetupOpen(true)}
|
|
||||||
class="text-sm font-medium text-amber-800 underline hover:text-amber-900 whitespace-nowrap"
|
|
||||||
>
|
|
||||||
Set up NWC or Stripe
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setPaymentBannerDismissed(true)}
|
|
||||||
aria-label="Dismiss"
|
|
||||||
class="text-amber-500 hover:text-amber-800 shrink-0"
|
|
||||||
>
|
|
||||||
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<path d="M18 6L6 18M6 6l12 12" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
<RelayDetailCard
|
<RelayDetailCard
|
||||||
relay={r()}
|
relay={r()}
|
||||||
currentMembers={members.length}
|
currentMembers={members.length}
|
||||||
@@ -91,12 +46,8 @@ export default function RelayDetail() {
|
|||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
<PaymentSetup
|
<PaymentSetup
|
||||||
open={needsPaymentSetup() || paymentSetupOpen()}
|
open={needsPaymentSetup()}
|
||||||
onClose={() => {
|
onClose={clearNeedsPaymentSetup}
|
||||||
clearNeedsPaymentSetup()
|
|
||||||
setPaymentSetupOpen(false)
|
|
||||||
void refetchTenant()
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user