create-charcole 2.2.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/CHANGELOG.md +34 -3
  2. package/README.md +94 -4
  3. package/bin/index.js +174 -4
  4. package/package.json +3 -3
  5. package/packages/payments/CHANGELOG.md +14 -0
  6. package/packages/payments/README.md +222 -0
  7. package/packages/payments/charcoles-payments-1.0.0.tgz +0 -0
  8. package/packages/payments/package.json +61 -0
  9. package/packages/payments/smoke-test.js +20 -0
  10. package/packages/payments/src/__tests__/LemonSqueezyAdapter.test.js +236 -0
  11. package/packages/payments/src/__tests__/StripeAdapter.test.js +139 -0
  12. package/packages/payments/src/__tests__/payments.service.test.js +131 -0
  13. package/packages/payments/src/__tests__/webhookUtils.test.js +47 -0
  14. package/packages/payments/src/adapters/LemonSqueezyAdapter.js +150 -0
  15. package/packages/payments/src/adapters/PaymentAdapter.js +109 -0
  16. package/packages/payments/src/adapters/StripeAdapter.js +114 -0
  17. package/packages/payments/src/controllers/payments.controller.js +48 -0
  18. package/packages/payments/src/errors/PaymentError.js +8 -0
  19. package/packages/payments/src/helpers/webhookUtils.js +27 -0
  20. package/packages/payments/src/index.d.ts +87 -0
  21. package/packages/payments/src/index.js +6 -0
  22. package/packages/payments/src/routes/payments.routes.js +41 -0
  23. package/packages/payments/src/schemas/payments.schemas.js +24 -0
  24. package/packages/payments/src/services/payments.service.js +68 -0
  25. package/packages/swagger/BACKWARD_COMPATIBILITY.md +1 -1
  26. package/packages/swagger/CHANGELOG.md +1 -1
  27. package/packages/swagger/README.md +3 -3
  28. package/packages/swagger/package.json +3 -3
  29. package/packages/swagger/src/setup.js +1 -1
  30. package/plan-2.3.0.md +1756 -0
  31. package/template/js/.env.example +20 -1
  32. package/template/js/README.md +140 -8
  33. package/template/js/basePackage.json +1 -1
  34. package/template/js/src/app.js +18 -1
  35. package/template/js/src/config/env.js +8 -0
  36. package/template/js/src/config/swagger.config.js +1 -1
  37. package/template/js/src/lib/swagger/SWAGGER_GUIDE.md +37 -3
  38. package/template/js/src/modules/payments/__tests__/payments.controller.test.js +342 -0
  39. package/template/js/src/modules/payments/__tests__/payments.routes.test.js +256 -0
  40. package/template/js/src/modules/payments/__tests__/payments.schemas.test.js +94 -0
  41. package/template/js/src/modules/payments/__tests__/payments.service.test.js +141 -0
  42. package/template/js/src/modules/payments/package.json +7 -0
  43. package/template/js/src/modules/payments/payments.adapter.js +47 -0
  44. package/template/js/src/modules/payments/payments.constants.js +20 -0
  45. package/template/js/src/modules/payments/payments.controller.js +85 -0
  46. package/template/js/src/modules/payments/payments.routes.js +125 -0
  47. package/template/js/src/modules/payments/payments.schemas.js +28 -0
  48. package/template/js/src/modules/payments/payments.service.js +34 -0
  49. package/template/js/src/modules/swagger/package.json +1 -1
  50. package/template/js/src/routes/index.js +16 -0
  51. package/template/ts/.env.example +18 -1
  52. package/template/ts/README.md +142 -8
  53. package/template/ts/basePackage.json +1 -1
  54. package/template/ts/src/app.ts +13 -0
  55. package/template/ts/src/config/env.ts +7 -0
  56. package/template/ts/src/config/swagger.config.ts +1 -1
  57. package/template/ts/src/lib/swagger/SWAGGER_GUIDE.md +36 -2
  58. package/template/ts/src/modules/payments/__tests__/payments.controller.test.ts +282 -0
  59. package/template/ts/src/modules/payments/__tests__/payments.routes.test.ts +256 -0
  60. package/template/ts/src/modules/payments/__tests__/payments.schemas.test.ts +94 -0
  61. package/template/ts/src/modules/payments/__tests__/payments.service.test.ts +135 -0
  62. package/template/ts/src/modules/payments/package.json +7 -0
  63. package/template/ts/src/modules/payments/payments.adapter.ts +74 -0
  64. package/template/ts/src/modules/payments/payments.constants.ts +18 -0
  65. package/template/ts/src/modules/payments/payments.controller.ts +104 -0
  66. package/template/ts/src/modules/payments/payments.routes.ts +125 -0
  67. package/template/ts/src/modules/payments/payments.schemas.ts +31 -0
  68. package/template/ts/src/modules/payments/payments.service.ts +51 -0
  69. package/template/ts/src/modules/payments/payments.types.ts +56 -0
  70. package/template/ts/src/modules/swagger/package.json +1 -1
  71. package/template/ts/src/routes/index.ts +8 -0
  72. package/packages/swagger/package-lock.json +0 -1715
