Minstrel

Pagamentos

Split de pagamento

Divida o valor de um pagamento entre recebedores com parcelas fixas e percentuais, saiba para onde vao os centavos e simule antes de cobrar.

O split divide o valor de um pagamento entre recebedores: o vendedor de um marketplace, um parceiro, um afiliado. Cada recebedor leva exatamente a parcela configurada para ele. O que nao foi dividido fica com a conta principal - a do estabelecimento que cobra - e nao gera linha de split.

Por isso nao e preciso cadastrar a plataforma como recebedor para ficar com a comissao: "90% para o vendedor" basta, e os outros 10% ficam com voce.

Cadastre os recebedores

curl -X POST http://localhost:3333/v1/recipients \
  -H "Authorization: Bearer $MINSTREL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Loja do Vendedor ME",
    "document": "12.345.678/0001-95",
    "email": "financeiro@lojadovendedor.com.br",
    "externalRecipientId": "rec_8812",
    "bankCode": "341",
    "bankBranch": "1234",
    "bankAccount": "56789-0",
    "pixKey": "12345678000195",
    "defaultSplitType": "percentage",
    "defaultSplitValue": 9000,
    "liableForChargeback": true
  }'
CampoPara que serve
document, documentTypeCPF ou CNPJ (inclusive o alfanumerico), com digito verificador conferido. O tipo e deduzido pelo tamanho; para outros documentos, informe passport ou other.
externalRecipientIdId do recebedor no parceiro (subadquirente). E o que vai nas instrucoes de split. Unico por ambiente.
connectorAccountIdConta de conector em que o recebedor esta registrado.
bankCode, bankBranch, bankAccountConta para repasse: codigo COMPE do banco (3 digitos), agencia e conta com digito. Os tres vem juntos; bankAccountType e checking (padrao) ou savings.
pixKeyCPF, CNPJ, e-mail, celular com +55 ou chave aleatoria.
defaultSplitType, defaultSplitValueRegra padrao do recebedor, usada pelo simulador e sugerida ao montar o no Split. Vem em par.
liableForChargebackO recebedor arca com o chargeback da parcela dele. Padrao false.
chargeProcessingFeeO recebedor paga parte da tarifa de processamento. Padrao false (veja Tarifa de processamento).
  • O recebedor pertence ao ambiente da credencial que o criou: um split de producao nunca paga um recebedor de sandbox, e vice-versa.
  • Celular sem o +55 e recusado: 11987654321 seria indistinguivel de um CPF, e repasse para a chave errada nao tem estorno automatico.
  • A listagem e os eventos de webhook trazem documento, conta e chave Pix mascarados (masked: true). Os dados completos so voltam em GET /v1/recipients/:id e na resposta de quem acabou de envia-los.

Gerenciar recebedores

EndpointO que faz
GET /v1/recipientsLista com filtros (active, search, document, splitType, destination, liableForChargeback) e o volume dividido no periodo (from e to, padrao 30 dias).
GET /v1/recipients/summaryRecebedores ativos, volume dividido contra o periodo anterior, total a repassar e maiores recebedores.
GET /v1/recipients/:idDados completos, totais e os fluxos publicados que usam o recebedor (usedByFlows).
GET /v1/recipients/:id/splitsParcelas do recebedor, pagamento a pagamento, da mais recente para a mais antiga.
GET /v1/recipients/:id/ledgerLancamentos da conta do recebedor no razao contabil, com o saldo.
GET /v1/recipients/:id/payoutsRepasses do recebedor.
PATCH /v1/recipients/:idEdicao parcial: campo ausente fica como esta, null apaga.
DELETE /v1/recipients/:idRemocao logica. Os splits ja gravados continuam no historico (preservedSplits informa quantos).

Leitura exige o escopo recipients:read; cadastro, edicao e remocao, recipients:write.

Desativar (active: false) ou remover um recebedor que esta no split de um fluxo publicado e recusado com 409, com os fluxos em meta.flows: os pagamentos desse fluxo passariam a falhar. Retire o recebedor do no Split e publique o fluxo antes. Com repasse em andamento, a remocao tambem e recusada. Toda troca de banco ou chave Pix fica na auditoria como recipient.payout_destination_changed - trocar a conta de repasse e a fraude classica de marketplace.

Divida o pagamento

Envie splits na criacao do pagamento:

