forked from coracle/caravel
6.2 KiB
6.2 KiB
pub struct Billing
Billing encapsulates logic related to synchronizing state with Stripe, processing payments via NWC, and managing subscription lifecycle.
Members:
nwc_url: String- a nostr wallet connect URL used to create bolt11 invoices (i.e. receive payments), fromNWC_URLstripe_webhook_secret: String- secret for verifying Stripe webhook signatures, fromSTRIPE_WEBHOOK_SECRETquery: Querycommand: Commandrobot: Robot
pub fn new(query: Query, command: Command, robot: Robot) -> Self
- Reads environment and populates members
pub fn start(&self)
- Subscribes to
command.notify.subscribe() - On
create_relay,update_relay,activate_relay,deactivate_relay,fail_relay_sync, andcomplete_relay_sync, callself.sync_relay_subscription.
pub fn sync_relay_subscription(&self, activity: &Activity)
Manages the Stripe subscription and subscription items for a relay's tenant. Only paid (non-free) relays interact with Stripe. Free-only tenants have no subscription. Must be idempotent.
- Fetch the relay and tenant associated with the
activity - If relay plan is
free: if the relay has astripe_subscription_item_id, delete it via the Stripe API and callcommand.delete_relay_subscription_item. Then check cleanup (below). Return early. - If relay is
inactive: if the relay has astripe_subscription_item_id, delete it via the Stripe API and callcommand.delete_relay_subscription_item. Then check cleanup (below). Return early. - If relay is
activeand on a paid plan:- Ensure subscription exists: If the tenant has no
stripe_subscription_id, create a Stripe subscription for the customer withcollection_method: "charge_automatically"and the relay's price as the first item. Save the subscription ID viacommand.set_tenant_subscriptionand the item ID viacommand.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_idvia the Stripe API, then callcommand.set_relay_subscription_item.
- Ensure subscription exists: If the tenant has no
- Clean up empty subscription: After any item deletion, check if the tenant has any remaining active paid relays. If none and the tenant has a
stripe_subscription_id, cancel the Stripe subscription immediately and callcommand.clear_tenant_subscription.
pub fn handle_webhook(&self, payload: &str, signature: &str) -> Result<()>
- Verify the webhook signature using
self.stripe_webhook_secret - Parse the event and dispatch by type:
invoice.created->self.handle_invoice_createdinvoice.paid->self.handle_invoice_paidinvoice.payment_failed->self.handle_invoice_payment_failedinvoice.overdue->self.handle_invoice_overduecustomer.subscription.updated->self.handle_subscription_updatedcustomer.subscription.deleted->self.handle_subscription_deleted
- Unknown event types are ignored (return Ok)
pub async fn stripe_list_invoices(&self, customer_id: &str) -> Result<Value>
- Fetches invoices from Stripe API for the given customer
- Returns the
dataarray from the Stripe response
pub async fn stripe_get_invoice(&self, invoice_id: &str) -> Result<Value>
- Fetches a single invoice from Stripe API by ID
- Returns the full Stripe invoice object
pub async fn create_bolt11(&self, amount_due_cents: i64) -> Result<String>
- Creates a bolt11 Lightning invoice for the given amount using the system NWC wallet (
self.nwc_url) - Returns the bolt11 invoice string
pub async fn stripe_create_portal_session(&self, customer_id: &str) -> Result<String>
- Creates a Stripe Customer Portal session for the given customer
- Returns the portal session URL
fn handle_invoice_created(&self, invoice: &Invoice)
Attempts to pay a new subscription invoice. Payment priority:
- NWC auto-pay: If the tenant has a
nwc_url:- Create a bolt11 Lightning invoice for the invoice amount using
self.nwc_url(the receiving/system wallet) - Pay the bolt11 invoice using the tenant's
nwc_url(the spending/tenant wallet) - If payment succeeds: call Stripe
POST /v1/invoices/:id/paywithpaid_out_of_band: true. Clearnwc_errorviacommand.clear_tenant_nwc_error. - If payment fails: set
nwc_erroron tenant viacommand.set_tenant_nwc_error. Fall through to next option.
- Create a bolt11 Lightning invoice for the invoice amount using
- Card on file: If the tenant has a payment method on the Stripe customer, do nothing — Stripe will charge automatically.
- Manual payment: If neither NWC nor card is available, send a DM via
robot.send_dmnotifying the tenant that payment is due with a link to the application for manual Lightning payment.
Skip invoices with amount_due of 0.
fn handle_invoice_paid(&self, invoice: &Invoice)
- Look up tenant by
stripe_customer_id - If tenant has
past_due_atset:- Clear
past_due_atviacommand.clear_tenant_past_due - Find all
inactiverelays 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
- Clear
fn handle_invoice_payment_failed(&self, invoice: &Invoice)
- Look up tenant by
stripe_customer_id - If tenant does not already have
past_due_atset:- Set
past_due_atto now viacommand.set_tenant_past_due - Send a DM via
robot.send_dmnotifying the tenant that their payment has failed and their relays may be deactivated if not resolved.
- Set
fn handle_invoice_overdue(&self, invoice: &Invoice)
- Look up tenant by
stripe_customer_id - Deactivate all active relays on paid plans via
command.deactivate_relay - Send a DM via
robot.send_dmnotifying the tenant that their paid relays have been deactivated due to non-payment
fn handle_subscription_updated(&self, subscription: &Subscription)
- Look up tenant by
stripe_customer_id - If subscription status is
canceledorunpaid:- Clear
stripe_subscription_idviacommand.clear_tenant_subscription - Deactivate all active paid relays for the tenant via
command.deactivate_relay
- Clear
fn handle_subscription_deleted(&self, subscription: &Subscription)
- Look up tenant by
stripe_customer_id - Clear
stripe_subscription_idviacommand.clear_tenant_subscription