Docus Logo
Charcoles Payments

Using Without Charcole

Install the payments module into any Express application.

For Existing Express Apps

If you have an existing Express application and want to add @charcoles/payments without using the Charcole CLI, install the package directly.


Installation

npm install @charcoles/payments

Setup

The critical requirement is middleware ordering. Register 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, {
  provider: 'stripe',
  stripeSecretKey: process.env.STRIPE_SECRET_KEY,
  stripeWebhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
})

app.listen(3000)

If you reverse the order, webhook signature verification will fail silently.


Configuration Options

The setupPayments() function accepts an options object:

setupPayments(app, {
  provider: 'stripe',              // or 'lemonsqueezy'
  stripeSecretKey: '...',
  stripeWebhookSecret: '...',
  lemonSqueezyApiKey: '...',
  lemonSqueezyWebhookSecret: '...',
  lemonSqueezyStoreId: '...',
  mountPath: '/payments',          // default — change if needed
})

Stripe Setup

setupPayments(app, {
  provider: 'stripe',
  stripeSecretKey: process.env.STRIPE_SECRET_KEY,
  stripeWebhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
})

All endpoints are live at /payments/*:

  • POST /payments/create-intent
  • POST /payments/refund
  • GET /payments/status/:paymentId
  • POST /payments/webhook

LemonSqueezy Setup

setupPayments(app, {
  provider: 'lemonsqueezy',
  lemonSqueezyApiKey: process.env.LEMONSQUEEZY_API_KEY,
  lemonSqueezyWebhookSecret: process.env.LEMONSQUEEZY_WEBHOOK_SECRET,
  lemonSqueezyStoreId: process.env.LEMONSQUEEZY_STORE_ID,
})

Custom Mount Path

If you want to mount payment endpoints at a different path, use the mountPath option:

setupPayments(app, {
  provider: 'stripe',
  stripeSecretKey: process.env.STRIPE_SECRET_KEY,
  stripeWebhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
  mountPath: '/api/payments',      // endpoints at /api/payments/*
})

Using Adapters Directly

If you only need the adapter logic without the Express routes, instantiate adapters directly:

import { StripeAdapter, LemonSqueezyAdapter } from '@charcoles/payments'

// Stripe
const stripeAdapter = new StripeAdapter({
  secretKey: process.env.STRIPE_SECRET_KEY,
  webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
})

const result = await stripeAdapter.createPayment({
  amount: 2999,
  currency: 'usd',
})

console.log(result.data.clientSecret)
// LemonSqueezy
const lsAdapter = new LemonSqueezyAdapter({
  apiKey: process.env.LEMONSQUEEZY_API_KEY,
  webhookSecret: process.env.LEMONSQUEEZY_WEBHOOK_SECRET,
  storeId: process.env.LEMONSQUEEZY_STORE_ID,
})

const result = await lsAdapter.createPayment({
  amount: 2999,
  currency: 'usd',
  metadata: { variantId: '78901' },
})

console.log(result.data.checkoutUrl)

Available adapter methods:

  • createPayment()
  • refundPayment()
  • getPaymentStatus()
  • verifyWebhookSignature()

TypeScript Support

The package ships with TypeScript types (index.d.ts). All types are available:

import type {
  CreatePaymentResult,
  RefundPaymentResult,
  PaymentStatus,
  SetupPaymentsOptions,
  PaymentEvent,
  StripeAdapter,
  LemonSqueezyAdapter,
} from '@charcoles/payments'

const options: SetupPaymentsOptions = {
  provider: 'stripe',
  stripeSecretKey: '...',
  stripeWebhookSecret: '...',
}

setupPayments(app, options)

Environment Variables

Set these in your .env:

PAYMENT_PROVIDER=stripe

# Stripe
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...

# Or LemonSqueezy
LEMONSQUEEZY_API_KEY=...
LEMONSQUEEZY_WEBHOOK_SECRET=...
LEMONSQUEEZY_STORE_ID=12345

See Environment Variables for complete reference.


Webhook Handling

Webhooks are handled automatically by the module. The webhook route verifies signatures and ensures events are processed only once (in-memory deduplication).

To add custom fulfillment logic, you'll need to extend the module or implement a separate webhook handler. Refer to Webhooks for event types.


What Comes Next