Integrações essenciais: pagamento, e-mail, mapas e storage em 1 semana

Guia completo de integrações obrigatórias para MVP: Stripe, SendGrid, Google Maps e Cloudinary com código pronto e custos reais.

MVP sem integrações = produto pela metade.

Realidade: 80% dos MVPs precisam de pelo menos 3 integrações:

  1. Pagamento (Stripe, Mercado Pago)
  2. E-mail transacional (SendGrid, Resend)
  3. Storage de arquivos (Cloudinary, AWS S3)

Mais 40% precisam de: 4. Mapas/geolocalização (Google Maps) 5. SMS (Twilio) 6. Videoconferência (Daily.co, Zoom)

Este artigo mostra implementação completa das 3 integrações essenciais — do zero ao funcionando em menor que 8h cada, com código pronto e custos reais.

1. Pagamentos: Stripe (implementação completa)

Por que Stripe: Melhor API, aceita cartão internacional, compliance automático (PCI-DSS).

Alternativa BR: Mercado Pago (aceita apenas LATAM, API pior).

Setup inicial (15min)

# 1. Criar conta em stripe.com/br
# 2. Pegar chaves API (Dashboard → Developers → API keys)

# 3. Instalar SDK
npm install stripe @stripe/stripe-js

# 4. Configurar env
# .env.local
STRIPE_SECRET_KEY=sk_test_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...

Checkout básico (one-time payment)

Frontend (Next.js):

// app/checkout/page.tsx
'use client';
import { loadStripe } from '@stripe/stripe-js';

const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);

export default function CheckoutPage() {
  const handleCheckout = async () => {
    const res = await fetch('/api/create-checkout-session', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        amount: 19900, // R$ 199,00 em centavos
        description: 'Plano Premium - Mensal',
      }),
    });

    const { sessionId } = await res.json();
    const stripe = await stripePromise;
    await stripe?.redirectToCheckout({ sessionId });
  };

  return (
    <button onClick={handleCheckout} className="btn-primary">
      Assinar por R$ 199/mês
    </button>
  );
}

Backend (API Route):

// app/api/create-checkout-session/route.ts
import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2024-11-20.acacia',
});

export async function POST(req: Request) {
  const { amount, description } = await req.json();

  const session = await stripe.checkout.sessions.create({
    payment_method_types: ['card'],
    line_items: [
      {
        price_data: {
          currency: 'brl',
          product_data: { name: description },
          unit_amount: amount,
        },
        quantity: 1,
      },
    ],
    mode: 'payment',
    success_url: `${process.env.NEXT_PUBLIC_URL}/success`,
    cancel_url: `${process.env.NEXT_PUBLIC_URL}/cancel`,
  });

  return Response.json({ sessionId: session.id });
}

Cobrança recorrente (assinatura)

// Criar produto e preço (apenas 1x, via Dashboard ou API)
const price = await stripe.prices.create({
  currency: 'brl',
  unit_amount: 19900, // R$ 199
  recurring: { interval: 'month' },
  product_data: { name: 'Plano Premium' },
});

// Criar assinatura para cliente
const subscription = await stripe.subscriptions.create({
  customer: customerId, // ID do cliente no Stripe
  items: [{ price: price.id }],
  payment_behavior: 'default_incomplete',
  expand: ['latest_invoice.payment_intent'],
});

return Response.json({
  subscriptionId: subscription.id,
  clientSecret: subscription.latest_invoice.payment_intent.client_secret,
});

Webhook (confirmar pagamento)

// app/api/webhooks/stripe/route.ts
import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export async function POST(req: Request) {
  const body = await req.text();
  const sig = req.headers.get('stripe-signature')!;

  let event;

  try {
    event = stripe.webhooks.constructEvent(
      body,
      sig,
      process.env.STRIPE_WEBHOOK_SECRET!
    );
  } catch (err) {
    return Response.json({ error: 'Webhook signature verification failed' }, { status: 400 });
  }

  // Pagamento confirmado
  if (event.type === 'checkout.session.completed') {
    const session = event.data.object;

    // Ativar conta do usuário
    await activateUserAccount(session.customer);
  }

  // Assinatura criada
  if (event.type === 'customer.subscription.created') {
    const subscription = event.data.object;
    // Lógica de ativação
  }

  // Pagamento recorrente falhou
  if (event.type === 'invoice.payment_failed') {
    const invoice = event.data.object;
    // Enviar e-mail de lembrete
    await sendPaymentFailedEmail(invoice.customer_email);
  }

  return Response.json({ received: true });
}

