Pular para o conteúdo
xFlow Payments

Blog

API Pix no WooCommerce

A xFlow não tem plugin oficial para WooCommerce. Para integrar o Pix pela API, você cria um gateway próprio: uma classe que estende WC_Payment_Gateway e cria a cobrança em process_payment, a página do pedido mostrando o QR Code e o copia e cola, e uma rota de webhook (WC_API) que valida o HMAC e chama payment_complete.

Atualizado em · Equipe xFlow Payments · 8 min de leitura

Para integrar o Pix no WooCommerce pela API da xFlow, você escreve um gateway de pagamento próprio: não existe plugin oficial da xFlow para WooCommerce. O gateway tem três partes. A classe cria a cobrança quando o cliente fecha o pedido, a página do pedido mostra o QR Code e o copia e cola, e uma rota recebe o webhook e marca o pedido como pago. Os nomes de classes e hooks seguem a documentação de gateways do WooCommerce, e os campos seguem a documentação da API. A autenticação e a assinatura do webhook em PHP puro estão explicadas em API Pix em PHP.

O arquivo principal do plugin

O plugin tem dois arquivos: xflow-pix.php e a classe do gateway. As credenciais ficam fora do banco, em constantes no wp-config.php: XFLOW_PUBLIC_KEY, XFLOW_SECRET_KEY e XFLOW_WEBHOOK_SECRET.

O WooCommerce pede que a classe seja carregada depois dos plugins (plugins_loaded) e registrada no filtro woocommerce_payment_gateways. Os hooks da página do pedido e do webhook ficam fora da classe, para funcionar mesmo quando o WooCommerce não instanciou o gateway, como numa entrega de webhook.

php
<?php
/**
 * Plugin Name: Pix pela API da xFlow (exemplo)
 */
defined( 'ABSPATH' ) || exit;

add_action( 'before_woocommerce_init', function () {
    if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) {
        \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true );
    }
} );

add_action( 'plugins_loaded', function () {
    if ( class_exists( 'WC_Payment_Gateway' ) ) {
        require __DIR__ . '/class-wc-gateway-xflow-pix.php';
    }
} );

add_filter( 'woocommerce_payment_gateways', function ( $gateways ) {
    $gateways[] = 'WC_Gateway_Xflow_Pix';
    return $gateways;
} );

add_action( 'woocommerce_thankyou_xflow_pix', 'xflow_pix_pagina_do_pedido' );
add_action( 'woocommerce_api_xflow_pix_webhook', 'xflow_pix_webhook' );

function xflow_pix_api( string $metodo, string $caminho, ?array $corpo = null, ?string $chave = null ): array {
    $headers = array(
        'Authorization' => 'Basic ' . base64_encode( XFLOW_PUBLIC_KEY . ':' . XFLOW_SECRET_KEY ),
        'Content-Type'  => 'application/json',
    );
    if ( $chave ) {
        $headers['Idempotency-Key'] = $chave;
    }
    $res = wp_remote_request( 'https://app.xflowpayments.com/api/v1' . $caminho, array(
        'method'  => $metodo,
        'headers' => $headers,
        'body'    => $corpo ? wp_json_encode( $corpo ) : null,
        'timeout' => 30,
    ) );
    // Em falha de rede, o status vem 0 e o corpo vazio.
    return array(
        'status' => (int) wp_remote_retrieve_response_code( $res ),
        'body'   => json_decode( wp_remote_retrieve_body( $res ), true ) ?? array(),
    );
}

A declaração custom_order_tables diz que o plugin funciona com o armazenamento de pedidos em tabelas próprias (HPOS). Para isso, o código usa só get_meta e update_meta_data no pedido, nunca get_post_meta, como pede o guia de HPOS do WooCommerce.

A classe do gateway e o process_payment

A API exige nome, e-mail e documento do cliente. O checkout padrão do WooCommerce não tem campo de CPF, então o gateway pede o documento no bloco de pagamento (payment_fields) e confere em validate_fields.

php
<?php
// class-wc-gateway-xflow-pix.php
class WC_Gateway_Xflow_Pix extends WC_Payment_Gateway {

    public function __construct() {
        $this->id           = 'xflow_pix';
        $this->method_title = 'Pix (API xFlow)';
        $this->has_fields   = true;
        $this->form_fields  = array(
            'enabled' => array( 'title' => 'Ativar', 'type' => 'checkbox', 'label' => 'Ativar Pix', 'default' => 'no' ),
            'title'   => array( 'title' => 'Título', 'type' => 'text', 'default' => 'Pix' ),
        );
        $this->init_settings();
        $this->title = $this->get_option( 'title' );

        add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options' ) );
    }

    public function payment_fields() {
        echo '<p><label for="xflow_documento">CPF ou CNPJ</label>
              <input id="xflow_documento" name="xflow_documento" type="text" inputmode="numeric"></p>';
    }

    private function documento(): string {
        return preg_replace( '/\D/', '', (string) wp_unslash( $_POST['xflow_documento'] ?? '' ) );
    }

