use super::{write_file, Scaffold, ScaffoldArgs, ScaffoldResult};
use anyhow::Result;
pub struct BillingScaffold;
impl Scaffold for BillingScaffold {
fn name(&self) -> &'static str {
"billing"
}
fn description(&self) -> &'static str {
"Billing: subscription plans, checkout, webhook handler, invoice model"
}
fn generate(&self, args: &ScaffoldArgs) -> Result<ScaffoldResult> {
let mut r = ScaffoldResult::default();
let d = args.dry_run;
write_file(
&mut r,
"src/app/controllers/billing_controller.rs",
CONTROLLER,
d,
)?;
write_file(&mut r, "migrations/create_billing_tables.sql", MIGRATION, d)?;
r.warnings
.push("Set STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET in .env".into());
r.warnings
.push("Register billing routes in src/app/routes.rs".into());
Ok(r)
}
}
const CONTROLLER: &str = r#"use axum::{extract::{Path, State}, response::IntoResponse, Json};
use rok_auth::axum::{Ctx, Response};
pub async fn plans(State(pool): State<sqlx::PgPool>) -> impl IntoResponse {
// TODO: list active subscription plans
Response::json(serde_json::json!({ "data": [] }))
}
pub async fn checkout(ctx: Ctx, Json(body): Json<serde_json::Value>) -> impl IntoResponse {
// TODO: create Stripe checkout session, return URL
Response::json(serde_json::json!({ "checkout_url": "" }))
}
pub async fn portal(ctx: Ctx) -> impl IntoResponse {
// TODO: create Stripe customer portal session
Response::json(serde_json::json!({ "portal_url": "" }))
}
pub async fn webhook(Json(payload): Json<serde_json::Value>) -> impl IntoResponse {
// TODO: handle stripe webhook events (checkout.completed, invoice.paid, etc.)
Response::json(serde_json::json!({ "received": true }))
}
pub async fn invoices(ctx: Ctx, State(pool): State<sqlx::PgPool>) -> impl IntoResponse {
// TODO: list invoices for current user
Response::json(serde_json::json!({ "data": [] }))
}
"#;
const MIGRATION: &str = r#"CREATE TABLE subscription_plans (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
price_cents INTEGER NOT NULL,
interval TEXT NOT NULL DEFAULT 'month',
stripe_id TEXT,
active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE subscriptions (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL,
plan_id BIGINT NOT NULL REFERENCES subscription_plans(id),
stripe_sub_id TEXT,
stripe_customer_id TEXT,
status TEXT NOT NULL DEFAULT 'active',
current_period_end TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE invoices (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL,
subscription_id BIGINT REFERENCES subscriptions(id),
amount_cents INTEGER NOT NULL,
currency TEXT NOT NULL DEFAULT 'usd',
status TEXT NOT NULL DEFAULT 'open',
stripe_id TEXT,
paid_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
"#;