Dunas dos Lençóis Maranhenses à noite, em pontilhado

O billing do seu em uma biblioteca.

Defina planos, assinaturas e limites em TypeScript. O dunas cuida das cobranças, renovações e webhooks.

Começar
import { createDunas, pg, woovi } from "dunas"
import { free, pro } from "./plans"

export const billing = createDunas({
  database: pg(pool),
  plans: [free, pro],
  providers: [
    woovi({ appId: env.WOOVI_APP_ID }),
  ],
})
import { feature, plan } from "dunas"

export const messages = feature({ id: "messages", type: "metered" })

export const free = plan({ id: "free", default: true })

export const pro = plan({
  id: "pro",
  price: { amountInCents: 4_990, interval: "month" },
  includes: [messages({ limit: 5_000 })],
})
import { billing } from "./dunas"

export async function POST(request: Request) {
  const user = await getUser(request)
  const { allowed } = await billing.consume({
    customerId: user.id,
    featureId: "messages",
  })
  if (!allowed) return new Response(null, { status: 402 })
  return Response.json(await reply(user))
}

Cada uso conta. O limite é seu.

O dunas mede cada uso contra o plano do cliente, cobra a assinatura e zera tudo na renovação.

Free · 55 / 100

Do plano à cobrança em três chamadas.

Defina os planos em TypeScript, assine e limite o uso na rota. Webhooks, cron e renovação rodam dentro do dunas.

  1. 01

    Defina os planos

    Features, limites e preços ficam num arquivo do seu repositório. Cada id vira tipo.

    billing/plans.ts
    import { feature, plan } from "dunas"
    
    const messages = feature({ id: "messages", type: "metered" })
    
    export const free = plan({ id: "free", default: true, includes: [messages({ limit: 100 })] })
    export const pro = plan({
      id: "pro",
      price: { amountInCents: 4_990, interval: "month" },
      includes: [messages({ limit: 5_000 })],
    })
    →planId: "free" | "pro"
  2. 02

    Assine

    O cliente assina e recebe a cobrança do gateway escolhido. O navegador espera o pagamento, e todo mês a renovação acontece sozinha.

    app/assinar/page.tsx
    const result = await dunas.subscribe({ planId: "pro" })
    
    if (result.status === "pending_payment") {
      showCharge(result.charge)
      await dunas.waitForPayment({ chargeId: result.charge.id })
    }
    →{ status: "paid", provider: "woovi" }
  3. 03

    Limite o uso

    Cada uso passa pelo limite do plano antes de ser registrado. A checagem é uma query no seu Postgres.

    app/api/chat/route.ts
    const { allowed } = await billing.consume({
      customerId: user.id,
      featureId: "messages",
    })
    
    if (!allowed) return Response.json({ error: "Limite do plano atingido" }, { status: 402 })
    402Limite do plano atingido

O resto, já resolvido.

Tudo o que dá trabalho em cobrança recorrente, dentro da biblioteca.

await billing.check({
  customerId,
  featureId: "messages",
})
// { allowed: true, balance: 4812 }

Postgres ou PGlite

billing.subscribe({ planId: "pr0" })

error Type '"pr0"' is not assignable to type '"free" | "pro"'.

Free
R$ 0
Plano atual
current
Pro
R$ 49,90/mês
Assinar
upgrade
Aguardando pagamentopending_payment
Pago · R$ 49,90active
import { All, Controller, Req, Res } from "@nestjs/common"
import type { Request, Response } from "express"
import { toNodeHandler } from "dunas/node"

@Controller("api/dunas")
export class DunasController {
  private readonly handler = toNodeHandler(billing.handler)

  @All("*path")
  handle(@Req() req: Request, @Res() res: Response) {
    return this.handler(req, res)
  }
}
  • Cliente do navegador
  • Webhook Woovi
  • Cron de renovação
consumemsg_81f2+1
retrymsg_81f2já contado
retrymsg_81f2já contado
createDunas({
  // …
  on: {
    "charge.created": ({ charge }) => sendEmail(charge),
    "subscription.canceled": ({ subscription }) => notifyChurn(subscription),
  },
})
  • charge.created
  • subscription.activated
  • subscription.renewed
  • subscription.past_due
  • subscription.canceled

Roda no seu app.
É seu.

Código aberto, licença MIT. Inspirado no paykit.

Funciona com o que você já usa

Dunas dos Lençóis Maranhenses ao amanhecer, em pontilhado