curl -X POST http://localhost:3333/v1/payments \
  -H "Authorization: Bearer $MINSTREL_API_KEY" \
  -H "Idempotency-Key: pedido-7781" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 10003,
    "customerId": "cus_01HQ...",
    "paymentMethodId": "pm_01HQ...",
    "splits": [
      { "recipientId": "rcp_vendedor", "type": "percentage", "value": 2500 },
      { "recipientId": "rcp_parceiro", "type": "percentage", "value": 2500 }
    ],
    "splitRemainderRecipientId": "rcp_parceiro"
  }'
Campo de splits[]ObrigatorioSignificado
recipientIdsimRecebedor cadastrado, ativo e do mesmo ambiente da chave.
typesimpercentage ou fixed.
valuesimInteiro. Em percentage, basis points: 1000 e 10%, 250 e 2,5%. Em fixed, centavos: 1500 e R$ 15,00.
liableForChargebacknaoOmitido, vale o padrao cadastrado no recebedor.
chargeProcessingFeenaoOmitido, vale o padrao cadastrado no recebedor.
  • No maximo 20 parcelas, uma por recebedor.
  • type e value sao obrigatorios na API. A regra padrao do recebedor (defaultSplitType e defaultSplitValue) so e usada pelo simulador.
  • splitRemainderRecipientId e opcional. Precisa ser um dos recebedores de splits e fica com os centavos de arredondamento. Sem ele, os centavos ficam com a conta principal.

Como o valor e calculado

A conta segue quatro regras, nesta ordem:

  1. As parcelas fixas sao reservadas primeiro.
  2. Os percentuais valem sobre o que sobra (valor menos fixas), literalmente: 10% sao 10%. Cada parte e truncada no centavo, entao nenhum recebedor leva mais do que configurou.
  3. Centavos de arredondamento sao a diferenca entre o grupo percentual truncado uma unica vez e a soma das partes truncadas uma a uma. Vao para splitRemainderRecipientId ou ficam com a conta principal.
  4. O que nao foi dividido fica com a conta principal, sem linha de split.

A soma sempre fecha ao centavo: parcelas mais conta principal da exatamente o valor do pagamento. O banco reforca a regra com um trigger que impede gravar splits que somem mais do que o pagamento.

10% sozinho

R$ 100,00 com uma unica parcela de 10% (value: 1000):

QuemValor
RecebedorR$ 10,00
Conta principalR$ 90,00

Dois de 50% com centavo impar

R$ 100,01 em duas parcelas de 50% (value: 5000). Cada metade, truncada, da R$ 50,00; o grupo de 100% vale R$ 100,01. Sobra 1 centavo:

QuemSem recebedor dos centavosCentavos para A
Recebedor AR$ 50,00R$ 50,01
Recebedor BR$ 50,00R$ 50,00
Conta principalR$ 0,01R$ 0,00

"Centavos para A" e splitRemainderRecipientId apontando para o recebedor A.

25% mais 25% com os centavos para B

E o pagamento do exemplo acima: R$ 100,03 com duas parcelas de 25% e splitRemainderRecipientId apontando para B. As partes truncadas dao R$ 25,00 cada; o grupo de 50%, truncado uma vez, da R$ 50,01:

QuemValor
Recebedor AR$ 25,00
Recebedor BR$ 25,01 (R$ 25,00 mais 1 centavo)
Conta principalR$ 50,02

Sem splitRemainderRecipientId, B ficaria com R$ 25,00 e a conta principal com R$ 50,03.

Fixo mais percentual

R$ 100,00 com R$ 15,00 fixos para A (type: fixed, value: 1500) e 20% para B. A parcela fixa sai primeiro, e os 20% valem sobre os R$ 85,00 restantes:

QuemValor
Recebedor AR$ 15,00
Recebedor BR$ 17,00 (20% de R$ 85,00)
Conta principalR$ 68,00

Parcela zerada

0,5% (value: 50) de uma venda de R$ 1,50 da R$ 0,0075, truncado para zero. A parcela e gravada com valor zero, mas nao vai ao parceiro.

Erros de validacao

O split da requisicao e validado antes de o pagamento ser criado: configuracao invalida recusa a chamada inteira e nao deixa pagamento pela metade. Todos os casos respondem 422, com type: invalid_request_error, code: validation_failed e param: splits - menos o ultimo, em que param e o proprio splitRemainderRecipientId:

SituacaoEm meta
Parcelas fixas somam mais que o valor do pagamentoreason: fixed_exceeds_amount
Percentuais somam mais de 100% (10000)reason: percentage_over_100
O mesmo recebedor em duas parcelasrecipientId
Recebedor inexistente, inativo ou de outro ambientemissingRecipientIds
Recebedor dos centavos fora das parcelas-
{
  "error": {
    "type": "invalid_request_error",
    "code": "validation_failed",
    "message": "A soma dos percentuais do split e 110.00%, acima de 100%.",
    "param": "splits",
    "requestId": "req_01HQ...",
    "meta": { "reason": "percentage_over_100" }
  }
}

