Customers
O recurso Customer — identificação por email, documento (CPF/CNPJ), endereço, e o que ele habilita em PaymentIntent e CustomerSubscription.
Customer é a entidade pessoa da Zhex. Cada cliente que volta para uma segunda compra, todo método salvo para cobrança recorrente, todo email de fatura — passa por aqui. Sem Customer, o PaymentIntent exige um — customer é parâmetro obrigatório no POST /v1/payment_intents.
Pense no Customer como um container leve: identidade (email, name, document), endereço, e a chave para anexar PaymentMethod e ler CustomerSubscription.
Anatomia
{
"id": "cus_NffrFeUfNV2Hib",
"object": "customer",
"livemode": false,
"email": "joao@meusite.com",
"name": "João Silva",
"phone": "+5511999990000",
"document": "12345678909",
"country": "BR",
"address": {
"line1": "Rua Tal, 100",
"number": "100",
"neighborhood": "Pinheiros",
"city": "São Paulo",
"state": "SP",
"postal_code": "01000-000",
"country": "BR"
},
"created": 1714060000
}Campos opcionais; o único obrigatório no create é email e name. Email é o que vai no recibo, em cobrança recorrente falhada, no link de "atualize seu cartão". Sem email, esses fluxos quebram.
Criar customer
curl https://prometheus.zhex.io/v1/customers \
-H "Authorization: Bearer $ZHEX_SECRET_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"email": "joao@meusite.com",
"name": "João Silva",
"phone": "+5511999990000",
"document": "12345678909",
"address": "Rua Tal",
"number": "100",
"neighborhood": "Pinheiros",
"city": "São Paulo",
"state": "SP",
"zipcode": "01000-000",
"country": "BR"
}'Boa prática: salve customer.id na sua coluna users.zhex_customer_id no signup. Você consulta sempre via essa FK, nunca por busca por email — é mais rápido.
Idempotente por email. Chamar POST /v1/customers com um email que já existe na sua conta (no mesmo modo) retorna o customer existente — não erra. Os campos mutáveis (name, phone, document, endereço) são atualizados com o que veio no request; o id permanece o mesmo. Você pode tratar customers.create como find-or-create sem precisar fazer GET /v1/customers?email= antes.
Endereço — campos do payload vs response
No request (create/update) você manda os campos individuais:
| Campo | Notas |
|---|---|
address | Logradouro (rua, avenida) |
number | Número do endereço |
neighborhood | Bairro |
city | Cidade |
state | UF |
zipcode | CEP |
country | ISO 3166-1 alpha-2 (default BR) |
No response, eles vêm aninhados em address no formato Stripe-style: address: { line1, number, neighborhood, city, state, postal_code, country }. Mapping é direto — address (request) → line1 (response), zipcode → postal_code.
Documento (CPF/CNPJ)
A Zhex aceita o documento como string plana — formate como preferir, dígitos ou com pontuação. O campo document cobre CPF, CNPJ, EIN, RFC e equivalentes; o país é inferido por country no Customer.
await zhex.customers.create({
email: 'empresa@acme.com.br',
name: 'ACME Comércio',
document: '11.222.333/0001-44', // CNPJ
country: 'BR',
});Para cobrar boleto no Brasil, o documento é mandatório — sem ele a CIP recusa o registro. Para cartão, é altamente recomendado (reduz fricção em verificação de risco).
Update
POST /v1/customers/:id faz shallow merge — só os campos enviados mudam:
await zhex.customers.update('cus_…', {
city: 'Rio de Janeiro',
state: 'RJ',
});Stripe-style: POST no recurso, não PATCH/PUT.
Buscar e listar
// retrieve por id
const customer = await zhex.customers.retrieve('cus_…');
// busca por email (paginated, pode haver múltiplos em test mode)
for await (const c of zhex.customers.list({
email: 'joao@meusite.com',
limit: 5,
}).autoPagingEach()) {
/* ... */
}Test mode vs Live mode
A unicidade de email é por modo: (companyId, email, livemode). O mesmo email pode existir uma vez em test e uma vez em live — sem colisão.
Cross-mode lookup retorna 404 (genérico, sem confirmar existência) — uma chave de test não enxerga clientes live e vice-versa.
Anexar PaymentMethod
PaymentMethod (pm_*) é a representação persistente de um meio de pagamento — diferente do Token que é single-use. Para cobrar de novo sem pedir o cartão:
// 1. cliente preenche zhex.js → vira tok_*
// 2. front manda tok_* pro seu servidor
// 3. seu servidor cria pm_* a partir de tok_*
const pm = await zhex.paymentMethods.create({
type: 'card',
token: 'tok_…',
customer: 'cus_…',
});
// pm.id → "pm_…", pm.card.{brand,last4,exp_month,exp_year}O token é consumido atomicamente — uma segunda chamada com o mesmo tok_* falha com token_unusable.
Listar payment methods de um customer
for await (const pm of zhex.paymentMethods.list({
customer: 'cus_…',
limit: 10,
}).autoPagingEach()) {
/* pm.card.brand, pm.card.last4, pm.card.exp_month, pm.card.exp_year */
}customer é obrigatório nessa listagem — não há "list all" cross-customer.
Detach
await zhex.paymentMethods.detach('pm_…');Marca o pm_* como inativo (soft detach). Histórico de transações que o usaram continua intacto. O cartão não pode mais ser cobrado a partir desse pm_* — para nova cobrança, gere novo tok_*.
Webhooks emitidos
Hoje a API V1 emite os seguintes events relacionados a Customer/PaymentMethod:
| Evento | Quando |
|---|---|
Nenhum direto sobre Customer ainda | Roadmap — emit em create/update/detach |
Customer mutações ainda não disparam webhook próprio. A próxima minor de Zhex-Version adiciona customer.created, customer.updated, payment_method.attached e payment_method.detached. Acompanhe o changelog.
Para reagir a side-effects de cobrança (recorrência, falha, refund), escute os events de PaymentIntent e CustomerSubscription.
Próximos passos
Tokenização
Token vs PaymentMethod
Assinaturas
Como Customer + pm_default vira cobrança recorrente
Idempotência
Não duplicar Customer no signup com retry
Atualizado em