Skip to content

Pagamentos e Finanças

Cenários reais para integração de pagamentos, faturamento e funcionalidades financeiras.

Checkout com Stripe

Objetivo: Adicionar checkout de pagamento com Stripe.

Comando:

Integrate Stripe checkout for one-time payments

O que acontece:

  1. Cria o serviço de pagamento
  2. Implementa a sessão de checkout
  3. Adiciona o tratamento de webhooks
  4. Cria a UI de confirmação de pagamento

Resultado:

typescript
// Backend: Create checkout session
app.post('/api/checkout', async (req, res) => {
  const { items, successUrl, cancelUrl } = req.body

  const session = await stripe.checkout.sessions.create({
    payment_method_types: ['card'],
    line_items: items.map(item => ({
      price_data: {
        currency: 'usd',
        product_data: { name: item.name },
        unit_amount: item.price * 100  // cents
      },
      quantity: item.quantity
    })),
    mode: 'payment',
    success_url: successUrl,
    cancel_url: cancelUrl
  })

  res.json({ sessionId: session.id })
})

// Backend: Handle webhook
app.post('/api/webhooks/stripe', async (req, res) => {
  const event = stripe.webhooks.constructEvent(
    req.body,
    req.headers['stripe-signature'],
    process.env.STRIPE_WEBHOOK_SECRET
  )

  switch (event.type) {
    case 'checkout.session.completed':
      await handlePaymentSuccess(event.data.object)
      break
    case 'payment_intent.payment_failed':
      await handlePaymentFailed(event.data.object)
      break
  }

  res.json({ received: true })
})

// Frontend: Redirect to checkout
async function handleCheckout(items) {
  const response = await api.post('/checkout', {
    items,
    successUrl: `${window.location.origin}/success`,
    cancelUrl: `${window.location.origin}/cart`
  })

  const stripe = await loadStripe(STRIPE_PUBLIC_KEY)
  await stripe.redirectToCheckout({
    sessionId: response.sessionId
  })
}

Cobrança por Assinatura

Objetivo: Adicionar planos de assinatura mensais.

Comando:

Implement subscription billing with monthly and yearly plans

Resultado:

typescript
// Plans
const plans = {
  basic: {
    monthly: 'price_basic_monthly',   // $9/mo
    yearly: 'price_basic_yearly'      // $90/yr
  },
  pro: {
    monthly: 'price_pro_monthly',     // $29/mo
    yearly: 'price_pro_yearly'        // $290/yr
  }
}

// Create subscription
app.post('/api/subscribe', async (req, res) => {
  const { planId, billingCycle, userId } = req.body

  // Get or create Stripe customer
  let customer = await getStripeCustomer(userId)
  if (!customer) {
    customer = await stripe.customers.create({
      email: user.email,
      metadata: { userId }
    })
  }

  // Create subscription
  const subscription = await stripe.subscriptions.create({
    customer: customer.id,
    items: [{ price: plans[planId][billingCycle] }],
    payment_behavior: 'default_incomplete',
    expand: ['latest_invoice.payment_intent']
  })

  res.json({
    subscriptionId: subscription.id,
    clientSecret: subscription.latest_invoice.payment_intent.client_secret
  })
})

// Handle subscription events
app.post('/api/webhooks/stripe', async (req, res) => {
  switch (event.type) {
    case 'customer.subscription.created':
      await activateSubscription(event.data.object)
      break
    case 'customer.subscription.deleted':
      await cancelSubscription(event.data.object)
      break
    case 'invoice.payment_failed':
      await handleFailedPayment(event.data.object)
      break
  }
})

// Check subscription status
function hasActiveSubscription(user) {
  return user.subscription?.status === 'active'
}

function canAccessFeature(user, feature) {
  const plan = user.subscription?.plan
  return planFeatures[plan]?.includes(feature)
}

Geração de Faturas

Objetivo: Gerar faturas em PDF para pedidos.

Comando:

Create invoice generation system with PDF export

Resultado:

typescript
// Invoice data structure
interface Invoice {
  number: string      // INV-2024-00001
  date: Date
  dueDate: Date
  customer: {
    name: string
    email: string
    address: string
  }
  items: Array<{
    description: string
    quantity: number
    unitPrice: number
    total: number
  }>
  subtotal: number
  tax: number
  total: number
  status: 'draft' | 'sent' | 'paid' | 'overdue'
}

