API Endpoints
POST /payments/create-intent
Create a payment intent. This endpoint returns either a clientSecret (Stripe) or checkoutUrl (LemonSqueezy) for the frontend to proceed with payment.
Authentication: JWT required (Bearer token)
Request
{
"amount": 2999,
"currency": "usd",
"metadata": {
"orderId": "order_123",
"variantId": "78901"
}
}
Fields:
amount(number, required) — Amount in the smallest currency unit (cents for USD, paisas for PKR). Example:2999= $29.99currency(string, required) — ISO 4217 currency code, 3 letters, case-insensitive. Examples:usd,pkr,gbpmetadata(object, optional) — Custom data to attach to the paymentorderId— Your internal order ID (optional but recommended)variantId(string) — LemonSqueezy only — Required for LemonSqueezy, ignored by Stripe. The product variant ID in your LemonSqueezy store.
Response (Stripe)
{
"success": true,
"data": {
"id": "pi_3abc...",
"clientSecret": "pi_3abc..._secret_xyz",
"status": "requires_payment_method",
"amount": 2999,
"currency": "usd"
}
}
Pass clientSecret to Stripe.js on the frontend to confirm the payment.
Response (LemonSqueezy)
{
"success": true,
"data": {
"id": "abc123",
"checkoutUrl": "https://store.lemonsqueezy.com/checkout/buy/...",
"status": "created",
"amount": 2999,
"currency": "usd"
}
}
Redirect the user to checkoutUrl to complete checkout on LemonSqueezy's hosted page.
POST /payments/refund
Refund a payment, either fully or partially.
Authentication: JWT required (Bearer token)
Request
{
"paymentId": "pi_3abc...",
"amount": 1000
}
Fields:
paymentId(string, required) — The payment ID to refundamount(number, optional) — Refund amount in smallest currency unit. Omit for full refund.
Response
{
"success": true,
"data": {
"id": "re_456...",
"status": "succeeded",
"amount": 1000
}
}
GET /payments/status/:paymentId
Check the current status of a payment.
Authentication: JWT required (Bearer token)
Request
GET /payments/status/pi_3abc...
No request body.
Response
{
"success": true,
"data": {
"id": "pi_3abc...",
"status": "paid",
"amount": 2999,
"currency": "usd"
}
}
Status values (normalized across both providers):
| Status | Meaning |
|---|---|
pending | Payment created but not yet confirmed |
paid | Payment successfully charged |
failed | Payment failed or was cancelled |
refunded | Payment was refunded |
POST /payments/webhook
Receive and process payment events from Stripe or LemonSqueezy.
Authentication: None — authenticated by provider signature (Stripe-Signature or X-Signature header)
This endpoint is called by your payment provider, not your frontend. Always return 200 OK to acknowledge receipt.
Signature Verification
The module automatically verifies webhook signatures using STRIPE_WEBHOOK_SECRET or LEMONSQUEEZY_WEBHOOK_SECRET. Unsigned or tampered webhooks are rejected.
Events
Stripe Events:
payment_intent.succeeded— Payment confirmed, fulfill orderpayment_intent.payment_failed— Payment failedcharge.refunded— Refund processed
LemonSqueezy Events:
order_created— Payment confirmed, fulfill orderorder_refunded— Refund processedsubscription_cancelled— Subscription ended
Response
Always return 200 OK:
{
"received": true
}
Webhook Reliability
Never rely on frontend callbacks alone. Users close tabs, lose internet, and refresh pages. The only reliable confirmation is the webhook.
Always fulfill orders based on webhook events, not on frontend callbacks. Your server's controller handles deduplication automatically — if the same event arrives twice, it processes only once.
Example Usage
Create a Stripe Payment
curl -X POST http://localhost:3000/payments/create-intent \
-H "Authorization: Bearer your-jwt-token" \
-H "Content-Type: application/json" \
-d '{
"amount": 2999,
"currency": "usd",
"metadata": {"orderId": "order_123"}
}'
Create a LemonSqueezy Payment
curl -X POST http://localhost:3000/payments/create-intent \
-H "Authorization: Bearer your-jwt-token" \
-H "Content-Type: application/json" \
-d '{
"amount": 2999,
"currency": "usd",
"metadata": {"variantId": "78901", "orderId": "order_456"}
}'
Check Payment Status
curl -X GET http://localhost:3000/payments/status/pi_3abc... \
-H "Authorization: Bearer your-jwt-token"
Refund a Payment
curl -X POST http://localhost:3000/payments/refund \
-H "Authorization: Bearer your-jwt-token" \
-H "Content-Type: application/json" \
-d '{"paymentId": "pi_3abc..."}'