VipterCentral de Ajuda

Migrar da Stripe para o Vipter

Guia para quem já cobra com a Stripe e vai passar a cobrar pelo Vipter, no todo ou só no Brasil: o que corresponde a quê (Price → oferta, Invoice → pedido), o que não migra (cartões salvos e assinaturas em curso), as mudanças no código de checkout, webhooks, cobranças avulsas e uso medido, com diffs em Node.js, e um plano de corte em cinco etapas que mantém os dois lado a lado durante a transição.

Admin ou DonoTodos os planos

A API do Vipter foi desenhada para quem já integrou a Stripe: os mesmos nomes de objeto e de evento, o mesmo formato de erro, a mesma paginação, a mesma Idempotency-Key, o mesmo esquema de assinatura de webhook. O que muda é o que está por trás: no Vipter a loja vende pelos próprios adquirentes brasileiros (PIX, cartão parcelado, boleto) e o checkout é sempre o hospedado. Esta página lista as correspondências, o que não tem equivalente, o que trocar em cada trecho de código e em que ordem fazer o corte.

A migração é da camada de cobrança, não necessariamente do adquirente: a loja pode conectar a própria conta Stripe ao Vipter para continuar processando cartões por ela, e somar a Pagar.me, o Mercado Pago ou o Asaas para PIX e parcelamento. O código do seu sistema fala só com a API do Vipter, qualquer que seja o adquirente por trás.

Antes de começar

  • Uma loja no Vipter com os provedores de pagamento conectados e, para testar, uma conexão de teste.
  • Uma chave de API com o escopo write e um endpoint de webhook na versão 2026-11-01.
  • A lista dos price_… da Stripe que o seu sistema usa hoje e dos eventos que ele trata.

O que corresponde a quê

StripeVipterO que muda
sk_live_…vk_live_…Mesmo uso (Authorization: Bearer). Não existe vk_test_: veja Testar sem modo de teste.
Stripe-VersionVipter-VersionDatas, como na Stripe. Atual: 2026-11-01.
Productproduct (prd_)Igual.
Priceoffer (ofr_)A oferta carrega o preço por moeda, o ciclo, o teste grátis, o limite de ciclos e o pacote. Não há price_data inline: o preço nasce no painel ou no catálogo, nunca na chamada.
Customercustomer (cust_)Pede documento (CPF ou CNPJ) e telefone, exigidos pelos adquirentes brasileiros.
Subscriptionsubscription (sub_)status igual (trialing, active, past_due, paused, canceled) mais expired. offer no lugar de items[].price. Uma oferta por assinatura.
Invoice e PaymentIntentorder (ord_)Cada cobrança é um pedido, com billing_reason (purchase, renewal, manual, usage). Não há PaymentIntent nem Charge separados.
InvoiceItem + Invoice paga na horasubscription_charge (sch_)Uma chamada só: POST /v1/subscriptions/{id}/charges com o valor. Gera um pedido.
Checkout Sessioncheckout.session (cs_)Mesmos campos principais (client_reference_id, customer_email, metadata, success_url com {CHECKOUT_SESSION_ID}, cancel_url). Um line_item só; quantity é o pacote.
Billing Portal Sessionbilling_portal.sessionIgual: customer, return_url, url de uso único.
Coupon e Promotion Codediscounts[0].coupon com o código do cupomO cupom é criado no painel.
Billing Meter e Meter Eventbilling.meter, billing.meter_eventMesmos campos. payload.customer_id; stripe_customer_id também é aceito como chave, para não mexer no código.
Price com recurring.usage_type: meteredusage_item na oferta ou na assinaturaPreço por unidade, franquia, faixas graduadas, limiar de cobrança.
Webhook Endpoint, whsec_…, Stripe-Signaturewebhook_endpoint, whsec_…, Vipter-SignatureMesmo esquema t=…,v1=…, HMAC SHA-256 de t.corpo. Veja Assinatura.
Event (evt_)event (evt_)Mesmos nomes no catálogo 2026-11-01. O data.object é o objeto do Vipter.

Sem equivalente

  • PaymentIntent, PaymentMethod, SetupIntent, Charge. O pedido é a unidade; o cartão salvo é implícito na assinatura e só é cobrado por POST /v1/subscriptions/{id}/charges ou pelas renovações.
  • expand[], Search API, Tax Rates, Invoice em rascunho, Quotes, Payment Links por API. Os objetos já vêm com o que precisam (offer e product dentro da assinatura, por exemplo). Links de checkout saem do painel e aceitam parâmetros de URL.
  • Vários line_items numa sessão. Uma oferta por sessão. Para vender um conjunto, crie a oferta do conjunto.
  • Modo de teste. Os testes rodam na mesma loja, com uma conexão de teste do provedor; os pedidos saem com livemode: false.