    public function validate_fields() {
        if ( ! in_array( strlen( $this->documento() ), array( 11, 14 ), true ) ) {
            wc_add_notice( 'Informe um CPF ou CNPJ válido para pagar com Pix.', 'error' );
            return false;
        }
        return true;
    }

    public function process_payment( $order_id ) {
        $order    = wc_get_order( $order_id );
        $centavos = (int) round( (float) $order->get_total() * 100 );

        $res = xflow_pix_api( 'POST', '/charges', array(
            'amountCents'        => $centavos,
            'customer'           => array(
                'name'     => $order->get_formatted_billing_full_name(),
                'email'    => $order->get_billing_email(),
                'document' => $this->documento(),
            ),
            'description'        => 'Pedido #' . $order->get_order_number(),
            'external_reference' => (string) $order->get_id(),
        ), 'woo-' . $order->get_id() . '-' . $centavos );

        if ( empty( $res['body']['id'] ) ) {
            $order->add_order_note( 'xFlow: ' . wp_json_encode( $res['body'] ) );
            wc_add_notice( 'Não foi possível gerar o Pix agora. Tente de novo.', 'error' );
            return array( 'result' => 'failure' );
        }

        $order->update_meta_data( '_xflow_charge_id', $res['body']['id'] );
        $order->update_status( 'on-hold', 'Aguardando pagamento Pix.' ); // também salva o meta
        WC()->cart->empty_cart();

        return array( 'result' => 'success', 'redirect' => $this->get_return_url( $order ) );
    }
}

Três escolhas que evitam problema:

  • A Idempotency-Key leva o id e o valor do pedido. No clique duplo, a API devolve a mesma cobrança. Se o cliente volta e muda o carrinho, a chave muda junto e nasce outra cobrança, em vez de um 409 idempotency_conflict.
  • O external_reference é o id do pedido. Ele volta no webhook, e é por ele que o handler acha o pedido.
  • Erro vira aviso no checkout. Valor fora do mínimo ou do máximo da conta volta como 400 validation, e falha em todas as adquirentes, como 502 charge_failed. O cliente vê o aviso, e a resposta, com o request_id, fica na nota do pedido para você achar a chamada em Integrações > Logs da API.

QR Code e copia e cola na página do pedido

Na página de pedido recebido, o WooCommerce dispara woocommerce_thankyou_ mais o id do gateway, com o id do pedido. Em vez de guardar o QR Code, a função consulta a cobrança, para nunca mostrar um código vencido.

php
function xflow_pix_pagina_do_pedido( $order_id ) {
    $order = wc_get_order( $order_id );
    if ( ! $order || $order->is_paid() ) {
        return;
    }
    $c = xflow_pix_api( 'GET', '/charges/' . rawurlencode( (string) $order->get_meta( '_xflow_charge_id' ) ) )['body'];

    $status = $c['status'] ?? '';
    if ( 'pending' !== $status ) {
        echo 'paid' === $status ? '<p>Pagamento recebido.</p>' : '<p>Este código Pix não está mais disponível. Fale com a loja.</p>';
        return;
    }
    printf(
        '<h2>Pague com Pix</h2><img src="%s" alt="QR Code Pix" width="240" height="240"><textarea readonly rows="4">%s</textarea>',
        esc_attr( $c['pix']['qrCodeBase64'] ),
        esc_textarea( $c['pix']['copyPaste'] )
    );
}

pix.qrCodeBase64 já é um PNG em data-URL, e pix.copyPaste é o código que o cliente cola no app do banco. A documentação não traz prazo de validade da cobrança nem webhook de expiração, então pedidos parados em "aguardando" precisam de uma rotina que consulte o status. O que fazer com expired está em cobrança Pix expirada.

Webhook: WC_API, HMAC e payment_complete

Pela API de callback do WooCommerce, a URL https://sualoja.com.br/wc-api/xflow_pix_webhook/ dispara a action woocommerce_api_xflow_pix_webhook. Cadastre essa URL em Integrações > Webhooks, com o evento transaction.paid.

php
function xflow_pix_webhook() {
    $raw      = file_get_contents( 'php://input' );
    $esperado = 'sha256=' . hash_hmac( 'sha256', $raw, XFLOW_WEBHOOK_SECRET );
    if ( ! hash_equals( $esperado, $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '' ) ) {
        status_header( 401 );
        exit;
    }

    $evento = json_decode( $raw, true );
    $data   = $evento['data'] ?? array();

    if ( 'transaction.paid' === ( $evento['event'] ?? '' ) && true === ( $data['livemode'] ?? null ) ) {
        $order = wc_get_order( (int) ( $data['external_reference'] ?? 0 ) );
        $ok    = $order
            && $order->get_meta( '_xflow_charge_id' ) === ( $data['id'] ?? '' )
            && (int) round( (float) $order->get_total() * 100 ) === (int) ( $data['amount'] ?? 0 );

        if ( $ok && ! $order->is_paid() ) {
            $order->payment_complete( $data['pix']['endToEnd'] ?? $data['id'] );
        }
    }

    status_header( 200 );
    exit;
}

