> ## Documentation Index
> Fetch the complete documentation index at: https://userepass.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Comissões

> Cálculo, holds, aprovação, void e clawback de comissões.

A **Comissão** (`comm_…`) é o lançamento financeiro individual gerado por cada cobrança bem-sucedida de uma [Conversão](/docs/conceitos/conversoes-e-fraude) ativa. O modelo da Repass é **por cobrança**, não por conversão: uma conversão recorrente gera uma comissão por ciclo de faturamento, cada uma com seu próprio cálculo auditável. Refunds e chargebacks geram comissões negativas (clawbacks) de primeira classe. Toda aritmética é inteira: valores em **centavos**, percentuais em **basis points** (bps, onde 1% = 100 bps).

Cada comissão carrega um campo `calculation` aberto que reconstrói "por que esse valor?". É a fonte de auditoria do dinheiro.

<Info>
  Não há rota para criar comissões `standard`. Elas são geradas automaticamente quando uma cobrança é processada. A API expõe leitura, void, aprovação antecipada e criação de bônus avulso. Veja a aba Referência da API para os endpoints detalhados.
</Info>

## Como uma comissão é calculada

Cada cobrança bem-sucedida de uma conversão ativa dispara o cálculo. O caminho é determinístico.

<Steps>
  <Step title="Resolução da regra (snapshot)">
    A conversão já carrega um `RuleSnapshot`, uma cópia congelada da [regra de comissão](/docs/conceitos/programas-e-regras) resolvida no momento da conversão. O cálculo usa **o snapshot, nunca a regra atual**. Mudar a regra do programa depois não altera comissões já calculadas; para retroagir, use [Reprocessamento](/docs/conceitos/reprocessamento).
  </Step>

  <Step title="Cobertura de recorrência">
    A política de recorrência decide se o ciclo atual é coberto e qual taxa aplicar (`one_time`, `lifetime`, `months:N`, `decreasing`). Fora da cobertura, nenhuma comissão é criada (`skipped: recurrence_exhausted`).
  </Step>

  <Step title="Base de cálculo">
    `gross` (default) usa o valor cobrado; `net_of_gateway_fees` subtrai a taxa do gateway antes de aplicar o percentual. A base vem do programa (`commissionBasis`).
  </Step>

  <Step title="Aplicação da taxa + arredondamento">
    Percentual ou valor fixo aplicado sobre a base, com arredondamento **half-up** no centavo, sobre o produto inteiro, nunca em float.
  </Step>

  <Step title="Split multi-touch (se aplicável)">
    Quando a atribuição tem múltiplos afiliados winners, o valor é dividido proporcionalmente ao peso (`weightBps`), com a sobra de arredondamento creditada ao maior peso.
  </Step>

  <Step title="Hold">
    A comissão `standard` nasce `pending` com `holdUntil = occurredAt + holdDays × 24h`.
  </Step>
</Steps>

### Base de cálculo

A base depende de `commissionBasis` no programa:

| `commissionBasis`     | Base                            | Regra                                              |
| --------------------- | ------------------------------- | -------------------------------------------------- |
| `gross` (default)     | valor cobrado                   | aplica a taxa direto sobre o valor da cobrança     |
| `net_of_gateway_fees` | valor cobrado − taxa do gateway | desconta `gatewayFeeCents` antes de aplicar a taxa |

Exemplo com `net_of_gateway_fees`: cobrança de `9900` centavos, taxa de gateway `900`, regra de `2000` bps (20%) → base `9000` → comissão `1800`.

### Arredondamento half-up

O percentual é aplicado em centavos com half-up no centavo, calculado sobre o **produto inteiro** (`amountCents × bps`), nunca em float:

```
quociente = floor(produto / 10000)
resto     = produto mod 10000
comissao  = quociente + (resto >= 5000 ? 1 : 0)
```

| Cobrança (centavos) | Taxa | Produto | Comissão                    |
| ------------------- | ---- | ------- | --------------------------- |
| 125                 | 20%  | 250000  | 25                          |
| 123                 | 20%  | 246000  | 25 (resto 6000 ≥ 5000 → +1) |
| 122                 | 20%  | 244000  | 24 (resto 4000 \< 5000)     |
| 25                  | 10%  | 25000   | 3 (2,5 → 3)                 |

<Warning>
  A API de criação de regras recebe e devolve percentual **decimal** (`0.00` a `100.00`, passo `0.01`), mas o armazenamento e todo o cálculo são em **bps** (`percentToBps = round(percentage × 100)`). Uma regra de `20.00%` vira `2000` bps internamente.
</Warning>

### Split multi-touch

