docs: add missing OpenAPI annotations for signup/verify, billing/success, billing/webhook
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 16m15s

This commit is contained in:
OpenClaw 2026-02-27 16:04:55 +00:00
parent 427ec8e894
commit 8b31d11e74
15 changed files with 2167 additions and 128 deletions

View file

@ -92,6 +92,31 @@ describe("App-level routes", () => {
});
});
describe("OpenAPI spec completeness", () => {
let spec: any;
beforeAll(async () => {
const res = await request(app).get("/openapi.json");
expect(res.status).toBe(200);
spec = res.body;
});
it("includes POST /v1/signup/verify", () => {
expect(spec.paths["/v1/signup/verify"]).toBeDefined();
expect(spec.paths["/v1/signup/verify"].post).toBeDefined();
});
it("includes GET /v1/billing/success", () => {
expect(spec.paths["/v1/billing/success"]).toBeDefined();
expect(spec.paths["/v1/billing/success"].get).toBeDefined();
});
it("includes POST /v1/billing/webhook", () => {
expect(spec.paths["/v1/billing/webhook"]).toBeDefined();
expect(spec.paths["/v1/billing/webhook"].post).toBeDefined();
});
});
describe("Security headers", () => {
it("includes helmet security headers", async () => {
const res = await request(app).get("/api");

View file

@ -112,6 +112,36 @@ router.post("/checkout", checkoutLimiter, async (req: Request, res: Response) =>
}
});
/**
* @openapi
* /v1/billing/success:
* get:
* tags: [Billing]
* summary: Checkout success page
* description: |
* Provisions a Pro API key after successful Stripe checkout and displays it in an HTML page.
* Called by Stripe redirect after payment completion.
* parameters:
* - in: query
* name: session_id
* required: true
* schema:
* type: string
* description: Stripe Checkout session ID
* responses:
* 200:
* description: HTML page displaying the new API key
* content:
* text/html:
* schema:
* type: string
* 400:
* description: Missing session_id or no customer found
* 409:
* description: Checkout session already used
* 500:
* description: Failed to retrieve session
*/
// Success page — provision Pro API key after checkout
router.get("/success", async (req: Request, res: Response) => {
const sessionId = req.query.session_id as string;
@ -189,6 +219,47 @@ a { color: #4f9; }
}
});
/**
* @openapi
* /v1/billing/webhook:
* post:
* tags: [Billing]
* summary: Stripe webhook endpoint
* description: |
* Receives Stripe webhook events for subscription lifecycle management.
* Requires the raw request body and a valid Stripe-Signature header for verification.
* Handles checkout.session.completed, customer.subscription.updated,
* customer.subscription.deleted, and customer.updated events.
* parameters:
* - in: header
* name: Stripe-Signature
* required: true
* schema:
* type: string
* description: Stripe webhook signature for payload verification
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* description: Raw Stripe event payload
* responses:
* 200:
* description: Webhook received
* content:
* application/json:
* schema:
* type: object
* properties:
* received:
* type: boolean
* example: true
* 400:
* description: Missing Stripe-Signature header or invalid signature
* 500:
* description: Webhook secret not configured
*/
// Stripe webhook for subscription lifecycle events
router.post("/webhook", async (req: Request, res: Response) => {
const sig = req.headers["stripe-signature"] as string;

View file

@ -63,6 +63,60 @@ router.post("/free", rejectDuplicateEmail, signupLimiter, async (req: Request, r
});
});
/**
* @openapi
* /v1/signup/verify:
* post:
* tags: [Account]
* summary: Verify email and get API key
* description: |
* Verifies the 6-digit code sent to the user's email and provisions a free API key.
* Rate limited to 15 attempts per 15 minutes.
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required: [email, code]
* properties:
* email:
* type: string
* format: email
* description: Email address used during signup
* example: user@example.com
* code:
* type: string
* description: 6-digit verification code from email
* example: "123456"
* responses:
* 200:
* description: Email verified, API key issued
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: string
* example: verified
* message:
* type: string
* apiKey:
* type: string
* description: The provisioned API key
* tier:
* type: string
* example: free
* 400:
* description: Missing fields or invalid verification code
* 409:
* description: Email already verified
* 410:
* description: Verification code expired
* 429:
* description: Too many failed attempts
*/
// Step 2: Verify code — creates API key
router.post("/verify", verifyLimiter, async (req: Request, res: Response) => {
const { email, code } = req.body || {};