@@ -0,0 +1,125 @@
1
+ import { Router } from "express";
2
+ import { validateRequest } from "../../middlewares/validateRequest.js";
3
+ import * as controller from "./payments.controller.js";
4
+ import {
5
+ createPaymentSchema,
6
+ refundPaymentSchema,
7
+ } from "./payments.schemas.js";
8
+
9
+ const router = Router();
10
+
11
+ /**
12
+ * @swagger
13
+ * /api/payments/create-intent:
14
+ * post:
15
+ * summary: Create a payment intent or checkout session
16
+ * tags:
17
+ * - Payments
18
+ * requestBody:
19
+ * required: true
20
+ * content:
21
+ * application/json:
22
+ * schema:
23
+ * type: object
24
+ * required: [amount, currency]
25
+ * properties:
26
+ * amount:
27
+ * type: integer
28
+ * description: Amount in smallest currency unit (cents for USD, paisas for PKR)
29
+ * example: 2999
30
+ * currency:
31
+ * type: string
32
+ * description: ISO 4217 currency code
33
+ * example: usd
34
+ * metadata:
35
+ * type: object
36
+ * description: Optional metadata. LemonSqueezy requires variantId here.
37
+ * responses:
38
+ * 201:
39
+ * description: Payment intent created
40
+ * 400:
41
+ * description: Validation error
42
+ */
43
+ router.post(
44
+ "/create-intent",
45
+ validateRequest(createPaymentSchema),
46
+ controller.createPayment,
47
+ );
48
+
49
+ /**
50
+ * @swagger
51
+ * /api/payments/refund:
52
+ * post:
53
+ * summary: Refund a payment
54
+ * tags:
55
+ * - Payments
56
+ * requestBody:
57
+ * required: true
58
+ * content:
59
+ * application/json:
60
+ * schema:
61
+ * type: object
62
+ * required: [paymentId]
63
+ * properties:
64
+ * paymentId:
65
+ * type: string
66
+ * example: pi_123456789
67
+ * amount:
68
+ * type: integer
69
+ * description: Optional refund amount in smallest currency unit
70
+ * example: 2999
71
+ * responses:
72
+ * 200:
73
+ * description: Refund processed
74
+ * 400:
75
+ * description: Validation error
76
+ */
77
+ router.post(
78
+ "/refund",
79
+ validateRequest(refundPaymentSchema),
80
+ controller.refundPayment,
81
+ );
82
+
83
+ /**
84
+ * @swagger
85
+ * /api/payments/status/{paymentId}:
86
+ * get:
87
+ * summary: Get payment status
88
+ * tags:
89
+ * - Payments
90
+ * parameters:
91
+ * - in: path
92
+ * name: paymentId
93
+ * required: true
94
+ * schema:
95
+ * type: string
96
+ * responses:
97
+ * 200:
98
+ * description: Payment status retrieved
99
+ * 404:
100
+ * description: Payment not found
101
+ */
102
+ router.get("/status/:paymentId", controller.getPaymentStatus);
103
+
104
+ /**
105
+ * @swagger
106
+ * /api/payments/webhook:
107
+ * post:
108
+ * summary: Receive payment provider webhook events
109
+ * tags:
110
+ * - Payments
111
+ * requestBody:
112
+ * required: true
113
+ * content:
114
+ * application/json:
115
+ * schema:
116
+ * type: object
117
+ * responses:
118
+ * 200:
119
+ * description: Webhook received
120
+ * 400:
121
+ * description: Missing signature header
122
+ */
123
+ router.post("/webhook", controller.handleWebhook);
124
+
125
+ export default router;
@@ -0,0 +1,28 @@
1
+ import { z } from "zod";
2
+
3
+ export const createPaymentSchema = z.object({
4
+ amount: z
5
+ .number({ required_error: "amount is required" })
6
+ .int("amount must be an integer (smallest currency unit, e.g. cents)")
7
+ .positive("amount must be positive")
8
+ .max(99999999, "amount exceeds maximum"),
9
+
10
+ currency: z
11
+ .string({ required_error: "currency is required" })
12
+ .length(3, "currency must be a 3-letter ISO 4217 code (e.g. usd, pkr)")
13
+ .toLowerCase(),
14
+
15
+ metadata: z.record(z.string()).optional().default({}),
16
+ });
17
+
18
+ export const refundPaymentSchema = z.object({
19
+ paymentId: z
20
+ .string({ required_error: "paymentId is required" })
21
+ .min(1, "paymentId cannot be empty"),
22
+
23
+ amount: z
24
+ .number()
25
+ .int("amount must be an integer")
26
+ .positive("amount must be positive")
27
+ .optional(),
28
+ });
@@ -0,0 +1,34 @@
1
+ import { getAdapter } from "./payments.adapter.js";
2
+
3
+ const processedWebhookIds = new Set();
4
+
5
+ export async function createPayment({ amount, currency, metadata }) {
6
+ const adapter = getAdapter();
7
+ return adapter.createPayment({ amount, currency, metadata });
8
+ }
9
+
10
+ export async function refundPayment({ paymentId, amount }) {
11
+ const adapter = getAdapter();
12
+ return adapter.refundPayment({ paymentId, amount });
13
+ }
14
+
15
+ export async function getPaymentStatus(paymentId) {
16
+ const adapter = getAdapter();
17
+ return adapter.getPaymentStatus(paymentId);
18
+ }
19
+
20
+ export async function processWebhook(rawBody, signature) {
21
+ const adapter = getAdapter();
22
+ const result = await adapter.verifyWebhook(rawBody, signature);
23
+
24
+ const eventId = result.data?.id
25
+ ? String(result.data.id)
26
+ : `${result.event}-${Date.now()}`;
27
+
28
+ if (processedWebhookIds.has(eventId)) {
29
+ return { ...result, duplicate: true };
30
+ }
31
+
32
+ processedWebhookIds.add(eventId);
33
+ return { ...result, duplicate: false };
34
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "dependencies": {
3
- "@charcoles/swagger": "file:./charcole-swagger-1.0.0.tgz"
3
+ "@charcoles/swagger": "^1.0.1"
4
4
  }
5
5
  }
