Por que o aplicativo implantado em 30 minutos falha em pagamentos reais e a solução
A dopamina que você sente ao subir um serviço via CLI em apenas 30 minutos e colocar o primeiro botão de pagamento não dura muito. O verdadeiro problema acontece quando um usuário real fecha a tela de pagamento, há um atraso na comunicação com a operadora do cartão ou um usuário internacional tenta fazer um pagamento. Uma estrutura que atualiza o banco de dados confiando apenas no redirecionamento do cliente quebrar imediatamente ao primeiro timeout de rede. Acontece uma situação em que o dinheiro saiu, mas a assinatura ou produto não foi entregue.
Para evitar esse tipo de incidente, o status de sucesso do pagamento deve ser recebido não pelo cliente, mas por um evento assíncrono de servidor para servidor (webhook).
1. Depurando webhooks de pagamento localmente com o Stripe CLI
No ambiente de desenvolvimento local, o servidor da Stripe não consegue enviar solicitações para o seu computador (localhost). Você deve abandonar a ideia de implantar para depurar. Usando o Stripe CLI, você pode receber os eventos diretamente por tunelamento.
De acordo com um relatório da GitGuardian, mais de 40% das falhas de pagamento vêm de falhas no processamento de eventos assíncronos. Você precisa executar cenários de falha diretamente no ambiente local.
Primeiro, abra o túnel.
`bash
stripe listen --events payment_intent.succeeded,payment_intent.payment_failed,checkout.session.completed --forward-to localhost:4242/webhook
`
Ao executar o comando, um segredo de webhook começando com whsec_ será exibido no terminal. Coloque esse valor nas variáveis de ambiente e, em outra janela do terminal, force a geração de eventos.
`bash
stripe trigger payment_intent.succeeded
stripe trigger payment_intent.payment_failed
`
No servidor, você deve verificar a assinatura do evento e garantir a idempotência (Idempotency). Se a Stripe enviar o mesmo evento duas vezes devido à instabilidade da rede, o usuário não pode receber o produto/serviço duas vezes. Este é um exemplo em Express que armazena event.id no Redis para evitar execuções duplicadas.
`javascript
const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const Redis = require('ioredis');
const app = express();
const redis = new Redis(process.env.REDIS_URL);
app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(
req.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
} catch (err) {
return res.status(400).send(Webhook Error: ${err.message});
}
const eventId = event.id;
const isProcessed = await redis.get(webhook:processed:${eventId});
if (isProcessed) {
return res.status(200).json({ received: true, status: 'already_processed' });
}
try {
switch (event.type) {
case 'payment_intent.succeeded':
await handlePaymentSuccess(event.data.object);
break;
case 'payment_intent.payment_failed':
await handlePaymentFailure(event.data.object);
break;
}
await redis.set(`webhook:processed:${eventId}`, 'true', 'EX', 604800);
return res.status(200).json({ received: true });
} catch (dbError) {
return res.status(500).send('Internal Server Error');
}
});
async function handlePaymentSuccess(paymentIntent) {}
async function handlePaymentFailure(paymentIntent) {}
app.listen(4242, () => console.log('Stripe 웹훅 모니터링 가동'));
`
2. Script de verificação prévia para evitar o vazamento de chaves de API na etapa de build
De acordo com o relatório de estado de segredos da GitGuardian de 2025, 28,6 milhões de chaves secretas vazaram em repositórios públicos do GitHub durante o ano de 2024. Os bots de varredura coletam as chaves em menos de 5 minutos após o repositório se tornar público.
Ao usar ferramentas de codificação com IA, é comum inserir acidentalmente o conteúdo de .env no código ou esquecer o .gitignore. A melhor forma de garantir a segurança é validar isso com um script ao executar o comando de build (npm run build).
Crie a seguinte lógica no arquivo scripts/preflight-security.js.
`javascript
const fs = require('fs');
const path = require('path');
const rootDir = path.resolve(__dirname, '..');
const gitignorePath = path.join(rootDir, '.gitignore');
if (!fs.existsSync(gitignorePath)) {
console.error('.gitignore 파일이 없습니다.');
process.exit(1);
}
const gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
if (!gitignoreContent.split('\n').map(l => l.trim()).includes('.env')) {
console.error('.gitignore에 .env가 누락되었습니다.');
process.exit(1);
}
const secretPatterns = [
/sk_live_[0-9a-zA-Z]{24,}/g,
/sk_test_[0-9a-zA-Z]{24,}/g
];
function scanDirectory(dir) {
const files = fs.readdirSync(dir);
for (const file of files) {
const fullPath = path.join(dir, file);
if (['node_modules', '.git', '.next', 'build'].includes(file)) continue;
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
scanDirectory(fullPath);
} else if (stat.isFile() && (file.endsWith('.js') || file.endsWith('.ts'))) {
const content = fs.readFileSync(fullPath, 'utf8');
secretPatterns.forEach(pattern => {
if (pattern.test(content)) {
console.error(`하드코딩된 API 키 발견: ${fullPath}`);
process.exit(1);
}
});
}
}
}
scanDirectory(rootDir);
console.log('보안 검증 완료.');
`
Insira isso no package.json.
`json
"scripts": {
"prebuild": "node scripts/preflight-security.js",
"build": "next build"
}
`
Agora, os segredos reais são injetados diretamente nas configurações de variáveis de ambiente da Vercel ou AWS. Se uma chave vazar, não hesite em usar a opção 'Roll Key' no painel da Stripe para invalidar imediatamente a chave anterior.
3. Automatizando o cálculo de impostos por país com o Stripe Tax
Quando você começa a vender SaaS para usuários internacionais, surgem problemas de impostos indiretos, como o IVA da UE ou o imposto sobre vendas (Sales Tax) de cada estado americano. No início você pode ignorar, mas à medida que a receita aumenta, você receberá contato da receita federal ou de autoridades fiscais estrangeiras.
Ao usar o Stripe Tax, ele identifica a localização do cliente no momento do pagamento e aplica os impostos automaticamente.
- Registre o endereço da empresa em Settings > Tax no painel da Stripe.
- Defina o código fiscal do produto no Product Catalog para o padrão de SaaS
txcd_10000000.
- Ative a opção de imposto automático na API de criação de sessão.
`javascript
const express = require('express');
const app = express();
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
app.post('/create-checkout-session', async (req, res) => {
const session = await stripe.checkout.sessions.create({
mode: 'subscription',
payment_method_types: ['card'],
line_items: [{ price: 'price_1NXXXXXXXXXXXXXX', quantity: 1 }],
automatic_tax: { enabled: true },
customer_update: { address: 'auto', name: 'auto' },
billing_address_collection: 'required',
success_url: 'https://yourdomain.com/success?session_id={CHECKOUT_SESSION_ID}',
cancel_url: 'https://yourdomain.com/cancel',
});
res.redirect(333, session.url);
});
`
Adicionando apenas as duas linhas automatic_tax: { enabled: true } e billing_address_collection: 'required', a coleta de endereços por país e o cálculo de IVA serão tratados. Os dados relacionados a impostos podem ser extraídos mensalmente como CSV no menu Reports para serem utilizados na contabilidade.