Implementação técnica do Checkout via iframe

A implementação do Checkout via iframe reúne as etapas necessárias para que a loja inicie o processo de pagamento, envie as informações para o servidor e carregue o fluxo de checkout diretamente na página. Essa implementação integra três partes principais: front-end, back-end e estrutura da requisição enviada à API Checkout.

Back-end

O back-end é a camada que garante a segurança na criação do checkout. Ele recebe a requisição enviada pelo front-end, adiciona as credenciais da loja e envia a solicitação para a API Checkout. Em seguida, retorna a checkoutUrl, que será utilizada para exibir o fluxo de pagamento no iframe.

Essas responsabilidades impedem que dados sensíveis sejam expostos no navegador e asseguram o funcionamento correto da integração.

Por que o back-end é necessário?

O back-end atua como um proxy seguro. Isso significa que ele:

  • Recebe a requisição do front-end;
  • Adiciona as credenciais necessárias;
  • Envia a requisição para a API Checkout;
  • Devolve ao front-end somente os dados necessários, como a checkoutUrl.

Essa função evita exposição de informações sigilosas e mantém a comunicação compatível com navegadores modernos.

Regras obrigatórias de segurança

  • Nunca expor MerchantId ou MerchantKey em código JavaScript;
  • Usar HTTPS/TLS nas requisições do back-end;
  • Validar a requisição recebida antes de enviá-lo à API;
  • Evitar logs contendo dados sensíveis;
  • Configurar timeout adequado;
  • Aceitar apenas métodos e origens autorizadas no endpoint;
  • Verificar todas as possíveis localizações da checkoutUrl no retorno (checkoutUrl, url, etc).

Essas regras asseguram conformidade e evitam riscos de segurança.

Exemplo de implementação (Node.js)

A seguir, um exemplo em Node.js que mostra como o back-end cria a página de pagamento e retorna a checkoutUrl. A mesma estrutura pode ser usada em outras linguagens.

// server/checkout.js
import express from 'express';
import axios from 'axios';

const router = express.Router();

// Variável de ambiente com o Merchant ID da Cielo
const CIELO_MERCHANT_ID = process.env.CIELO_MERCHANT_ID;

// POST /api/checkout/create
router.post('/checkout/create', async (req, res) => {
    try {
        // Corpo da requisição enviado pelo cliente
        const payload = req.body; // Deve conter OrderNumber, Cart, Payment, Customer, Shipping, Options

        // Chamada à API da Cielo
        const { data } = await axios.post(
            'https://cieloecommerce.cielo.com.br/api/public/v1/orders/',
            payload,
            {
                headers: {
                    'Content-Type': 'application/json',
                    'Accept': 'application/json',
                    'MerchantId': CIELO_MERCHANT_ID
                },
                timeout: 10000 // Timeout de 10 segundos
            }
        );

        // Extrai a URL de checkout retornada pela Cielo
        const checkoutUrl = data?.CheckoutUrl || data?.checkoutUrl || data?.url;
        return res.json({ checkoutUrl });
    } catch (err) {
        return res.status(err.response?.status || 500).json({
            message: 'Falha ao criar checkout',
            detail: err.response?.data || err.message
        });
    }
});
export default router;

Front-end

O front-end é responsável por iniciar a criação do checkout, enviar os dados da compra ao back-end e exibir o fluxo de pagamento dentro do iframe. Ele também controla os estados da interface, como carregamento, exibição do iframe e mensagens de erro.

Chamada ao back-end

A aplicação de front-end não se comunica diretamente com a API Checkout. Em vez disso, faz uma chamada ao back-end da loja, que atua como proxy, adiciona as credenciais e cria o checkout.

No exemplo abaixo, o serviço JavaScript usa a biblioteca Axios para enviar a requisição ao back-end, mas você pode usar qualquer biblioteca HTTP com a mesma estrutura de chamada.

// services/CheckoutFetcher.js
import axios from 'axios';

export default {
  async callCreateCheckout(url, payload) {
    const response = await axios.post('/checkout/create', {
      url,
      payload
    });
    return response;
  }
};

O front-end envia apenas a URL da API Checkout e a requisição do pedido. O back-end é responsável por incluir as credenciais e retornar a resposta com a checkoutUrl.

Estrutura do componente e estados

