Three launch blockers from the Oct 17 direct-Stripe billing work: - verifyStripeCheckoutSession defaulted an unconfirmed checkout session to "active"/isPaying:true, and had no billing permission check, so an abandoned (unpaid) checkout marked the org as a paying subscriber for free. Now defaults to "incomplete" and only trusts a real subscription status or a confirmed paid payment_status; also pins org id to the caller's own organization instead of trusting session metadata. - The Stripe webhook fell back to parsing an unsigned request body as a real event whenever STRIPE_WEBHOOK_SECRET was unset, letting anyone POST a fake checkout.session.completed and grant any org a paid plan. STRIPE_WEBHOOK_SECRET is now required; unsigned requests are refused. Also maps Stripe's subscription.status properly (trialing counted as active) instead of collapsing anything but "active" to "canceled". - meterDataforseoCall and every credit-check in subscription.ts skipped all usage metering/capping whenever AUTUMN_SECRET_KEY was unset — which is exactly the mode the Oct 17 work introduced for self-host direct Stripe billing. Added a local per-org monthly usage ledger (usage_ledger table, sqlite + pg) checked against each plan's monthly data budget (src/shared/crawlerxPlanBudgets.ts, from the PRD's unit economics table) so DataForSEO spend is capped even without Autumn. Also: documented STRIPE_SECRET_KEY/STRIPE_WEBHOOK_SECRET/AUTUMN_SECRET_KEY in .env.example, and published the CrawlerX product demo as a static page at /demo.html (self-contained, no backend dependency). Verified: tsc --noEmit clean, schema-parity test green, full vitest suite green except two pre-existing unrelated failures (confirmed via stash), knip/oxlint show no new findings over the pre-change baseline. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
8 lines
485 B
SQL
8 lines
485 B
SQL
CREATE TABLE "usage_ledger" (
|
|
"organization_id" text PRIMARY KEY NOT NULL,
|
|
"period_month" text NOT NULL,
|
|
"credits_used" integer DEFAULT 0 NOT NULL,
|
|
"updated_at" text DEFAULT to_char(now() AT TIME ZONE 'utc', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"') NOT NULL
|
|
);
|
|
--> statement-breakpoint
|
|
ALTER TABLE "usage_ledger" ADD CONSTRAINT "usage_ledger_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action; |