Docus Logo
Charcoles Payments

Setup

Get the payments module running in your project with either the Charcole CLI or manual setup.

Setup with Charcole CLI

If you're creating a new Charcole project, payments are the easiest to set up.

Run the create command:

npx create-charcole@latest

When the CLI asks "Include payments module?", select Yes.

Then choose your provider:

? Choose a payment provider
❯ Stripe
  LemonSqueezy

The CLI generates everything: env vars, controllers, webhooks, and adapter switching logic. All you need to do is add credentials to .env.


Setup in Existing Charcole Project

If you already have a Charcole project and want to add payments later:

npm install @charcoles/payments

Then in your main app.js file, add the raw body middleware before express.json():

import express from 'express'
import { setupPayments } from '@charcoles/payments'

const app = express()

// ⚠️ Must come BEFORE express.json()
app.use('/payments/webhook', express.raw({ type: 'application/json' }))

app.use(express.json())
// ... rest of your middleware

setupPayments(app)

app.listen(3000)

Then configure environment variables (see Environment Variables).


The Critical Raw Body Middleware

The webhook route requires the raw request body before Express parses it. Register express.raw() on /payments/webhookbeforeapp.use(express.json()). Reversing the order will silently break webhook signature verification.Webhook signature verification needs the exact raw bytes from the provider (Stripe or LemonSqueezy). If Express parses the body into JSON first, the raw bytes are gone, and signature verification fails. Providers will consider unsigned webhooks a security breach and reject them.This is the #1 setup mistake. If your webhooks aren't firing, check this first.

Verify It Works

Start your server:

npm run dev

Check that the endpoints respond. Using a JWT token for your app:

curl -X POST http://localhost:3000/payments/create-intent \
  -H "Authorization: Bearer your-jwt-token" \
  -H "Content-Type: application/json" \
  -d '{"amount": 1000, "currency": "usd"}'

You should get back a response with either a clientSecret (Stripe) or checkoutUrl (LemonSqueezy).

If you get a 404, check that the module initialized correctly.

If you get a 401, the JWT verification failed — this is expected if you haven't authenticated properly. That's not a setup issue.


What Comes Next