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
package/CHANGELOG.md CHANGED
@@ -5,9 +5,40 @@ All notable changes to Charcole will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [2.2.1] – 2026-02-06
9
-
10
- ### 🎉 Major Release: Auto-Generated Swagger Documentation
8
+ ## [2.3.0] – 2026-05-05
9
+
10
+ ### Added
11
+
12
+ - **Payments module** — optional, production-ready payment processing for scaffolded projects
13
+ - `@charcoles/payments` standalone npm package — drop-in payment support for any Express app
14
+ - **Stripe adapter** — full payment intent lifecycle: create, refund, status check, webhook verification
15
+ - **LemonSqueezy adapter** — checkout sessions, order retrieval, refunds, and webhook HMAC verification
16
+ - Added specifically for Pakistani developers: Stripe does not support PKR payouts; LemonSqueezy provides merchant-of-record payments with full Pakistani bank payout support
17
+ - Adapter pattern for provider abstraction — switch between Stripe and LemonSqueezy via `PAYMENT_PROVIDER` env var
18
+ - New CLI prompt: "Include payments module?" with provider selection (Stripe / LemonSqueezy / Both)
19
+ - `src/modules/payments/` module in both JS and TS templates with:
20
+ - `payments.adapter.js/ts` — provider factory with singleton caching
21
+ - `payments.service.js/ts` — service layer with in-memory webhook deduplication
22
+ - `payments.controller.js/ts` — request handlers for all 4 endpoints
23
+ - `payments.routes.js/ts` — Express router with Swagger JSDoc comments
24
+ - `payments.schemas.js/ts` — Zod validation schemas for all request bodies
25
+ - `payments.constants.js/ts` — provider names, event names, webhook header names
26
+ - Four new API endpoints in scaffolded projects:
27
+ - `POST /payments/create-intent` — create Stripe PaymentIntent or LemonSqueezy checkout session
28
+ - `POST /payments/refund` — full or partial refunds
29
+ - `GET /payments/status/:paymentId` — normalized payment status across providers
30
+ - `POST /payments/webhook` — secure webhook event handling with signature verification
31
+ - Webhook raw body middleware auto-configured in `app.js` when payments module is selected
32
+ - Conditional route loading for payments (same `existsSync` pattern as auth and swagger)
33
+ - Payment env vars added to Zod env schema in both templates (all optional)
34
+ - Payment section added to `.env.example` in both templates with inline documentation
35
+ - Swagger JSDoc comments on all 4 payment endpoints
36
+ - `## Payments Module` section added to `SWAGGER_GUIDE.md` in both templates
37
+ - Payments section added to root `README.md` including migration guide for existing projects
38
+ - Integration tests for payments controller, service, schemas, and routes in both JS and TS templates
39
+ - `PaymentError` custom error class with `code` and `statusCode` fields, compatible with existing error handler
40
+
41
+ ## [2.2.2] – 2026-02-06
11
42
 
12
43
  #### ✨ **New Features**
13
44
 
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # Charcole API v2.2
1
+ # Charcole API v2.3
2
2
 
3
- > **Charcole v2.2 is a production-grade Node.js backend starter CLI that scaffolds enterprise-ready Express APIs with first-class TypeScript or JavaScript support, centralized error handling, Zod validation, structured logging, optional JWT authentication, **auto-generated Swagger documentation**, and a revolutionary repository pattern for database abstraction.**
3
+ > **Charcole v2.3 is a production-grade Node.js backend starter CLI that scaffolds enterprise-ready Express APIs with first-class TypeScript or JavaScript support, centralized error handling, Zod validation, structured logging, optional JWT authentication, **auto-generated Swagger documentation**, **optional payment processing (Stripe & LemonSqueezy)**, and a revolutionary repository pattern for database abstraction.**
4
4
 