Em atribuição com vários afiliados winners (modelos `linear`, `time_decay`, `position_based`), cada parte arredonda para **baixo** (`floor(total × weightBps / 10000)`) e a sobra inteira vai ao winner de **maior peso** (empate → o winner primário/cupom). A sobra creditada é registrada em `calculation.splitRemainderCents`.

Exemplo: total `1001` com pesos `7000` / `3000` bps → `701` (700 + 1 de sobra) e `300`.

<Note>
  Uma cobrança gera **no máximo uma comissão `standard` por afiliado**. Quando o multi-touch atribui mais de um clique ao mesmo afiliado, os pesos são agregados (somados) antes do split. Veja [Atribuição](/docs/conceitos/atribuicao) para os modelos e pesos. Comissão de valor `0` **é criada** (não suprimida) para preservar a numeração de `billingCycle` e a auditoria.
</Note>

### O campo `calculation`

Reconstrói o valor. Campos principais:

<ResponseField name="basis" type="string">
  `gross` ou `net_of_gateway_fees`.
</ResponseField>

<ResponseField name="chargedAmountCents" type="integer">
  Valor cobrado pelo gateway.
</ResponseField>

<ResponseField name="gatewayFeeCents" type="integer">
  Taxa do gateway (presente apenas em base `net_of_gateway_fees`).
</ResponseField>

<ResponseField name="baseAmountCents" type="integer">
  Base efetiva sobre a qual a taxa foi aplicada.
</ResponseField>

<ResponseField name="weightBps" type="integer">
  Peso do afiliado na atribuição (10000 = 100%). Em bônus, sempre `10000`.
</ResponseField>

<ResponseField name="rule" type="object">
  `{ ruleId, version, precedence, type }`: a regra que originou o valor. Ausente em `bonus` e `clawback`, que não derivam de regra.
</ResponseField>

<ResponseField name="appliedPercentageBps / appliedFixedAmountCents" type="integer">
  Taxa efetivamente aplicada (um ou outro, conforme o tipo da regra).
</ResponseField>

<ResponseField name="recurrenceKind" type="string">
  Política de recorrência usada no ciclo.
</ResponseField>

<ResponseField name="tier" type="object">
  `{ minCount, approvedConversionsCount }`: presente em regras `tiered`.
</ResponseField>

<ResponseField name="splitRemainderCents" type="integer">
  Sobra de arredondamento creditada (apenas em split multi-touch).
</ResponseField>

<ResponseField name="refund" type="object">
  `{ refundedAmountCents, chargedAmountCents, originalCommissionAmountCents, chargeback }`: presente em clawbacks.
</ResponseField>

## Tipos de comissão

| Tipo         | Origem                                                                     | Status inicial     | Hold?                 |
| ------------ | -------------------------------------------------------------------------- | ------------------ | --------------------- |
| `standard`   | cobrança de uma conversão ativa                                            | `pending`          | sim, sempre           |
| `bonus`      | criação avulsa pelo operador (`POST /commissions/bonus`)                   | `approved`         | não                   |
| `clawback`   | refund/chargeback de comissão paga, ou refund parcial                      | `approved`         | não (débito imediato) |
| `adjustment` | delta de [reprocessamento](/docs/conceitos/reprocessamento) sobre comissão paga | conforme reprocess | N/A                   |

## Ciclo de vida

A comissão tem quatro estados (`status`) mais um flag ortogonal `underReview`:

* **`pending`**: calculada, em carência (`holdUntil` no futuro). Só comissões `standard` nascem assim.
* **`approved`**: liberada para pagamento. Chega aqui pelo job de hold, por aprovação manual antecipada, ou já nasce assim (`bonus`, `clawback`).
* **`paid`**: quitada por um [Payout](/docs/conceitos/payouts) concluído (transição feita pelo módulo de payouts).
* **`voided`**: anulada (terminal).

O flag **`underReview`** é independente do status: uma comissão `approved` mas `underReview = true` está congelada (não entra em payout nem é projetada no saldo) por causa de uma revisão de ban.

```mermaid theme={null}
stateDiagram-v2
    [*] --> pending: recordCharge (standard)
    [*] --> approved: bonus
    [*] --> approved: clawback (amount negativo)

    pending --> approved: job de hold (early=false)
    pending --> approved: aprovação manual antecipada (early=true)
    pending --> voided: void manual
    pending --> voided: ban do afiliado
    pending --> voided: cancelamento no hold
    pending --> voided: refund total não paga

    approved --> paid: payout concluído
    approved --> voided: void manual (não paga, sem payout)

    state "approved + underReview" as review
    approved --> review: ban congela aprovada não paga
    review --> approved: aprovação limpa a revisão

    paid --> [*]
    voided --> [*]
```

### Hold e carência