Mais de 20 parcelas, type desconhecido e value negativo ou fracionado sao barrados antes, na validacao do corpo, tambem com 422.

Na resposta do pagamento

O pagamento devolvido traz as parcelas calculadas:

{
  "object": "payment",
  "id": "pay_01HQ...",
  "amount": 10003,
  "splits": [
    { "recipientId": "rcp_vendedor", "amount": 2500, "type": "percentage", "liableForChargeback": true },
    { "recipientId": "rcp_parceiro", "amount": 2501, "type": "percentage", "liableForChargeback": false }
  ]
}

Em GET /v1/payments/:id, cada parcela tambem traz id, o nome do recebedor em recipient, o valor configurado em configuredValue (basis points ou centavos), chargeProcessingFee e feeAmount. Veja Transacoes.

Precedencia e envio ao parceiro

  • O split da requisicao vence o do fluxo. Com split gravado, o no Split nao recalcula; a etapa registra "O pagamento ja tem split gravado (da requisicao ou de uma execucao anterior); as regras deste no nao foram reaplicadas.". O mesmo vale quando o fluxo do pagamento e retomado (revisao manual, espera): a divisao de um pagamento e decidida uma unica vez.
  • Sem no Split no fluxo, o split da requisicao segue para o conector do mesmo jeito - inclusive no roteamento direto, sem fluxo publicado.
  • A autorizacao e a captura total levam as instrucoes. Para cada parcela, o conector recebe o externalRecipientId do recebedor (ou o id dele no Minstrel, quando nao ha id externo), o valor, liableForChargeback e chargeProcessingFee.
  • Captura parcial segue sem split. As parcelas gravadas foram calculadas sobre o valor autorizado, e o recalculo para o valor capturado ainda nao existe. E uma limitacao conhecida: se a divisao importa, capture o total.
  • Parcela de valor zero fica gravada, mas nao vai ao parceiro.

Quem liquida o dinheiro de cada recebedor e o parceiro. O catalogo (GET /v1/connectors) indica a capacidade split em capabilities. Os adquirentes disponiveis hoje - Minstrel Sandbox e Cielo - nao tem essa capacidade e nao repassam as instrucoes: o valor e liquidado inteiro para o estabelecimento, e a divisao fica registrada no Minstrel para consulta e conciliacao.

No construtor de fluxos

O no Split divide o valor com as mesmas regras e o mesmo calculo. Ele precisa vir antes da rota, porque as instrucoes viajam com a autorizacao. Um Split depois da rota grava a divisao, mas a autorizacao ja saiu sem ela: a validacao avisa com split_after_route. E um aviso, nao um erro - a publicacao segue e devolve o aviso em warnings.

A publicacao recusa com 422 (flow_error, codigo flow_invalid_graph):

  • recebedor repetido no mesmo no;
  • percentuais acima de 100%;
  • recebedor dos centavos (remainderRecipientId) fora das regras;
  • recebedor inexistente no ambiente ou removido;
  • recebedor inativo.

Os tres primeiros vem em meta.issues, com o no e o campo de cada problema; os dois ultimos, com os ids em meta.missingRecipientIds e meta.inactiveRecipientIds.

So uma coisa aparece apenas na execucao: parcelas fixas acima do valor, que so e conhecido no pagamento. Nesse caso a requisicao falha com o mesmo 422 da tabela de erros e o pagamento fecha como failed, com o evento payment.failed - nunca fica preso em processing. Confira as regras no simulador antes de publicar.

No editor do painel, ao escolher o recebedor, a regra padrao, o chargeback e a taxa vem do cadastro dele. No grafo salvo os valores ficam explicitos: um no criado pela API sem liableForChargeback e chargeProcessingFee usa false.

O modelo Marketplace com split ("template": "marketplace-split" em POST /v1/flows) ja vem na ordem certa: gatilho, Split, rota e captura. Ele divide 10% para a plataforma e 90% para o vendedor, com os centavos para a plataforma. Troque os ids de exemplo (rcp_plataforma, rcp_vendedor e ca_subadquirente_split) pelos seus antes de publicar. Como o que nao e dividido fica com a conta principal, voce tambem pode manter so a parcela do vendedor e deixar a comissao com a conta principal.

Tarifa de processamento

chargeProcessingFee e gravado em cada parcela e enviado ao conector, mas o rateio da tarifa ainda nao e calculado: o feeAmount de toda parcela e 0. O simulador avisa com fees_not_calculated quando alguma regra marca o campo.