O que não migra

  • Cartões salvos. O token de um cartão pertence ao adquirente e à integração que o criou. O Vipter não importa tokens, nem mesmo os da sua conta Stripe quando ela está conectada ao Vipter: os cartões salvos pela sua integração direta não são reaproveitados. Cada cliente precisa pagar uma vez pelo checkout do Vipter; a partir daí o cartão fica salvo na nova assinatura. O plano de corte abaixo é desenhado em volta disso.
  • Assinaturas em curso. Não há endpoint de importação de assinatura. A assinatura nova nasce de uma sessão de checkout paga. Para não cobrar duas vezes o mesmo período, use uma oferta de migração com trial_days igual aos dias que faltam no ciclo da Stripe, ou cancele a assinatura da Stripe com reembolso proporcional no dia em que a sessão completar.
  • Histórico de faturas e eventos. Fica na Stripe. Exporte o que precisar antes de desligar a conta e guarde no seu banco o par (provider, subscription_id) de cada cliente.

O que trocar no código

Os exemplos usam Node.js com fetch. O switch por tipo de evento, os IDs de usuário em client_reference_id e a estrutura de "um endpoint por provedor" vêm de Convivendo com a Stripe.

Criar o checkout

checkout.mjs
-const session = await stripe.checkout.sessions.create({
-  mode: 'subscription',
-  line_items: [{ price: 'price_1Pq…', quantity: 1 }],
-  client_reference_id: user.id,
-  customer_email: user.email,
-  metadata: { user_id: user.id, plan: 'pro' },
-  success_url: 'https://app.example.com/billing/success?session_id={CHECKOUT_SESSION_ID}',
-  cancel_url: 'https://app.example.com/billing/plans',
-});
-redirect(session.url);
+const res = await fetch('https://api.vipter.com/v1/checkout/sessions', {
+  method: 'POST',
+  headers: {
+    Authorization: `Bearer ${process.env.VIPTER_API_KEY}`,
+    'Content-Type': 'application/json',
+    'Idempotency-Key': `${user.id}:pro:${Date.now()}`,
+  },
+  body: JSON.stringify({
+    offer: 'ofr_6e2b8d4f1a9c3e7b',          // a oferta que substitui o price
+    client_reference_id: user.id,
+    customer_email: user.email,
+    metadata: { user_id: user.id, plan: 'pro' },
+    success_url: 'https://app.example.com/billing/success?session_id={CHECKOUT_SESSION_ID}',
+    cancel_url: 'https://app.example.com/billing/plans',
+  }),
+});
+const session = await res.json();
+redirect(session.url);

O mode sai: o Vipter deduz da oferta. Guarde uma tabela price_… → ofr_… no seu código ou no banco durante a transição.

Confirmar o pagamento

success.mjs
-const session = await stripe.checkout.sessions.retrieve(sessionId, { expand: ['subscription'] });
-if (session.payment_status === 'paid') activate(session.client_reference_id, session.subscription.id);
+const session = await vipter(`/checkout/sessions/${sessionId}`);
+if (session.payment_status === 'paid') activate(session.client_reference_id, session.subscription);

session.subscription já é o sub_…; para o objeto inteiro, GET /v1/subscriptions/{id}. Um PIX fica payment_status: pending até ser pago e então sai checkout.session.async_payment_succeeded, o mesmo nome que a Stripe usa para boleto.

Verificar o webhook

webhook.mjs
-const event = stripe.webhooks.constructEvent(rawBody, req.headers['stripe-signature'], process.env.STRIPE_WEBHOOK_SECRET);
+const event = verifyVipter(rawBody, req.headers['vipter-signature'], process.env.VIPTER_WEBHOOK_SECRET);

O esquema é o mesmo (t=…,v1=…, HMAC SHA-256 sobre `${t}.${rawBody}`), então a função que você já tem serve com outro cabeçalho e outro segredo. O código completo da verificação está em Assinatura dos webhooks.

Tratar os eventos

EventoNa Stripe, data.object éNo Vipter, data.object éO que ajustar
checkout.session.completedCheckout Sessioncheckout.sessionsubscription e customer já são IDs; order no lugar de payment_intent/invoice.
invoice.paidInvoiceorder (object: "order")amount_total no lugar de amount_paid; subscription e customer iguais; billing_reason diz se é renovação, compra ou cobrança avulsa.
invoice.payment_failedInvoiceorder com status: failedIgual ao anterior.
customer.subscription.created / updated / deletedSubscriptionsubscriptionoffer no lugar de items.data[0].price. updated traz data.previous_attributes como na Stripe.
customer.subscription.paused / resumedSubscriptionsubscriptionIguais.
customer.created / updatedCustomercustomerdocument e address no formato do Vipter.

Os eventos que só existem no Vipter, subscription_charge.succeeded|failed, order.refunded|partially_refunded|charged_back e billing.meter.error_report_triggered, estão no catálogo de eventos.

Cobrança avulsa