// Generate invoice number
function generateInvoiceNumber(): string {
  const year = new Date().getFullYear()
  const count = await Invoice.count({ year })
  return `INV-${year}-${String(count + 1).padStart(5, '0')}`
}

// Create invoice
app.post('/api/invoices', async (req, res) => {
  const invoice = await createInvoice({
    ...req.body,
    number: await generateInvoiceNumber(),
    status: 'draft'
  })

  res.json(invoice)
})

// Generate PDF
app.get('/api/invoices/:id/pdf', async (req, res) => {
  const invoice = await Invoice.findById(req.params.id)
  const pdf = await generateInvoicePDF(invoice)

  res.setHeader('Content-Type', 'application/pdf')
  res.setHeader('Content-Disposition',
    `attachment; filename="${invoice.number}.pdf"`)
  res.send(pdf)
})

// Send invoice email
app.post('/api/invoices/:id/send', async (req, res) => {
  const invoice = await Invoice.findById(req.params.id)
  const pdf = await generateInvoicePDF(invoice)

  await sendEmail({
    to: invoice.customer.email,
    subject: `Invoice ${invoice.number}`,
    template: 'invoice',
    data: { invoice },
    attachments: [{ filename: `${invoice.number}.pdf`, content: pdf }]
  })

  await Invoice.updateStatus(invoice.id, 'sent')
  res.json({ sent: true })
})

Dashboard Financeiro

Objetivo: Criar um dashboard de receita e métricas.

Comando:

Create financial dashboard with MRR, churn, and revenue metrics

Resultado:

typescript
// Metrics
interface FinancialMetrics {
  mrr: number                    // Monthly Recurring Revenue
  arr: number                    // Annual Recurring Revenue
  churnRate: number              // % customers lost per month
  ltv: number                    // Lifetime Value
  newMrr: number                 // From new customers this month
  expansionMrr: number           // From upgrades
  contractionMrr: number         // From downgrades
  churnedMrr: number             // From cancellations
}

// Calculate MRR
async function calculateMRR(): Promise<number> {
  const activeSubscriptions = await Subscription.find({
    status: 'active'
  })

  return activeSubscriptions.reduce((sum, sub) => {
    const monthlyAmount = sub.billingCycle === 'yearly'
      ? sub.amount / 12
      : sub.amount
    return sum + monthlyAmount
  }, 0)
}

// Calculate churn rate
async function calculateChurnRate(month: Date): Promise<number> {
  const startOfMonth = new Date(month.getFullYear(), month.getMonth(), 1)
  const endOfMonth = new Date(month.getFullYear(), month.getMonth() + 1, 0)

  const customersAtStart = await Customer.count({
    createdAt: { $lt: startOfMonth },
    cancelledAt: { $gte: startOfMonth }
  })

  const customersCancelled = await Customer.count({
    cancelledAt: {
      $gte: startOfMonth,
      $lte: endOfMonth
    }
  })

  return (customersCancelled / customersAtStart) * 100
}

// Dashboard endpoint
app.get('/api/dashboard/financial', async (req, res) => {
  const [mrr, churnRate, revenueByMonth] = await Promise.all([
    calculateMRR(),
    calculateChurnRate(new Date()),
    getRevenueByMonth(12)
  ])

  res.json({
    mrr,
    arr: mrr * 12,
    churnRate,
    ltv: mrr / (churnRate / 100),
    revenueChart: revenueByMonth
  })
})

Dicas Rápidas

Valores Monetários em Centavos

typescript
// Sempre armazene valores como inteiros (centavos)
const price = 1999        // $19.99
const total = price * qty // Sem erros de ponto flutuante

// Converta para exibição
const display = (price / 100).toFixed(2) // "19.99"

Idempotência de Webhooks

typescript
// Armazene os IDs de webhooks já processados
app.post('/webhooks', async (req, res) => {
  const eventId = req.body.id

  if (await isEventProcessed(eventId)) {
    return res.json({ received: true })
  }

  await processEvent(req.body)
  await markEventProcessed(eventId)

  res.json({ received: true })
})

Cenários Relacionados

Publicado sob a Licença MIT.