Toda comissão `standard` nasce `pending` com uma janela de carência. O hold conta a partir de `occurredAt` da cobrança (não da data de registro):

```
holdUntil = occurredAt + program.holdDays × 24h
```

`holdDays` é configurável por programa (default 30, faixa 0 a 90). Ao fim do hold, um job promove `pending → approved` em lotes, **desde que**:

* a comissão esteja `pending` e `underReview = false`;
* a conversão de origem esteja `approved` (fraude resolvida);
* o afiliado não esteja `banned`.

`bonus` e `clawback` nunca entram no job: nascem `approved`.

### Aprovação antecipada e revisão de ban

`POST /commissions/:commissionId/approve` cobre dois cenários:

* **Aprovação antecipada** de uma comissão `pending`: aplica as mesmas guardas do job, **exceto** a de `holdUntil`. Emite `commission.approved` com `early: true`.
* **Limpeza de revisão**: sobre uma comissão `approved + underReview`, apenas limpa o flag (`underReview = false`) e emite `commission.review_cleared`, sem mudar o status, devolvendo a comissão ao fluxo de payout.

```bash theme={null}
curl -X POST https://api.userepass.com/commissions/comm_01J9Z.../approve \
  -H "Authorization: Bearer rstr_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json"
```

### Void

`POST /commissions/:commissionId/void` anula manualmente uma comissão. Só comissões `pending` ou `approved` **sem `payoutId`** podem ser anuladas; `paid`/`voided` ou já atribuídas a um payout são rejeitadas com `409 conflict`. Exige uma razão (`reason`, 1 a 500 caracteres).

```bash theme={null}
curl -X POST https://api.userepass.com/commissions/comm_01J9Z.../void \
  -H "Authorization: Bearer rstr_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"reason": "Conversão duplicada confirmada pelo gateway"}'
```

<Note>
  Cancelar uma assinatura enquanto há comissões `pending` voida essas comissões com razão `subscription_canceled_in_hold`, a menos que a organização desligue `void_on_cancel_in_hold` (default ON). Banir o afiliado voida suas `pending` e congela (`underReview`) suas `approved` não pagas.
</Note>

## Clawback (refund e chargeback)

Refunds são tratados como entidade de primeira classe e auditável, não como ajuste opaco. O comportamento depende de o refund ser total ou parcial e de a comissão original já ter sido paga.

| Situação                                  | Resultado                                                                 |
| ----------------------------------------- | ------------------------------------------------------------------------- |
| Refund **total** de comissão **não paga** | comissão anulada (`voided`, razão `refunded`/`chargeback`)                |
| Refund **total** de comissão **paga**     | `clawback` negativo de valor cheio (`approved`, sem hold)                 |
| Refund **parcial** (paga ou não)          | `clawback` negativo proporcional (`approved`, sem hold)                   |
| **Chargeback**                            | tratado como refund total + grava `fraudDecision: confirmed` na conversão |

O clawback é uma comissão `type: clawback`, `status: approved` (débito imediato, sem hold), com `amountCents` **negativo** e `originalCommissionId` apontando a comissão revertida. O refund parcial **sempre** vira clawback, mesmo que a comissão original não esteja paga.

Valor do clawback parcial (arredondado para baixo):

```
clawbackCents = -floor(originalAmountCents × refundedAmountCents / chargedAmountCents)
```

```mermaid theme={null}
sequenceDiagram
    participant API as Registro de refund
    participant Comm as Comissões

    API->>Comm: refund (conversionId, refundedAmount, sourceEventId, chargeback?)
    Comm->>Comm: já processado por este sourceEventId?
    alt replay
        Comm-->>API: comissões existentes (replayed: true)
    else novo
        Note over Comm: total = refundedAmount >= chargedAmount
        loop por comissão alvo (ignora já voided)
            alt total e não paga
                Comm->>Comm: anula a comissão (voided)
            else paga ou parcial
                Comm->>Comm: cria clawback negativo (approved)
            end
        end
        Comm-->>API: resultado
    end
```

Para o passo a passo completo de integração de refunds via API, veja o guia [Refund e clawback](/docs/guias/refund-e-clawback).

### Saldo do afiliado e projeção não-negativa

`GET /affiliates/:affiliateId/balance` retorna o saldo agregado por bucket e a projeção do próximo payout:

| Bucket             | O que conta                                              |
| ------------------ | -------------------------------------------------------- |
| `pendingCents`     | comissões `pending`                                      |
| `approvedCents`    | `approved`, sem payout, fora de revisão, exceto clawback |
| `underReviewCents` | `approved + underReview`                                 |
| `clawbackCents`    | clawbacks não voided, sem payout (valores negativos)     |