Custo Stripe:

  • Taxa: 3,4% + R$ 0,80 por transação
  • Exemplo: R$ 199 → Taxa de R$ 7,57 (3,8%)

2. E-mail transacional: Resend (mais simples que SendGrid)

Por que Resend: API moderna, templates em React (TSX), deliverability excelente.

Setup (10min)

npm install resend react-email @react-email/components

# .env.local
RESEND_API_KEY=re_...

Template de e-mail em React

// emails/welcome.tsx
import {
  Body,
  Button,
  Container,
  Head,
  Html,
  Preview,
  Section,
  Text,
} from '@react-email/components';

interface WelcomeEmailProps {
  userName: string;
  loginUrl: string;
}

export default function WelcomeEmail({ userName, loginUrl }: WelcomeEmailProps) {
  return (
    <Html>
      <Head />
      <Preview>Bem-vindo ao OrientMe, {userName}!</Preview>
      <Body style={{ backgroundColor: '#f6f9fc', fontFamily: 'Arial' }}>
        <Container style={{ margin: '40px auto', padding: '20px' }}>
          <Text style={{ fontSize: '24px', fontWeight: 'bold' }}>
            Olá {userName}! 👋
          </Text>

          <Text>
            Sua conta foi criada com sucesso. Estamos animados para ter você conosco!
          </Text>

          <Section style={{ textAlign: 'center', margin: '32px 0' }}>
            <Button
              href={loginUrl}
              style={{
                backgroundColor: '#23a4ff',
                color: '#fff',
                padding: '12px 24px',
                borderRadius: '6px',
                textDecoration: 'none',
              }}
            >
              Acessar minha conta
            </Button>
          </Section>

          <Text style={{ fontSize: '14px', color: '#666' }}>
            Dúvidas? Responda este e-mail ou acesse nossa central de ajuda.
          </Text>
        </Container>
      </Body>
    </Html>
  );
}

Enviar e-mail

// lib/email.ts
import { Resend } from 'resend';
import WelcomeEmail from '@/emails/welcome';

const resend = new Resend(process.env.RESEND_API_KEY);

export async function sendWelcomeEmail(to: string, userName: string) {
  await resend.emails.send({
    from: 'OrientMe <noreply@orientme.com.br>',
    to,
    subject: `Bem-vindo, ${userName}!`,
    react: WelcomeEmail({ userName, loginUrl: 'https://app.orientme.com.br/login' }),
  });
}

// Uso
await sendWelcomeEmail('joao@exemplo.com', 'João Silva');

Custo Resend:

  • Grátis: 3.000 e-mails/mês
  • R$ 100/mês: 50.000 e-mails

3. Storage de arquivos: Cloudinary

Por que Cloudinary: Upload direto do browser, transformações automáticas (resize, crop), CDN global.

Setup (15min)

npm install cloudinary next-cloudinary

# .env.local
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=...
CLOUDINARY_API_KEY=...
CLOUDINARY_API_SECRET=...

Upload de imagem (frontend)

// components/image-upload.tsx
'use client';
import { CldUploadWidget } from 'next-cloudinary';
import { useState } from 'react';

export default function ImageUpload() {
  const [imageUrl, setImageUrl] = useState('');

  return (
    <div>
      <CldUploadWidget
        uploadPreset="ml_default" // Criar no Cloudinary Dashboard
        onSuccess={(result) => {
          setImageUrl(result.info.secure_url);
        }}
      >
        {({ open }) => (
          <button onClick={() => open()}>
            Upload de foto
          </button>
        )}
      </CldUploadWidget>

      {imageUrl && (
        <img src={imageUrl} alt="Upload" width={200} />
      )}
    </div>
  );
}

Transformações automáticas

import { CldImage } from 'next-cloudinary';

// Imagem otimizada (webp, lazy load, responsive)
<CldImage
  src={publicId} // ID da imagem no Cloudinary
  width={400}
  height={300}
  crop="fill"
  gravity="face" // Foca no rosto se for foto de pessoa
  alt="Foto de perfil"
/>

// Thumbnail circular (avatar)
<CldImage
  src={publicId}
  width={80}
  height={80}
  crop="thumb"
  gravity="face"
  radius="max" // Torna circular
  alt="Avatar"
/>

Custo Cloudinary:

  • Grátis: 25GB storage + 25GB bandwidth/mês
  • R$ 400/mês: 100GB storage + 100GB bandwidth

4. Mapas e geolocalização: Google Maps API

Setup (20min):