Simule antes de cobrar

POST /v1/recipients/split-preview calcula com a mesma funcao do motor de pagamentos, sem gravar nada (escopo recipients:read):

curl -X POST http://localhost:3333/v1/recipients/split-preview \
  -H "Authorization: Bearer $MINSTREL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 10003,
    "rules": [
      { "recipientId": "rcp_vendedor", "type": "percentage", "value": 2500 },
      { "recipientId": "rcp_parceiro", "type": "percentage", "value": 2500 }
    ],
    "remainderRecipientId": "rcp_parceiro"
  }'

Resposta (resumida):

{
  "object": "split_preview",
  "amount": 10003,
  "currency": "BRL",
  "valid": true,
  "parts": [
    { "recipientId": "rcp_vendedor", "name": "Loja do Vendedor ME", "source": "request", "amount": 2500, "effectiveBps": 2499, "roundingCents": 0 },
    { "recipientId": "rcp_parceiro", "name": "Parceiro Exemplo", "source": "request", "amount": 2501, "effectiveBps": 2500, "roundingCents": 1 }
  ],
  "total": 5001,
  "fixedTotal": 0,
  "percentageTotalBps": 5000,
  "remainingForPercentages": 10003,
  "mainAccountAmount": 5002,
  "roundingCents": 1,
  "issues": [
    { "severity": "info", "code": "main_account_share", "message": "A conta principal fica com 5002 centavos: a parte que nao foi dividida." },
    { "severity": "info", "code": "rounding_cents", "ruleIndex": 1, "message": "Os percentuais nao dividem a venda em centavos exatos: 1 centavo(s) de arredondamento vao para Parceiro Exemplo." }
  ]
}

Diferencas em relacao ao pagamento, pensadas para a tela de simulacao:

  • regra sem type e value usa a regra padrao do recebedor (source: "default");
  • regra com label e sem recipientId vale so para simular ("Plataforma", por exemplo);
  • configuracao invalida nao vira erro HTTP: a resposta e 200 com valid: false, os valores nulos e os problemas em issues. So o corpo mal formado (mais de 20 regras, type sem value) responde 422.
codeSeveridadeSignificado
recipient_not_founderrorRecebedor inexistente no ambiente ou removido.
recipient_inactiveerrorRecebedor inativo: o pagamento seria recusado.
rule_incompleteerrorRegra sem type e value, e o recebedor nao tem regra padrao.
duplicate_recipienterrorRecebedor em mais de uma parcela.
remainder_not_in_ruleserrorRecebedor dos centavos fora das parcelas.
fixed_exceeds_amounterrorParcelas fixas acima do valor.
percentage_over_100errorPercentuais acima de 100%.
fixed_firstinfoHa fixas e percentuais: as fixas saem primeiro e os percentuais valem sobre o resto.
main_account_shareinfoQuanto fica com a conta principal (meta.amount e meta.bps).
rounding_centsinfoCentavos de arredondamento e quem fica com eles.
unregistered_rulesinfoHa regra sem recebedor cadastrado: serve para simular, nao para cobrar.
fees_not_calculatedinfoAlguma regra marca chargeProcessingFee, cujo rateio ainda nao e calculado.

effectiveBps e a participacao efetiva de cada parcela na venda, em basis points, so para exibicao.

Saldo do recebedor

GET /v1/recipients e GET /v1/recipients/:id trazem o valor a receber em stats.toReceive, com a origem do numero em source:

  • ledger: saldo do razao contabil (creditos menos debitos da conta do recebedor);
  • estimate: estimativa - parcelas de pagamentos capturados, menos as revertidas, as tarifas e os repasses registrados.

Hoje nenhum processo grava lancamentos de recebedor no razao nem repasses, entao o numero vem como estimate. Na estimativa, estorno total reverte a parcela inteira; chargeback reverte so a parcela de quem tem liableForChargeback; e estorno parcial e captura parcial nao sao rateados. Em GET /v1/recipients/:id/splits, cada parcela diz como entrou na conta em effect: counted, reversed, awaiting_capture, in_progress ou not_captured.

Eventos

EventoQuando acontece
recipient.createdRecebedor cadastrado.
recipient.updatedDados alterados. previousAttributes traz os valores anteriores, com documento, conta e chave Pix mascarados.
recipient.deletedRecebedor removido. Os splits gravados continuam no historico; pagamentos novos nao podem usa-lo.

O data.object desses eventos traz o recebedor na versao mascarada: webhook sai da nossa infraestrutura e nao precisa do numero da conta para cumprir o seu papel. Veja Webhooks.