O componente de front-end é formado por três partes:

  1. Template: contém os botões de ação, mensagens de carregamento, área do iframe e exibição de erros;
  2. Script: gerencia os estados do componente, realiza a chamada ao back-end e atualiza a checkoutUrl;
  3. Estilos: aplicados no bloco <style scoped>, organizam o layout do checkout, botões e mensagens na interface.

Os estados mínimos usados no componente são:

  • loading: indica que o checkout está sendo criado;
  • checkoutUrl: quando preenchida, aciona a renderização do iframe;
  • error: exibe mensagens em caso de falha.

Um exemplo de definição desses estados em Vue.js é:

data() {
  return {
    checkoutUrl: '',
    loading: false,
    error: ''
  };
}
ℹ️

Os estados loading, checkoutUrl e error funcionam juntos no fluxo de criação da página de pagamento.

Quando loading é true, o componente exibe a mensagem de carregamento. Quando checkoutUrl recebe um valor válido, o iframe é renderizado e, em caso de falha, o componente preenche error com uma mensagem amigável e interrompe o carregamento.

Para saber como esses estados se comportam em cenários de erro, consulte a seção Tratamento de erros

Exemplos de implementação

A seguir, duas formas de integrar o checkout no front-end:

  • Usando um componente Vue.js;
  • Usando HTML e JavaScript sem framework.
<template>
  <div class="checkout-poc">
    <h2>Checkout via iframe</h2>
    
    <!-- Botões de ação -->
    <button 
      v-if="!checkoutUrl && !loading" 
      @click="createCheckout" 
      class="action-btn">
      Criar Checkout
    </button>
    
    <button 
      v-if="!checkoutUrl && !loading" 
      @click="openShortLink" 
      class="action-btn">
      Usar Link Encurtado
    </button>
    
    <!-- Indicador de carregamento -->
    <div v-if="loading">
      <p>Carregando checkout...</p>
    </div>
    
    <!-- Iframe com o checkout -->
    <div v-if="checkoutUrl">
      <iframe 
        :src="checkoutUrl" 
        width="100%" 
        height="600" 
        frameborder="0" 
      />
    </div>
    
    <!-- Mensagem de erro -->
    <div v-if="error" class="error">
      {{ error }}
    </div>
  </div>
</template>

<script>
import CheckoutFetcher from '@/services/CheckoutFetcher';

export default {
  name: 'CheckoutIframe',
  data() {
    return {
      checkoutUrl: '',
      loading: false,
      error: ''
    };
  },
  methods: {
    // Opção 1: Usar link encurtado direto
    openShortLink() {
      this.checkoutUrl = 'https://cielolink.com.br/SEU_LINK_AQUI';
    },
    
    // Opção 2: Criar checkout via API
    async createCheckout() {
      this.loading = true;
      this.error = '';
      
      // URL da API Cielo
      const apiUrl = 'https://cieloecommerce.cielo.com.br/api/public/v1/orders';
      
      // Dados do pedido
      const payload = {
        OrderNumber: 'Pedido0123',
        SoftDescriptor: 'Nomefantasia',
        Cart: {
          Items: [
            {
              Name: 'Produto01',
              Description: 'ProdutoExemplo01',
              UnitPrice: 100,
              Quantity: 1,
              Type: 'Asset',
              Sku: 'ABC001',
              Weight: 500
            }
          ]
        },
        Payment: {
          BoletoDiscount: 15,
          MaxNumberOfInstallments: 12
        },
        Customer: {
          Identity: '45873281890',
          FullName: 'João da Silva',
          Email: '[email protected]',
          Phone: '11988776655'
        },
        Shipping: {
          SourceZipCode: '20020080',
          TargetZipCode: '21911130',
          Type: 'FixedAmount',
          Address: {
            Street: 'Alameda Xingu',
            Number: '512',
            Complement: '21 andar',
            District: 'Alphaville',
            City: 'Barueri',
            State: 'SP'
          },
          Services: [
            {
              Name: 'Entrega Express',
              Price: 1000,
              Deadline: 2
            }
          ]
        }
      };
      
      try {
        // Chama o backend que faz proxy para Cielo
        const data = await CheckoutFetcher.callCreateCheckout(apiUrl, payload);
        
        // Extrai a checkoutUrl da resposta
        // A URL pode estar em diferentes lugares dependendo da versão da API
        let checkoutUrl = '';
        if (data?.data?.settings?.checkoutUrl) {
          checkoutUrl = data.data.settings.checkoutUrl;
        } else if (data?.data?.checkoutUrl) {
          checkoutUrl = data.data.checkoutUrl;
        } else if (data?.data?.url) {
          checkoutUrl = data.data.url;
        }
        
        this.checkoutUrl = checkoutUrl;
        
        if (!this.checkoutUrl) {
          this.error = 'URL de checkout não encontrada na resposta.';
        }
      } catch (e) {
        this.error = 'Erro ao criar checkout.';
        console.error(e);
      } finally {
        this.loading = false;
      }
    }
  }
};
</script>