npm install @googlemaps/js-api-loader

# 1. Criar projeto em console.cloud.google.com
# 2. Ativar APIs: Maps JavaScript API, Geocoding API, Places API
# 3. Criar chave API

# .env.local
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=AIza...

Autocomplete de endereço

'use client';
import { useLoadScript, StandaloneSearchBox } from '@react-google-maps/api';
import { useRef, useState } from 'react';

const libraries: ('places')[] = ['places'];

export default function AddressAutocomplete() {
  const { isLoaded } = useLoadScript({
    googleMapsApiKey: process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY!,
    libraries,
  });

  const searchBoxRef = useRef<google.maps.places.SearchBox | null>(null);
  const [address, setAddress] = useState('');

  const handlePlacesChanged = () => {
    const places = searchBoxRef.current?.getPlaces();
    if (places && places.length > 0) {
      const place = places[0];
      setAddress(place.formatted_address || '');

      // Latitude e longitude
      const lat = place.geometry?.location?.lat();
      const lng = place.geometry?.location?.lng();

      console.log({ lat, lng, address: place.formatted_address });
    }
  };

  if (!isLoaded) return <div>Carregando...</div>;

  return (
    <StandaloneSearchBox
      onLoad={(ref) => (searchBoxRef.current = ref)}
      onPlacesChanged={handlePlacesChanged}
    >
      <input
        type="text"
        placeholder="Digite seu endereço"
        className="w-full p-2 border rounded"
      />
    </StandaloneSearchBox>
  );
}

Calcular distância entre 2 pontos

// lib/maps.ts
export async function calculateDistance(
  origin: { lat: number; lng: number },
  destination: { lat: number; lng: number }
) {
  const response = await fetch(
    `https://maps.googleapis.com/maps/api/distancematrix/json?origins=${origin.lat},${origin.lng}&destinations=${destination.lat},${destination.lng}&key=${process.env.GOOGLE_MAPS_API_KEY}`
  );

  const data = await response.json();

  return {
    distance: data.rows[0].elements[0].distance.value, // metros
    duration: data.rows[0].elements[0].duration.value, // segundos
  };
}

// Uso
const { distance, duration } = await calculateDistance(
  { lat: -23.550520, lng: -46.633308 }, // São Paulo
  { lat: -22.906847, lng: -43.172896 }  // Rio de Janeiro
);

console.log(`Distância: ${distance / 1000}km`); // 429km
console.log(`Duração: ${duration / 60}min`); // 382min

Custo Google Maps:

  • R$ 750 de crédito grátis/mês
  • Autocomplete: $2,83 por 1.000 requests
  • Geocoding: $5,00 por 1.000 requests
  • Realidade: R$ 0 até ~5K usuários/mês

5. Bônus: SMS (Twilio)

Setup rápido:

npm install twilio

# .env.local
TWILIO_ACCOUNT_SID=AC...
TWILIO_AUTH_TOKEN=...
TWILIO_PHONE_NUMBER=+55...

Enviar SMS:

import twilio from 'twilio';

const client = twilio(
  process.env.TWILIO_ACCOUNT_SID,
  process.env.TWILIO_AUTH_TOKEN
);

export async function sendSMS(to: string, message: string) {
  await client.messages.create({
    body: message,
    from: process.env.TWILIO_PHONE_NUMBER,
    to,
  });
}

// Uso
await sendSMS(
  '+5521987654321',
  'Seu código de confirmação é: 739281'
);

Custo Twilio:

  • R$ 0,30 por SMS enviado no Brasil

Resumo de custos (mensal)

Integração0-1K usuários1K-10K10K-100K
StripeR$ 0 (apenas taxa 3,4%)R$ 0R$ 0
ResendR$ 0 (3K e-mails)R$ 100R$ 300
CloudinaryR$ 0 (25GB)R$ 80R$ 400
Google MapsR$ 0 (crédito)R$ 0R$ 200
TwilioR$ 0-150R$ 300-1KR$ 2K-5K
TOTALR$ 0-150R$ 480-1.180R$ 2.900-5.900

Conclusão: Integrações custam quase R$ 0 até validar produto com 1K usuários. Depois escalam gradualmente.

Não desenvolva sistema de pagamento, e-mail ou storage próprio. Use APIs prontas e foque no core do seu produto.

Pronto para sair do manual?

Agende o diagnóstico gratuito. Vamos mapear o gargalo, estimar o impacto e definir o primeiro resultado mensurável.

Você sai com clareza — não com um pitch de vendas.