O handler valida o HMAC sobre o corpo cru, confere se a cobrança e o valor batem com o pedido e só então chama payment_complete, o método que o WooCommerce indica para pagamento confirmado: ele muda o status para processando ou concluído e cuida do estoque. O End-to-end ID do Pix vira o id da transação. Como a entrega é reenviada até 3 vezes sem resposta 2xx, o teste is_paid() evita baixa em dobro. Eventos com livemode: false, de chave de teste, são ignorados.

Se preferir a REST API do WordPress, registre a rota com register_rest_route no hook rest_api_init, com permission_callback liberado (a autenticação é o HMAC), e leia o corpo cru com $request->get_body(). A validação e a baixa são as mesmas. Para testar uma assinatura, use o validador de assinatura de webhook.

Checkout em blocos

Para o Pix aparecer no checkout em blocos, o gateway precisa de uma integração de método de pagamento para blocos: uma classe que estende AbstractPaymentMethodType, registrada no hook woocommerce_blocks_payment_method_type_registration, e um script que chama registerPaymentMethod. No bloco, o cliente vê o componente que esse script registra. O bloco converte o payment_data enviado pelo script em $_POST e chama o mesmo process_payment, então o campo de documento precisa seguir no payment_data como xflow_documento.

Antes de publicar

  1. Teste com chaves pk_test_ numa loja de homologação: simule o pagamento com POST /api/v1/test/charges/{id}/pay e aceite livemode: false só lá. O roteiro está em API Pix sandbox.
  2. URL pública com https. URL de webhook em rede interna é recusada com 400 invalid_url.
  3. Pedidos parados. Programe a consulta das cobranças dos pedidos em "aguardando" e cancele ou ofereça código novo para os expirados.
  4. Allowlist de IP. Se ativar, inclua o IP de saída do servidor da loja. Fora da lista, a API responde 401.
  5. Revisão final com o checklist de integração Pix para produção.

Perguntas frequentes

Existe plugin oficial da xFlow para WooCommerce?

Não. A integração com WooCommerce é feita por um gateway de pagamento próprio, que chama a API REST da xFlow. O código deste guia é um ponto de partida para você adaptar à sua loja.

Onde mostrar o QR Code Pix no WooCommerce?

Na página de pedido recebido. O WooCommerce dispara o hook woocommerce_thankyou_ seguido do id do gateway, com o id do pedido. Nesse hook, consulte a cobrança em GET /api/v1/charges/{id} e mostre pix.qrCodeBase64 e pix.copyPaste enquanto o status for pending.

Qual URL de webhook cadastrar para o WooCommerce?

Com WC_API, a URL é https://sualoja.com.br/wc-api/nome_do_callback/, e o WooCommerce dispara a action woocommerce_api_nome_do_callback. Também dá para usar uma rota da REST API do WordPress. Em ambos os casos, a URL precisa ser https e pública.

O gateway aparece no checkout em blocos?

Só com uma integração própria para blocos: uma classe que estende AbstractPaymentMethodType, registrada no hook woocommerce_blocks_payment_method_type_registration, e um script que chama registerPaymentMethod. O processamento no servidor continua no process_payment.

Continue lendo

BlogAPI Pix em PHPPara integrar a API Pix em PHP, envie POST /api/v1/charges com cURL usando Basic auth (CURLOPT_USERPWD com pk_live_:sk_live_), exiba o pix.qrCodeBase64 e o pix.copyPaste da resposta e confirme o pagamento pelo webhook transaction.paid, validando o HMAC-SHA256 do corpo cru com hash_hmac e hash_equals. No sandbox, use chaves pk_test_ e simule o pagamento.BlogWebhook Pix: eventos, assinatura HMAC e boas práticasWebhook Pix é o aviso que a API envia para a sua URL quando algo acontece com uma cobrança ou um saque, como o pagamento confirmado. Na xFlow, cada aviso é um POST JSON assinado com HMAC-SHA256, reenviado até 3 vezes até você responder 2xx.BlogCobrança Pix expiradaNa API da xFlow, cobrança Pix expirada é a que passou ao status expired: expirou sem pagamento. Não há webhook de expiração, então você detecta pela consulta ou pela listagem com status=expired e, se o cliente ainda quiser pagar, cria uma cobrança nova para o mesmo pedido, com outra Idempotency-Key.BlogAPI Pix sandbox: como testar sem mover dinheiroO sandbox da API Pix da xFlow funciona com chaves pk_test_ e sk_test_: mesmos endpoints, mesmas respostas com livemode: false, nenhum dinheiro real e um endpoint para simular o pagamento que dispara o webhook transaction.paid.BlogChecklist de integração Pix para produçãoAntes de colocar a integração Pix em produção, confirme: credenciais de produção guardadas só no servidor, Idempotency-Key em toda cobrança e saque, webhook com assinatura validada e handler idempotente, conciliação pelo End-to-end ID, tratamento de limites e erros, fluxo de estorno e monitoramento. Abaixo, a lista para marcar item a item.