<style scoped>
h2 {
  font-size: 1.25rem;
  font-weight: 700;
  color: #2F363E;
}

.checkout-poc {
  max-width: 600px;
  margin: 40px auto;
  padding: 24px;
  background: #fff;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}

button {
  padding: 10px 24px;
  font-size: 16px;
  background: #1976d2;
  color: #fff;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.action-btn {
  margin-right: 16px;
}

.error {
  color: #d32f2f;
  margin-top: 16px;
}
</style>
<!-- Container acessível para o checkout embutido -->
<section class="checkout-embed" aria-label="Pagamento seguro">
  <div id="checkout-wrapper">
    <iframe
      id="cielo-checkout"
      title="Finalização de pagamento"
      allowpaymentrequest
      allow="payment *; clipboard-write"
      style="width:100%; min-height:640px; border:0; background:#fff; border-radius:12px; box-shadow:0 8px 24px rgba(0,0,0,.12);"
      referrerpolicy="no-referrer"
      sandbox="allow-scripts allow-same-origin allow-forms"
            src=""  
<!-- preenchido via JS -->
    ></iframe>
  </div>

  <div id="checkout-loading" role="status">Carregando o checkout…</div>
  <div id="checkout-fallback" hidden>
    Não foi possível carregar o checkout embutido.
    <a id="checkout-direct-link" rel="noopener">Abrir pagamento</a>.
  </div>
</section>

<script>
 
  // Mock: usar sempre o mesmo link encurtado (fixo) no protótipo
  const CHECKOUT_URL = 'https://pay.exemplo/abc123';

  const iframe     = document.getElementById('cielo-checkout');
  const loadingEl  = document.getElementById('checkout-loading');
  const fallbackEl = document.getElementById('checkout-fallback');
  const directLink = document.getElementById('checkout-direct-link');

  function loadCheckout(url) {
    // Link direto para fallback manual
    directLink.href = url;

    // Define a URL no iframe
    iframe.src = url;

    // Estado de carregamento simples (sem depender de mensagens do destino)
    let finished = false;
    const FAIL_TIMEOUT_MS = 7000; // ajuste conforme sua UX

    // Se o documento do iframe carregar, ocultamos o loading
    iframe.addEventListener('load', () => {
      finished = true;
      loadingEl.hidden = true;
      // (Opcional) Ajustes de layout adicionais podem ser aplicados aqui
    }, { once: true });

    // Fallback por timeout: se não carregou em N segundos, exibe alternativa
    setTimeout(() => {
      if (!finished) {
        loadingEl.hidden = true;
        fallbackEl.hidden = false;
      }
    }, FAIL_TIMEOUT_MS);
  }

  loadCheckout(CHECKOUT_URL);
</script>

O exemplo HTML + JavaScript mostra como carregar o iframe diretamente, exibir um indicador de carregamento e apresentar um fallback com link direto caso o carregamento não seja concluído em um tempo definido.

Exemplo visual do checkout exibido no iframe

A imagem a seguir mostra como o checkout é exibido no iframe após a criação da página de pagamento. Esse exemplo é baseado na POC usada durante os testes e tem o objetivo de ilustrar a experiência final da pessoa compradora.

Estrutura da requisição

A criação da página de pagamento exige o envio de uma requisição para a API Checkout. Essa requisição reúne os dados do pedido, da pessoa compradora e das condições de pagamento. O envio deve ser feito pelo back-end da loja para o endpoint:

POST https://cieloecommerce.cielo.com.br/api/public/v1/orders

A lista completa de campos, formatos aceitos e exemplos de requisição está disponível na referência da API, em Criar página de pagamento do Checkout Cielo.