A projeção do próximo payout é `max(0, approvedCents + clawbackCents)`: **um débito que excede o crédito não vira cobrança ao afiliado**: rola para payouts futuros.

```bash theme={null}
curl https://api.userepass.com/affiliates/aff_01J9Z.../balance \
  -H "Authorization: Bearer rstr_..."
```

## Bônus

`POST /commissions/bonus` cria uma comissão `bonus` avulsa, fora do ciclo normal: sem conversão, sem ciclo de faturamento e **sem hold**. Nasce `approved`, com `currency` igual à moeda do programa do afiliado e `calculation.weightBps = 10000`.

```bash theme={null}
curl -X POST https://api.userepass.com/commissions/bonus \
  -H "Authorization: Bearer rstr_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "affiliateId": "aff_01J9Z...",
    "amountCents": 5000,
    "description": "Bônus por meta de Q2"
  }'
```

Bônus para um afiliado `banned` ou `rejected` é rejeitado com `409 conflict`.

## Listagem e idempotência

`GET /commissions` lista comissões com paginação por cursor (`limit` 1 a 100, default 25; `starting_after` / `ending_before`) e filtros: `affiliate_id`, `conversion_id`, `status`, `type` (clawbacks são listados via `type=clawback`), `billing_cycle`, `payout_id`, `occurred_after`, `occurred_before`. A resposta é `{ data, hasMore }`. O detalhe (`GET /commissions/:commissionId`) já vem com o `calculation` embutido: não há `expand[]` neste módulo.

```bash theme={null}
curl "https://api.userepass.com/commissions?affiliate_id=aff_01J9Z...&status=approved&limit=50" \
  -H "Authorization: Bearer rstr_..."
```

A geração de comissões é idempotente: cobranças e refunds já processados para um mesmo `sourceEventId` retornam o resultado existente (`replayed: true`) sem duplicar comissões. Reenviar o mesmo evento de cobrança ou refund é seguro.

Todas as rotas POST aceitam o header `Idempotency-Key` (veja [Idempotência](/docs/convencoes/idempotencia)); replays retornam a resposta armazenada com `Idempotent-Replay: true`.

## Eventos de domínio

Cada mudança de estado emite um evento, útil para assinar via webhook. Os principais deste módulo:

| Evento                        | Quando                                                                |
| ----------------------------- | --------------------------------------------------------------------- |
| `commission.created`          | comissão `standard` por afiliado winner, ou `bonus`                   |
| `commission.approved`         | aprovação pelo job de hold (`early: false`) ou manual (`early: true`) |
| `commission.voided`           | void manual, ban, refund total não paga, ou cancelamento no hold      |
| `commission.clawback_created` | clawback gerado por refund pago ou parcial                            |
| `commission.review_required`  | comissão aprovada não paga congelada por ban                          |
| `commission.review_cleared`   | operador limpa a revisão                                              |
| `commission.paid`             | comissão quitada por payout concluído (módulo de payouts)             |

Consulte o [catálogo de eventos](/docs/webhooks/catalogo-de-eventos) e o [event store](/docs/conceitos/event-store) para os payloads completos.

## Erros comuns

| HTTP | code                 | Quando                                                                                                                      |
| ---- | -------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| 400  | `parameter_invalid`  | corpo/querystring fora do schema (ex.: `reason` vazio, `amountCents < 1`, `percentage` fora de 0 a 100)                     |
| 401  | `unauthorized`       | sem sessão ou API key válida                                                                                                |
| 404  | `resource_not_found` | comissão, regra, programa ou afiliado inexistente na organização                                                            |
| 409  | `conflict`           | aprovar/void em status inválido; aprovar com conversão não aprovada ou afiliado banido; bônus para afiliado banned/rejected |

Veja [Erros](/docs/convencoes/erros) para o formato completo da resposta de erro.

## Próximos passos

<CardGroup cols={2}>
  <Card title="Programas e regras" icon="sliders" href="/docs/conceitos/programas-e-regras">
    Como configurar regras versionadas (percentage, fixed, tiered) e recorrência.
  </Card>

  <Card title="Refund e clawback" icon="rotate-left" href="/docs/guias/refund-e-clawback">
    Guia prático de como registrar refunds e chargebacks via API.
  </Card>

  <Card title="Payouts" icon="money-bill-transfer" href="/docs/conceitos/payouts">
    Como comissões aprovadas viram pagamentos, com netting de clawbacks.
  </Card>

  <Card title="Reprocessamento" icon="arrows-rotate" href="/docs/conceitos/reprocessamento">
    Aplicar uma nova versão de regra retroativamente, gerando ajustes auditáveis.
  </Card>
</CardGroup>
