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
/**
* 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
// 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-Keyleva 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 um409 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, como502 charge_failed. O cliente vê o aviso, e a resposta, com orequest_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.
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.
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
- Teste com chaves
pk_test_numa loja de homologação: simule o pagamento comPOST /api/v1/test/charges/{id}/paye aceitelivemode: falsesó lá. O roteiro está em API Pix sandbox. - URL pública com https. URL de webhook em rede interna é recusada com
400 invalid_url. - Pedidos parados. Programe a consulta das cobranças dos pedidos em "aguardando" e cancele ou ofereça código novo para os expirados.
- Allowlist de IP. Se ativar, inclua o IP de saída do servidor da loja. Fora da lista, a API responde
401. - 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.