@@ -1,4 +1,7 @@
1
1
  import { Router } from "express";
2
+ import { existsSync } from "fs";
3
+ import { dirname, join } from "path";
4
+ import { fileURLToPath } from "url";
2
5
  import {
3
6
  getHealth,
4
7
  createItem,
@@ -8,6 +11,9 @@ import { validateRequest } from "../middlewares/validateRequest.js";
8
11
  import { requireAuth } from "../modules/auth/auth.middlewares.js";
9
12
  import protectedRoutes from "./protected.js";
10
13
  import authRoutes from "../modules/auth/auth.routes.js";
14
+ import paymentsRoutes from "../modules/payments/payments.routes.js";
15
+
16
+ const __dirname = dirname(fileURLToPath(import.meta.url));
11
17
  const router = Router();
12
18
 
13
19
  // Health check
@@ -19,6 +25,16 @@ router.post("/items", validateRequest(createItemSchema), createItem);
19
25
  // 🔐 Auth routes
20
26
  router.use("/auth", authRoutes);
21
27
 
28
+ // Payments routes — only loaded if the payments module was included during scaffolding
29
+ const paymentsRoutesPath = join(
30
+ __dirname,
31
+ "../modules/payments/payments.routes.js",
32
+ );
33
+ if (existsSync(paymentsRoutesPath)) {
34
+ const { default: paymentsRoutes } = await import(paymentsRoutesPath);
35
+ router.use("/payments", paymentsRoutes);
36
+ }
37
+
22
38
  // 🔐 Protected routes (REQUIRED BEARER TOKEN FOR THEM)
23
39
  router.use("/protected", protectedRoutes);
24
40
 
@@ -1,4 +1,5 @@
1
1
  # Server Configuration
2
+ APP_NAME=CHARCOLE API
2
3
  NODE_ENV=development
3
4
  PORT=3000
4
5
 
@@ -13,4 +14,20 @@ REQUEST_TIMEOUT=30000
13
14
 
14
15
 
15
16
  # Authentication
16
- JWT_SECRET=your-secret-key-here
17
+ JWT_SECRET=your-secret-key-here
18
+
19
+ # ─── Payments ──────────────────────────────────────────────────────────────────
20
+ # PAYMENT_PROVIDER selects the active payment adapter.
21
+ # Options: "stripe" | "lemonsqueezy"
22
+ # Use "lemonsqueezy" if you are in Pakistan — Stripe does not support PKR payouts.
23
+ PAYMENT_PROVIDER=
24
+
25
+ # Stripe — https://dashboard.stripe.com/apikeys
26
+ STRIPE_SECRET_KEY=
27
+ STRIPE_WEBHOOK_SECRET=
28
+ STRIPE_PUBLISHABLE_KEY=
29
+
30
+ # LemonSqueezy — https://app.lemonsqueezy.com/settings/api
31
+ LEMONSQUEEZY_API_KEY=
32
+ LEMONSQUEEZY_WEBHOOK_SECRET=
33
+ LEMONSQUEEZY_STORE_ID=
@@ -9,11 +9,12 @@ Welcome! This guide will help you set up and start using the Charcole API framew
9
9
  3. [Configuration](#configuration)
10
10
  4. [Creating Your First Endpoint](#creating-your-first-endpoint)
11
11
  5. [API Documentation with Swagger](#api-documentation-with-swagger)
12
- 6. [Error Handling](#error-handling)
13
- 7. [Validation](#validation)
14
- 8. [Logging](#logging)
15
- 9. [Running Your API](#running-your-api)
16
- 10. [Troubleshooting](#troubleshooting)
12
+ 6. [Payment Processing (if enabled)](#payment-processing-if-enabled)
13
+ 7. [Error Handling](#error-handling)
14
+ 8. [Validation](#validation)
15
+ 9. [Logging](#logging)
16
+ 10. [Running Your API](#running-your-api)
17
+ 11. [Troubleshooting](#troubleshooting)
17
18
 
18
19
  ---
19
20
 
@@ -34,9 +35,13 @@ Welcome! This guide will help you set up and start using the Charcole API framew
34
35
 
35
36
  2. **Create environment file**
36
37
 
37
- ```bash
38
- cp .env.example .env
39
- ```
38
+ The `create-charcole` CLI will automatically create a `.env` from `.env.example` and initialize a Git repository for you. Edit the generated `.env` as needed.
39
+
40
+ If you prefer to create it manually:
41
+
42
+ ```bash
43
+ cp .env.example .env
44
+ ```
40
45
 
41
46
  3. **Run the charcole**
42
47
  ```bash
@@ -519,6 +524,135 @@ For protected endpoints:
519
524
 
520
525
  ---
521
526
 
527
+ ## 💳 Payment Processing (if enabled)
528
+
529
+ ### Overview
530
+
531
+ Your API comes with **production-ready payment processing** if you selected the payments module during setup. Choose between:
532
+
533
+ - **Stripe** - Industry standard payment processing
534
+ - **LemonSqueezy** - Perfect for Pakistani developers (PKR payout support via bank transfer)
535
+ - **Both** - Flexibility to switch providers
536
+
537
+ ### Payment Endpoints
538
+
539
+ Four ready-to-use payment APIs are auto-configured:
540
+
541
+ ```
542
+ POST /api/payments/create-intent # Create payment intent
543
+ POST /api/payments/refund # Refund a payment
544
+ GET /api/payments/status/:paymentId # Check payment status
545
+ POST /api/payments/webhook # Webhook receiver
546
+ ```
547
+
548
+ ### Configuration
549
+
550
+ Add payment provider credentials to `.env`:
551
+
552
+ ```env
553
+ # Payment Provider (stripe, lemonsqueezy, or both)
554
+ PAYMENT_PROVIDER=stripe
555
+
556
+ # Stripe Configuration
557
+ STRIPE_SECRET_KEY=sk_live_...
558
+ STRIPE_WEBHOOK_SECRET=whsec_...
559
+
560
+ # LemonSqueezy Configuration
561
+ LEMONSQUEEZY_API_KEY=...
562
+ LEMONSQUEEZY_WEBHOOK_SECRET=...
563
+ ```
564
+
565
+ ### Usage Example
566
+
567
+ ```typescript
568
+ import { setupPayments } from "@charcoles/payments";
569
+
570
+ // In your app.ts
571
+ const paymentAdapter = setupPayments({
572
+ provider: process.env.PAYMENT_PROVIDER,
573
+ stripeKey: process.env.STRIPE_SECRET_KEY,
574
+ lemonsqueezyKey: process.env.LEMONSQUEEZY_API_KEY,
575
+ });
576
+
577
+ // In your controller
578
+ export const createPaymentIntent = asyncHandler(
579
+ async (req: Request, res: Response) => {
580
+ const { amount, currency, customerId } = req.body;
581
+
582
+ const intent = await paymentAdapter.createPayment({
583
+ amount,
584
+ currency,
585
+ customerId,
586
+ });
587
+
588
+ sendSuccess(res, intent, 201, "Payment intent created");
589
+ },
590
+ );
591
+ ```
592
+
593
+ ### Webhook Handling
594
+
595
+ Webhooks are automatically configured and validated:
596
+
597
+ ```typescript
598
+ // src/modules/payments/payments.routes.ts
599
+ // POST /api/payments/webhook automatically handles:
600
+ // - Stripe: payment_intent.succeeded, charge.refunded
601
+ // - LemonSqueezy: order_created, order_refunded
602
+
603
+ // Raw body middleware auto-configured in app.ts
604
+ // app.use('/payments/webhook', express.raw({ type: 'application/json' }))
605
+ ```
606
+
607
+ ### Error Handling
608
+
609
+ Payment errors are automatically caught by the global error handler:
610
+
611
+ ```typescript
612
+ import { PaymentError } from "@charcoles/payments";
613
+
614
+ // Throws structured error
615
+ throw new PaymentError("Insufficient funds", {
616
+ code: "insufficient_funds",
617
+ statusCode: 402,
618
+ });
619
+
620
+ // Response:
621
+ // {
622
+ // "success": false,
623
+ // "message": "Insufficient funds",
624
+ // "statusCode": 402,
625
+ // "code": "insufficient_funds"
626
+ // }
627
+ ```
628
+
629
+ ### Testing Payments Locally
630
+
631
+ **Stripe:**
632
+
633
+ ```bash
634
+ STRIPE_SECRET_KEY=sk_test_... npm run dev
635
+ # Use test card: 4242 4242 4242 4242
636
+ ```
637
+
638
+ **LemonSqueezy:**
639
+
640
+ ```bash
641
+ LEMONSQUEEZY_API_KEY=... npm run dev
642
+ # Sandbox mode automatically used with test keys
643
+ ```
644
+
645
+ ### Documentation in Swagger
646
+
647
+ All payment endpoints are automatically documented in Swagger UI when enabled:
648
+
649
+ 1. Start server: `npm run dev`
650
+ 2. Visit http://localhost:3000/api-docs
651
+ 3. Look for **Payments** tag
652
+ 4. Test all endpoints directly from the browser
653
+
654
+ ---
655
+
522
656
  ## ✔️ Validation
523
657
 
524
658
  ### Zod Schema Basics
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "charcole",
3
- "version": "2.2.1",
3
+ "version": "2.3.0",
4
4
  "description": "Production-grade Node.js Express API",
5
5
  "main": "dist/server.js",
6
6
  "type": "module",
@@ -1,4 +1,7 @@
1
1
  import express, { Request, Response, NextFunction } from "express";
2
+ import { existsSync } from "fs";
3
+ import { dirname, join } from "path";
4
+ import { fileURLToPath } from "url";
2
5
  import cors from "cors";
3
6
  import { userRepo } from "./repositories/user.repo.ts";
4
7
  import { env } from "./config/env.ts";
@@ -14,6 +17,12 @@ import routes from "./routes/index.ts";
14
17
  import swaggerOptions from "./config/swagger.config.ts";
15
18
  import { setupSwagger } from "@charcoles/swagger";
16
19
 
20
+ const __dirname = dirname(fileURLToPath(import.meta.url));
21
+ const paymentsModulePath = join(
22
+ __dirname,
23
+ "modules/payments/payments.routes.ts",
24
+ );
25
+
17
26
  export const app = express();
18
27
 
19
28
  app.set("trust proxy", 1);
@@ -27,6 +36,10 @@ app.use(
27
36
  }),
28
37
  );
29
38
 
39
+ if (existsSync(paymentsModulePath)) {
40
+ app.use("/payments/webhook", express.raw({ type: "application/json" }));
41
+ }
42
+
30
43
  app.use(express.json({ limit: "10mb" }));
31
44
  app.use(express.urlencoded({ extended: true, limit: "10mb" }));
32
45
 
@@ -8,6 +8,13 @@ const envSchema = z.object({
8
8
  LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"),
9
9
  CORS_ORIGIN: z.string().default("*"),
10
10
  REQUEST_TIMEOUT: z.coerce.number().default(30000),
11
+ PAYMENT_PROVIDER: z.enum(["stripe", "lemonsqueezy"]).optional(),
12
+ STRIPE_SECRET_KEY: z.string().optional(),
13
+ STRIPE_WEBHOOK_SECRET: z.string().optional(),
14
+ STRIPE_PUBLISHABLE_KEY: z.string().optional(),
15
+ LEMONSQUEEZY_API_KEY: z.string().optional(),
16
+ LEMONSQUEEZY_WEBHOOK_SECRET: z.string().optional(),
17
+ LEMONSQUEEZY_STORE_ID: z.string().optional(),
11
18
  });
12
19
 
13
20
  type EnvSchema = z.infer<typeof envSchema>;
@@ -3,7 +3,7 @@ import { createItemSchema } from "../modules/health/controller.ts";
3
3
 
4
4
  const swaggerConfig = {
5
5
  title: process.env.APP_NAME || "Charcole API",
6
- version: process.env.APP_VERSION || "1.0.0",
6
+ version: process.env.APP_VERSION || "1.0.1",
7
7
  description: "Production-ready Node.js Express API",
8
8
  path: "/api-docs",
9
9
  servers: [
@@ -18,7 +18,7 @@ import { createItemSchema } from "../modules/health/controller.ts";
18
18
 
19
19
  const swaggerConfig = {
20
20
  title: "My API",
21
- version: "1.0.0",
21
+ version: "1.0.1",
22
22
  // Auto-register schemas - they'll be converted to OpenAPI automatically!
23
23
  schemas: {
24
24
  registerSchema,
@@ -96,6 +96,40 @@ That's it! Your Zod schemas are now available as `$ref` in Swagger.
96
96
 
97
97
  **Result:** 60 lines eliminated! And your schema stays in sync automatically.
98
98
 
99
+ ### Example 2.1: Documenting a Payments Webhook Endpoint
100
+
101
+ ```typescript
102
+ /**
103
+ * @swagger
104
+ * /api/payments/webhook:
105
+ * post:
106
+ * summary: Receive payment provider webhook events
107
+ * tags:
108
+ * - Payments
109
+ * requestBody:
110
+ * required: true
111
+ * content:
112
+ * application/json:
113
+ * schema:
114
+ * type: object
115
+ * responses:
116
+ * 200:
117
+ * description: Webhook received
118
+ * 400:
119
+ * description: Missing signature header
120
+ */
121
+ router.post("/webhook", handleWebhook);
122
+ ```
123
+
124
+ When you expose a webhook route, mount raw JSON middleware before `express.json()` so the provider signature verification receives the raw body:
125
+
126
+ ```typescript
127
+ app.use("/payments/webhook", express.raw({ type: "application/json" }));
128
+ app.use(express.json());
129
+ ```
130
+
131
+ This means your final webhook route becomes `/api/payments/webhook` when the payments router is mounted at `/api`.
132
+
99
133
  ---
100
134
 
101
135
  ## 📚 Complete Examples
@@ -472,7 +506,7 @@ const app = express();
472
506
 
473
507
  setupSwagger(app, {
474
508
  title: "My API",
475
- version: "1.0.0",
509
+ version: "1.0.1",
476
510
  schemas: {
477
511
  mySchema, // Your Zod schemas
478
512
  },