charge.mjs
-await stripe.invoiceItems.create({ customer, amount: 1990, currency: 'brl', description: 'Excedente de uso' });
-const invoice = await stripe.invoices.create({ customer, auto_advance: true });
-await stripe.invoices.pay(invoice.id);
+const charge = await vipter(`/subscriptions/${subscriptionId}/charges`, {
+  method: 'POST',
+  idempotencyKey: `usage:${subscriptionId}:2026-09`,
+  body: { amount: 1990, description: 'Excedente de uso', metadata: { period: '2026-09' } },
+});
+// charge.status: succeeded | pending | failed; charge.order é o ord_…

A recusa do cartão volta como 402 card_error, com a cobrança falhada em error.subscription_charge. A Idempotency-Key é obrigatória. Veja Cobrar o cartão salvo.

Uso medido

usage.mjs
-await stripe.billing.meterEvents.create({
-  event_name: 'api_calls',
-  payload: { stripe_customer_id: customerId, value: '250' },
-  identifier: requestId,
-});
+await vipter('/billing/meter_events', {
+  method: 'POST',
+  body: { event_name: 'api_calls', payload: { customer_id: customerId, value: '250' }, identifier: requestId },
+});

payload.stripe_customer_id também é aceito, então até essa linha pode ficar como está. O preço do uso sai da oferta ou de POST /v1/subscriptions/{id}/usage_items, e a cobrança acontece no fim do ciclo ou ao atingir o limiar. Veja Uso medido.

Portal do cliente

portal.mjs
-const portal = await stripe.billingPortal.sessions.create({ customer, return_url });
+const portal = await vipter('/billing_portal/sessions', { method: 'POST', body: { customer, return_url } });
 redirect(portal.url);

Plano de corte em cinco etapas

Liste os product_… e price_… em uso. Crie no Vipter os produtos e as ofertas equivalentes, com preço em reais, ciclo e teste grátis. Confira pela API com GET /v1/offers?type=recurring e monte a tabela price_… → ofr_….

2. Chave, endpoint e código

Crie a chave e um endpoint de webhook na versão 2026-11-01, apontando para uma rota nova (/webhooks/vipter). Aplique as trocas de código acima atrás de um sinalizador por usuário (billing_provider: 'stripe' | 'vipter'). Teste a sessão de checkout com a conexão de teste do provedor.

3. Novos clientes no Vipter

Ligue o sinalizador para quem se cadastra a partir de agora. A Stripe continua cobrando os clientes antigos. Os dois webhooks chegam em rotas separadas, com segredos separados, e o client_reference_id identifica o usuário nos dois.

4. Migrar os clientes antigos na renovação

Para cada assinatura da Stripe, perto da renovação, mande ao cliente um link de uma sessão de checkout do Vipter com client_reference_id igual ao ID dele e metadata.stripe_subscription com o sub_… antigo. No checkout.session.completed, leia esse metadata, cancele a assinatura da Stripe (cancel_at_period_end: true ou del com reembolso proporcional) e troque o sinalizador do usuário. Quem não pagar segue na Stripe até você decidir.

5. Desligar a Stripe

Quando a lista de assinaturas ativas na Stripe chegar a zero, desative o endpoint da Stripe, revogue as chaves sk_live_… e exporte faturas e clientes para o seu arquivo. Remova o sinalizador do código.

Diferenças que costumam surpreender

  • O valor vem da oferta, não da chamada. Não existe unit_amount numa sessão de checkout. Para um preço novo, crie uma oferta.
  • invoice.paid traz um pedido, não uma fatura. O campo é object: "order", com amount_total, lines e billing_reason.
  • Um customer.subscription.updated para muita coisa: renovação, troca de plano, recuperação de inadimplência e troca de cartão. Diferencie por data.previous_attributes, como na Stripe.
  • Documento e telefone são exigidos na criação de clientes pela API, porque os adquirentes brasileiros exigem.
  • Parcelamento é decisão do comprador no checkout; o pedido informa o número de parcelas, e a assinatura cobra o valor cheio em cada ciclo.
  • PIX e boleto são assíncronos: a sessão completa com payment_status: pending e o pagamento confirma depois por checkout.session.async_payment_succeeded.
  • Sem expand[]: a assinatura já vem com offer e product resumidos; o resto é uma segunda chamada.
  • Limites: 100 requisições a cada 2 segundos por chave, com X-RateLimit-* e 429. Veja convenções.

Lista de verificação

  • Tabela price_… → ofr_… completa e ofertas ativas (GET /v1/offers).
  • Chave vk_live_… com write em variável de ambiente; GET /v1/account responde 200.
  • Endpoint 2026-11-01 criado; teste com POST /v1/webhook_endpoints/{id}/test aceito pelo seu servidor.
  • Checkout de teste pago com a conexão de teste; checkout.session.completed recebido; client_reference_id conferido na assinatura.
  • Cobrança avulsa de teste com Idempotency-Key; repetição devolve a mesma cobrança.
  • Deduplicação de eventos por (provider, id).
  • Sinalizador por usuário e fluxo de migração na renovação.
  • Exportação da Stripe guardada antes de desligar.

O que fazer a seguir

Esta página ajudou?

Nesta página

Idioma