5
5
  [![Node.js](https://img.shields.io/badge/Node.js-18+-green.svg)](https://nodejs.org/)
6
6
  [![Express.js](https://img.shields.io/badge/Express-4.18+-blue.svg)](https://expressjs.com/)
@@ -8,9 +8,25 @@
8
8
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
9
9
  [![License: ISC](https://img.shields.io/badge/License-ISC-yellow.svg)](LICENSE)
10
10
 
11
- ## What's New in v2.2
11
+ ## What's New in v2.3
12
12
 
13
- ### 🎯 Auto-Generated Swagger Documentation (@charcoles/swagger)
13
+ ### 💳 Payment Processing Module (@charcoles/payments)
14
+
15
+ Professional payment processing integrated directly into Charcole projects:
16
+
17
+ - **Stripe adapter** - Full payment intent lifecycle (create, refund, status, webhook verification)
18
+ - **LemonSqueezy adapter** - Perfect for Pakistani developers (Stripe doesn't support PKR payouts; LemonSqueezy provides merchant-of-record payments with full Pakistani bank payout support)
19
+ - **Adapter pattern** - Switch between Stripe and LemonSqueezy via `PAYMENT_PROVIDER` environment variable
20
+ - **Optional module** - Include/exclude during project creation with provider selection
21
+ - **Webhook handling** - Secure webhook event processing with signature verification
22
+ - **Full APIs included** - Four production-ready endpoints:
23
+ - `POST /api/payments/create-intent` - Create Stripe PaymentIntent or LemonSqueezy checkout session
24
+ - `POST /api/payments/refund` - Full or partial refunds
25
+ - `GET /api/payments/status/:paymentId` - Normalized payment status across providers
26
+ - `POST /api/payments/webhook` - Secure webhook event handling
27
+ - **Standalone package** - Use `@charcoles/payments` in any Express.js project
28
+
29
+ ### Previous: Auto-Generated Swagger Documentation (@charcoles/swagger)
14
30
 
15
31
  The game-changing feature that eliminates 60-80% of API documentation overhead:
16
32
 
@@ -108,6 +124,7 @@ npx create-charcole@latest
108
124
  # 1. Language: TypeScript or JavaScript
109
125
  # 2. JWT Authentication: Yes/No (includes complete auth system)
110
126
  # 3. Swagger Documentation: Yes/No (auto-generated from Zod schemas)
127
+ # 4. Payment Processing: Yes/No (Stripe, LemonSqueezy, or Both)
111
128
 
112
129
  # Configure environment
113
130
  cp .env.example .env
@@ -173,6 +190,77 @@ setupSwagger(app, {
173
190
 
174
191
  **See complete guide:** `src/lib/swagger/SWAGGER_GUIDE.md` (when swagger is enabled)
175
192
 
193
+ ## Payment Processing: Drop-in Payments for Any Provider
194
+
195
+ ### The Problem
196
+
197
+ Payment integration typically requires:
198
+
199
+ - Complex Stripe/payment provider setup
200
+ - Different APIs for different providers
201
+ - Manual webhook handling
202
+ - Difficult provider switching
203
+
204
+ ### The Solution
205
+
206
+ Charcole's payment module abstracts payment providers:
207
+
208
+ ```javascript
209
+ // Switch providers with one environment variable
210
+ PAYMENT_PROVIDER=stripe // Uses Stripe
211
+ PAYMENT_PROVIDER=lemonsqueezy // Uses LemonSqueezy
212
+
213
+ // Same API regardless of provider
214
+ POST /api/payments/create-intent
215
+ POST /api/payments/refund
216
+ GET /api/payments/status/:paymentId
217
+ POST /api/payments/webhook
218
+ ```
219
+
220
+ ### Why Two Providers?
221
+
222
+ **For Pakistani Developers:**
223
+
224
+ - ❌ Stripe - Does NOT support PKR payouts to Pakistani bank accounts
225
+ - ✅ LemonSqueezy - Merchant-of-record platform with full Pakistani bank payout support
226
+
227
+ **For Global Developers:**
228
+
229
+ - ✅ Stripe - Industry standard with global support
230
+ - ✅ LemonSqueezy - Alternative with regional payment solutions
231
+
232
+ ### Setup
233
+
234
+ 1. **During project creation:**
235
+
236
+ ```bash
237
+ npx create-charcole@latest my-api
238
+ # Select payment processing: Yes → Choose Stripe/LemonSqueezy/Both
239
+ ```
240
+
241
+ 2. **For existing projects:**
242
+
243
+ ```bash
244
+ npm install @charcoles/payments
245
+ ```
246
+
247
+ 3. **Configure environment:**
248
+ ```env
249
+ PAYMENT_PROVIDER=stripe # or lemonsqueezy
250
+ STRIPE_SECRET_KEY=sk_...
251
+ STRIPE_WEBHOOK_SECRET=whsec_...
252
+ LEMONSQUEEZY_API_KEY=...
253
+ LEMONSQUEEZY_WEBHOOK_SECRET=...
254
+ ```
255
+
256
+ ### Payment routes
257
+
258
+ Payment endpoints are automatically documented with Swagger when enabled:
259
+
260
+ - Routes: `/api/payments/create-intent`, `/api/payments/refund`, `/api/payments/status/{paymentId}`, `/api/payments/webhook`
261
+ - All routes use route-level `@swagger` comments and Zod schemas
262
+ - For webhook routes, raw JSON middleware is auto-configured: `app.use('/payments/webhook', express.raw({ type: 'application/json' }))`
263
+
176
264
  ## Repository Pattern: A Game Changer
177
265
 
178
266
  ### The Problem
@@ -349,6 +437,8 @@ We welcome contributions! Please:
349
437
  6. Update README.md for significant changes
350
438
  7. If adding Swagger docs, use Zod schemas as single source of truth
351
439
 
440
+ Good luck!!!
441
+
352
442
  ## 📄 License
353
443
 
354
444
  ISC
package/bin/index.js CHANGED
@@ -132,13 +132,31 @@ function copyDirRecursive(src, dest, excludeFiles = [], excludeDirs = []) {
132
132
  message: "Include auto-generated Swagger documentation?",
133
133
  initial: true,
134
134
  },
135
+ {
136
+ type: "confirm",
137
+ name: "includePayments",
138
+ message: "Include payments module? (Stripe / LemonSqueezy)",
139
+ initial: false,
140
+ },
141
+ {
142
+ type: (prev, values) => (values.includePayments ? "select" : null),
143
+ name: "paymentProvider",
144
+ message: "Which payment provider will you use?",
145
+ choices: [
146
+ { title: "Stripe (global)", value: "stripe" },
147
+ { title: "LemonSqueezy (Pakistan + global)", value: "lemonsqueezy" },
148
+ { title: "Both (I'll switch via env var)", value: "both" },
149
+ ],
150
+ initial: 0,
151
+ },
135
152
  );
136
153
 
137
154
  const responses = await prompts(questions);
138
155
 
139
156
  // Use command line project name if provided, otherwise use prompt response
140
157
  const projectName = projectNameFromArgs || responses.projectName;
141
- const { language, auth, swagger } = responses;
158
+ const { language, auth, swagger, includePayments, paymentProvider } =
159
+ responses;
142
160
 
143
161
  if (!projectName || projectName.trim() === "") {
144
162
  console.error("❌ Project name is required");
@@ -233,14 +251,14 @@ function copyDirRecursive(src, dest, excludeFiles = [], excludeDirs = []) {
233
251
  const swaggerPkgPath = path.join(swaggerModuleDir, "package.json");
234
252
  const swaggerTgzPath = path.join(
235
253
  swaggerModuleDir,
236
- "charcole-swagger-1.0.0.tgz",
254
+ "charcole-swagger-1.0.1.tgz",
237
255
  );
238
256
 
239
257
  // Copy tarball temporarily for npm install (will be cleaned up after)
240
258
  if (fs.existsSync(swaggerTgzPath)) {
241
259
  fs.copyFileSync(
242
260
  swaggerTgzPath,
243
- path.join(targetDir, "charcole-swagger-1.0.0.tgz"),
261
+ path.join(targetDir, "charcole-swagger-1.0.1.tgz"),
244
262
  );
245
263
  console.log("✓ Copied Swagger tarball temporarily for installation");
246
264
  } else {
@@ -347,6 +365,94 @@ function copyDirRecursive(src, dest, excludeFiles = [], excludeDirs = []) {
347
365
  }
348
366
  }
349
367
 
368
+ // Handle payments module if selected
369
+ if (includePayments) {
370
+ console.log("\n📦 Adding payments module...");
371
+
372
+ // The payments module is in src/modules/payments in the template
373
+ const paymentsModulePath = path.join(
374
+ templateDir,
375
+ "src",
376
+ "modules",
377
+ "payments",
378
+ );
379
+
380
+ if (!fs.existsSync(paymentsModulePath)) {
381
+ console.error(`❌ Payments module not found at ${paymentsModulePath}`);
382
+ } else {
383
+ // 1. Merge payments module's package.json
384
+ const paymentsPkgPath = path.join(paymentsModulePath, "package.json");
385
+
386
+ if (fs.existsSync(paymentsPkgPath)) {
387
+ try {
388
+ const paymentsPkg = JSON.parse(
389
+ fs.readFileSync(paymentsPkgPath, "utf-8"),
390
+ );
391
+ console.log("✓ Found payments module package.json");
392
+
393
+ mergedPkg = mergePackageJson(mergedPkg, paymentsPkg);
394
+ console.log("✓ Merged payments module dependencies");
395
+ console.log(
396
+ " Added dependencies:",
397
+ Object.keys(paymentsPkg.dependencies || {}).join(", "),
398
+ );
399
+ if (paymentsPkg.devDependencies) {
400
+ console.log(
401
+ " Added devDependencies:",
402
+ Object.keys(paymentsPkg.devDependencies).join(", "),
403
+ );
404
+ }
405
+ } catch (error) {
406
+ console.error(
407
+ `❌ Failed to parse payments module package.json:`,
408
+ error.message,
409
+ );
410
+ }
411
+ } else {
412
+ console.error(
413
+ "❌ Payments module package.json not found at:",
414
+ paymentsPkgPath,
415
+ );
416
+ }
417
+
418
+ const targetPaymentsPath = path.join(targetModulesDir, "payments");
419
+ console.log(
420
+ `Copying payments module to: ${targetPaymentsPath} (excluding package.json)`,
421
+ );
422
+
423
+ copyDirRecursive(
424
+ paymentsModulePath,
425
+ targetPaymentsPath,
426
+ ["package.json"],
427
+ [],
428
+ );
429
+ console.log(
430
+ "✓ Copied payments module files (package.json was excluded)",
431
+ );
432
+
433
+ const copiedPkgPath = path.join(targetPaymentsPath, "package.json");
434
+ if (fs.existsSync(copiedPkgPath)) {
435
+ console.warn(
436
+ "⚠️ package.json was accidentally copied, removing it...",
437
+ );
438
+ fs.unlinkSync(copiedPkgPath);
439
+ }
440
+ }
441
+ } else {
442
+ console.log("\n⏭️ Skipping payments module");
443
+
444
+ const targetPaymentsPath = path.join(
445
+ targetDir,
446
+ "src",
447
+ "modules",
448
+ "payments",
449
+ );
450
+ if (fs.existsSync(targetPaymentsPath)) {
451
+ console.log("Cleaning up payments directory (not selected)...");
452
+ fs.rmSync(targetPaymentsPath, { recursive: true, force: true });
453
+ }
454
+ }
455
+
350
456
  // Remove Swagger imports and setup from app file if not selected
351
457
  if (!swagger) {
352
458
  console.log("\n🧹 Removing Swagger references from app file...");
@@ -418,12 +524,76 @@ function copyDirRecursive(src, dest, excludeFiles = [], excludeDirs = []) {
418
524
  Object.keys(mergedPkg.devDependencies || {}).join(", "),
419
525
  );
420
526
 
527
+ // Create .env from .env.example and ensure APP_NAME default exists
528
+ try {
529
+ const exampleEnvPath = path.join(targetDir, ".env.example");
530
+ const envPath = path.join(targetDir, ".env");
531
+
532
+ if (fs.existsSync(exampleEnvPath) && !fs.existsSync(envPath)) {
533
+ let exampleContent = fs.readFileSync(exampleEnvPath, "utf-8");
534
+
535
+ if (!/APP_NAME\s*=/.test(exampleContent)) {
536
+ exampleContent = `APP_NAME=CHARCOLE API\n` + exampleContent;
537
+ }
538
+
539
+ if (includePayments) {
540
+ if (paymentProvider === "both") {
541
+ exampleContent = exampleContent.replace(
542
+ "PAYMENT_PROVIDER=",
543
+ 'PAYMENT_PROVIDER= # Set to "stripe" or "lemonsqueezy"',
544
+ );
545
+ } else {
546
+ exampleContent = exampleContent.replace(
547
+ "PAYMENT_PROVIDER=",
548
+ `PAYMENT_PROVIDER=${paymentProvider}`,
549
+ );
550
+ }
551
+ }
552
+
553
+ fs.writeFileSync(envPath, exampleContent, "utf-8");
554
+ console.log("✓ Created .env from .env.example with default APP_NAME");
555
+ }
556
+ } catch (err) {
557
+ console.warn("⚠️ Failed to create .env automatically:", err.message);
558
+ }
559
+
560
+ // Initialize git repository to make project git-friendly
561
+ try {
562
+ const { execSync } = require("child_process");
563
+
564
+ execSync("git --version", { stdio: "ignore" });
565
+ execSync("git init", { cwd: targetDir, stdio: "ignore" });
566
+
567
+ // Ensure .gitignore exists (copy from template if missing)
568
+ const gitignoreSrc = path.join(templateDir, ".gitignore");
569
+ const gitignoreDest = path.join(targetDir, ".gitignore");
570
+ if (!fs.existsSync(gitignoreDest) && fs.existsSync(gitignoreSrc)) {
571
+ fs.copyFileSync(gitignoreSrc, gitignoreDest);
572
+ }
573
+
574
+ // Stage files and attempt initial commit; ignore commit errors (e.g., missing git user config)
575
+ try {
576
+ execSync("git add .", { cwd: targetDir, stdio: "ignore" });
577
+ execSync('git commit -m "chore: initial commit from Charcole"', {
578
+ cwd: targetDir,
579
+ stdio: "ignore",
580
+ });
581
+ console.log("✓ Initialized git repository and created initial commit");
582
+ } catch (commitErr) {
583
+ console.log(
584
+ "✓ Initialized git repository (skipped commit — configure git user to enable commits)",
585
+ );
586
+ }
587
+ } catch (gitErr) {
588
+ console.log("ℹ️ Git not available; skipping repository initialization");
589
+ }
590
+
421
591
  console.log(`\n📦 Installing dependencies using ${pkgManager}...`);
422
592
  installDependencies(targetDir, pkgManager);
423
593
 
424
594
  // Clean up the swagger tarball after installation
425
595
  if (swagger) {
426
- const tgzPath = path.join(targetDir, "charcole-swagger-1.0.0.tgz");
596
+ const tgzPath = path.join(targetDir, "charcole-swagger-1.0.1.tgz");
427
597
  if (fs.existsSync(tgzPath)) {
428
598
  fs.unlinkSync(tgzPath);
429
599
  console.log("✓ Cleaned up temporary Swagger tarball");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-charcole",
3
- "version": "2.2.1",
3
+ "version": "2.3.0",
4
4
  "description": "CLI to create production-ready Node.js Express APIs with TypeScript/JavaScript, JWT auth, auto-generated Swagger docs, and repository pattern",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -9,10 +9,10 @@
9
9
  },
10
10
  "repository": {
11
11
  "type": "git",
12
- "url": "https://github.com/sheraz4196/create-charcole.git"
12
+ "url": "https://github.com/sheraz4196/charcole.git"
13
13
  },
14
14
  "bugs": {
15
- "url": "https://github.com/sheraz4196/create-charcole/issues"
15
+ "url": "https://github.com/sheraz4196/charcole/issues"
16
16
  },
17
17
  "homepage": "https://www.charcole.site/",
18
18
  "bin": {
@@ -0,0 +1,14 @@
1
+ # Changelog
2
+
3
+ ## [1.0.0] - 2024-04-29
4
+
5
+ ### Added
6
+
7
+ - Initial release
8
+ - StripeAdapter: createPayment, refundPayment, getPaymentStatus, verifyWebhook
9
+ - LemonSqueezyAdapter: same interface with regional payment support
10
+ - setupPayments() Express integration function
11
+ - Zod validation for all inputs
12
+ - In-memory webhook deduplication
13
+ - Full TypeScript definitions
14
+ - Comprehensive test suite with Vitest
@@ -0,0 +1,222 @@
1
+ # @charcoles/payments
2
+
3
+ Drop-in payment processing for Express apps. Supports Stripe and LemonSqueezy for regional payments (Pakistan, etc.).
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @charcoles/payments
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ### Standalone Express App
14
+
15
+ ```js
16
+ import express from "express";
17
+ import { setupPayments } from "@charcoles/payments";
18
+
19
+ const app = express();
20
+
21
+ // IMPORTANT: Register webhook raw body middleware BEFORE express.json()
22
+ app.use("/payments/webhook", express.raw({ type: "application/json" }));
23
+
24
+ // Global JSON parsing for other routes
25
+ app.use(express.json());
26
+
27
+ // Setup payments
28
+ setupPayments(app, {
29
+ provider: "lemonsqueezy",
30
+ lemonSqueezyApiKey: process.env.LEMONSQUEEZY_API_KEY,
31
+ lemonSqueezyWebhookSecret: process.env.LEMONSQUEEZY_WEBHOOK_SECRET,
32
+ lemonSqueezyStoreId: process.env.LEMONSQUEEZY_STORE_ID,
33
+ });
34
+
35
+ app.listen(3000);
36
+ ```
37
+
38
+ ### Environment Variables
39
+
40
+ | Variable | Stripe | LemonSqueezy | Description |
41
+ | ----------------------------- | ------ | ------------ | ---------------------------------- |
42
+ | `PAYMENT_PROVIDER` | ✅ | ✅ | `"stripe"` or `"lemonsqueezy"` |
43
+ | `STRIPE_SECRET_KEY` | ✅ | ❌ | `sk_live_...` or `sk_test_...` |
44
+ | `STRIPE_WEBHOOK_SECRET` | ✅ | ❌ | `whsec_...` from Stripe dashboard |
45
+ | `LEMONSQUEEZY_API_KEY` | ❌ | ✅ | From LS API settings |
46
+ | `LEMONSQUEEZY_WEBHOOK_SECRET` | ❌ | ✅ | From LS webhook settings |
47
+ | `LEMONSQUEEZY_STORE_ID` | ❌ | ✅ | Numeric store ID from LS dashboard |
48
+
49
+ ### Stripe Configuration
50
+
51
+ ```js
52
+ setupPayments(app, {
53
+ provider: "stripe",
54
+ stripeSecretKey: "sk_live_...",
55
+ stripeWebhookSecret: "whsec_...",
56
+ });
57
+ ```
58
+
59
+ ### LemonSqueezy Configuration
60
+
61
+ ```js
62
+ setupPayments(app, {
63
+ provider: "lemonsqueezy",
64
+ lemonSqueezyApiKey: "your_api_key",
65
+ lemonSqueezyWebhookSecret: "your_webhook_secret",
66
+ lemonSqueezyStoreId: "12345",
67
+ });
68
+ ```
69
+
70
+ ## API Endpoints
71
+
72
+ ### POST /payments/create-intent
73
+
74
+ Create a payment intent or checkout session.
75
+
76
+ **Request:**
77
+
78
+ ```json
79
+ {
80
+ "amount": 2999,
81
+ "currency": "usd",
82
+ "metadata": {
83
+ "orderId": "order_123"
84
+ }
85
+ }
86
+ ```
87
+
88
+ **Response (Stripe):**
89
+
90
+ ```json
91
+ {
92
+ "success": true,
93
+ "data": {
94
+ "id": "pi_3abc...",
95
+ "clientSecret": "pi_..._secret_...",
96
+ "status": "requires_payment_method",
97
+ "amount": 2999,
98
+ "currency": "usd"
99
+ }
100
+ }
101
+ ```
102
+
103
+ **Response (LemonSqueezy):**
104
+
105
+ ```json
106
+ {
107
+ "success": true,
108
+ "data": {
109
+ "id": "12345",
110
+ "checkoutUrl": "https://app.lemonsqueezy.com/checkout/...",
111
+ "status": "created",
112
+ "amount": 2999,
113
+ "currency": "usd"
114
+ }
115
+ }
116
+ ```
117
+
118
+ ### POST /payments/refund
119
+
120
+ Refund a payment.
121
+
122
+ **Request:**
123
+
124
+ ```json
125
+ {
126
+ "paymentId": "pi_3abc...",
127
+ "amount": 1500
128
+ }
129
+ ```
130
+
131
+ ### GET /payments/status/:paymentId
132
+
133
+ Get payment status.
134
+
135
+ **Response:**
136
+
137
+ ```json
138
+ {
139
+ "success": true,
140
+ "data": {
141
+ "id": "pi_3abc...",
142
+ "status": "paid",
143
+ "amount": 2999,
144
+ "currency": "usd"
145
+ }
146
+ }
147
+ ```
148
+
149
+ ### POST /payments/webhook
150
+
151
+ Receive provider webhooks. Returns `{"received": true}`.
152
+
153
+ ## LemonSqueezy Notes
154
+
155
+ ### Regional Support
156
+
157
+ LemonSqueezy is included specifically for developers in regions where Stripe payouts are not available (e.g., Pakistan). LemonSqueezy uses a merchant-of-record model, so you sell through their entity and receive bank transfers.
158
+
159
+ ### Variant IDs Required
160
+
161
+ Unlike Stripe (which accepts raw amounts), LemonSqueezy requires a Product Variant ID. Create a "Pay What You Want" product in your LemonSqueezy dashboard and pass the variant ID in `metadata.variantId`:
162
+
163
+ ```json
164
+ {
165
+ "amount": 10000,
166
+ "currency": "pkr",
167
+ "metadata": {
168
+ "variantId": "12345"
169
+ }
170
+ }
171
+ ```
172
+
173
+ ### Raw Body Middleware Warning
174
+
175
+ ⚠️ **Critical**: Webhook endpoints require raw `Buffer` bodies for signature verification. Always register `express.raw({ type: 'application/json' })` on the webhook path **before** `express.json()`:
176
+
177
+ ```js
178
+ // ✅ Correct order
179
+ app.use("/payments/webhook", express.raw({ type: "application/json" }));
180
+ app.use(express.json()); // Global JSON parsing
181
+
182
+ // ❌ Wrong order — webhooks will fail
183
+ app.use(express.json());
184
+ app.use("/payments/webhook", express.raw({ type: "application/json" }));
185
+ ```
186
+
187
+ ## TypeScript Support
188
+
189
+ Full TypeScript definitions included:
190
+
191
+ ```ts
192
+ import {
193
+ setupPayments,
194
+ PaymentAdapter,
195
+ StripeAdapter,
196
+ } from "@charcoles/payments";
197
+
198
+ interface MyOptions {
199
+ provider: "stripe";
200
+ stripeSecretKey: string;
201
+ stripeWebhookSecret: string;
202
+ }
203
+
204
+ setupPayments(app, options);
205
+ ```
206
+
207
+ ## Error Handling
208
+
209
+ All errors are `PaymentError` instances with specific codes:
210
+
211
+ - `CONFIG_ERROR`: Missing required configuration
212
+ - `WEBHOOK_INVALID`: Invalid webhook signature
213
+ - `STRIPE_ERROR`: Stripe API error
214
+ - `LS_CHECKOUT_FAILED`: LemonSqueezy checkout creation failed
215
+ - `LS_REFUND_FAILED`: LemonSqueezy refund failed
216
+ - `LS_ORDER_NOT_FOUND`: LemonSqueezy order not found
217
+ - `MISSING_VARIANT_ID`: LemonSqueezy variantId not provided
218
+ - `PROVIDER_NOT_CONFIGURED`: PAYMENT_PROVIDER env var missing
219
+
220
+ ## License
221
+
222
+ ISC