Negociar em rodadas
Contrapropostas, histórico e aceite de rodadas para cotações negociadas.
Quando a loja liga o módulo enhanced-rfq, cliente e seller trocam propostas em rodadas antes de fechar. Cada rodada é uma cesta completa (produtos, quantidades e preços). Estas cinco rotas exigem os dois módulos, b2b-pricing e enhanced-rfq; sem o segundo, respondem 403 module_disabled.
Fazer contraproposta
Abre uma nova rodada de negociação com a cesta e os preços que o cliente quer. A cotação passa a negotiating, as propostas pendentes anteriores viram countered e o total da cotação passa a ser o da nova rodada. A loja define um máximo de rodadas (5 se não configurou).
/api/v1/storefront/quotes/{id}/counterClienteO cliente propõe uma nova cesta e novos preços
Parâmetros de caminho
idintegerobrigatórioCorpo da requisição
itemsarrayobrigatórioCesta proposta. Pelo menos um item.
product_iduuidobrigatórioquantityintegerobrigatórioprice_centsintegerobrigatórionotesstringmessagestringexpires_atstringexpired.Exemplo de requisição
curl -X POST "$BASE_URL/api/v1/storefront/quotes/42/counter" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"items": [
{
"product_id": "c3f92551-9dda-4fc7-b518-353d1c4e4035",
"quantity": 200,
"price_cents": 1950
}
],
"message": "Fechamos por R$ 19,50 a unidade?",
"expires_at": "2026-10-18T23:59:59Z"
}'Resposta
{
"id": "9d1c7e52-4a3b-4f60-8e1d-2b5a7c9e1f30",
"quote_id": 42,
"round_number": 1,
"initiated_by": "customer",
"message": "Fechamos por R$ 19,50 a unidade?",
"items": [
{
"product_id": "c3f92551-9dda-4fc7-b518-353d1c4e4035",
"quantity": 200,
"price_cents": 1950
}
],
"total_cents": 390000,
"status": "pending",
"expires_at": "2026-10-18T23:59:59Z",
"created_at": "2026-10-12T10:30:00Z"
}Campos da resposta
iduuidquote_idintegerround_numberintegerinitiated_bystringcustomervendoradminmessagestringitemsarrayproduct_id, product_name (nas consultas), quantity, price_cents (centavos por unidade) e notes.total_centsintegerprice_cents x quantity, em centavos.statusstringpendingacceptedrejectedcounteredexpiredcountered é uma proposta substituída por outra mais nova.expires_atstringcreated_atstringErros
| Status | Quando | Corpo de exemplo |
|---|---|---|
| 400 | O id não é um número. | {"error":"invalid quote id"} |
| 401 | Sem token de cliente no cabeçalho Authorization. | {"error":"missing authorization header"} |
| 403 | A cotação é de outro cliente. | {"error":"quote does not belong to this user"} |
| 403 | O módulo enhanced-rfq está desligado nesta loja. | {"error":"module_disabled","module":"enhanced-rfq"} |
| 404 | Cotação inexistente. | {"error":"quote not found"} |
| 400 | Corpo inválido, sem itens, quantidade ou preço inválido, cotação fora de um estado negociável (pending, quoted, negotiating) ou limite de rodadas atingido. | {"error":"counter-offer failed","detail":"maximum negotiation rounds (5) exceeded"} |
Ver o histórico da negociação
Lista as rodadas em ordem crescente, de quem quer que as tenha feito (cliente, seller ou administrador), junto com o limite e a rodada atual. É a base para a tela de negociação: a rodada pending mais recente é a que espera resposta.
/api/v1/storefront/quotes/{id}/negotiationsClienteTodas as rodadas de uma cotação
Parâmetros de caminho
idintegerobrigatórioExemplo de requisição
curl "$BASE_URL/api/v1/storefront/quotes/42/negotiations" \
-H "Authorization: Bearer $TOKEN"Resposta
{
"quote_id": 42,
"max_rounds": 5,
"current_round": 1,
"rounds": [
{
"id": "9d1c7e52-4a3b-4f60-8e1d-2b5a7c9e1f30",
"quote_id": 42,
"round_number": 1,
"initiated_by": "customer",
"message": "Fechamos por R$ 19,50 a unidade?",
"items": [
{
"product_id": "c3f92551-9dda-4fc7-b518-353d1c4e4035",
"product_name": "Caneta esferográfica azul (cx 50)",
"quantity": 200,
"price_cents": 1950
}
],
"total_cents": 390000,
"status": "pending",
"expires_at": "2026-10-18T23:59:59Z",
"created_at": "2026-10-12T10:30:00Z"
}
]
}Campos da resposta
quote_idintegermax_roundsintegercurrent_roundinteger0 se ninguém propôs ainda.expires_atstringroundsarrayErros
| Status | Quando | Corpo de exemplo |
|---|---|---|
| 400 | O id não é um número. | {"error":"invalid quote id"} |
| 401 | Sem token de cliente no cabeçalho Authorization. | {"error":"missing authorization header"} |
| 403 | A cotação é de outro cliente. | {"error":"quote does not belong to this user"} |
| 403 | O módulo enhanced-rfq está desligado nesta loja. | {"error":"module_disabled","module":"enhanced-rfq"} |
| 404 | Cotação inexistente. | {"error":"quote not found"} |
Aceitar uma rodada
Aceita a rodada indicada. A cotação vira accepted, as demais rodadas pendentes viram rejected, e as linhas da cotação são reescritas com a cesta da rodada (quantidades e preços, inclusive itens incluídos ou removidos na negociação). Depois, leve ao carrinho.
/api/v1/storefront/quotes/{id}/accept-round/{roundId}ClienteFecha a negociação com os preços de uma rodada
Parâmetros de caminho
idintegerobrigatórioroundIduuidobrigatórioid de uma rodada).Exemplo de requisição
curl -X POST "$BASE_URL/api/v1/storefront/quotes/42/accept-round/9d1c7e52-4a3b-4f60-8e1d-2b5a7c9e1f30" \
-H "Authorization: Bearer $TOKEN"Resposta
{
"message": "round accepted"
}Erros
| Status | Quando | Corpo de exemplo |
|---|---|---|
| 400 | O id não é um número. | {"error":"invalid quote id"} |
| 401 | Sem token de cliente no cabeçalho Authorization. | {"error":"missing authorization header"} |
| 403 | A cotação é de outro cliente. | {"error":"quote does not belong to this user"} |
| 403 | O módulo enhanced-rfq está desligado nesta loja. | {"error":"module_disabled","module":"enhanced-rfq"} |
| 404 | Cotação inexistente. | {"error":"quote not found"} |
| 400 | A rodada não existe nesta cotação ou não está pending. | {"error":"failed to accept round","detail":"round is not in pending status (current: A)"} |
| 422 | A regra de margem da loja recusou o preço da rodada. O corpo traz ok: false e um reason (below_floor, cost_missing ou unmodelled_offer). | {"ok":false,"reason":"below_floor"} |
Recusar uma rodada
Marca a rodada como rejected, com um motivo opcional que é anexado à mensagem da rodada. A cotação continua negotiating: o cliente pode fazer uma nova contraproposta ou desistir de vez com recusar cotação.
/api/v1/storefront/quotes/{id}/reject-round/{roundId}ClienteRecusa uma proposta do seller sem encerrar a cotação
Parâmetros de caminho
idintegerobrigatórioroundIduuidobrigatórioid de uma rodada).Corpo da requisição
reasonstringExemplo de requisição
curl -X POST "$BASE_URL/api/v1/storefront/quotes/42/reject-round/9d1c7e52-4a3b-4f60-8e1d-2b5a7c9e1f30" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"reason": "Abaixo do nosso orçamento"
}'Resposta
{
"message": "round rejected"
}Erros
| Status | Quando | Corpo de exemplo |
|---|---|---|
| 400 | O id não é um número. | {"error":"invalid quote id"} |
| 401 | Sem token de cliente no cabeçalho Authorization. | {"error":"missing authorization header"} |
| 403 | A cotação é de outro cliente. | {"error":"quote does not belong to this user"} |
| 403 | O módulo enhanced-rfq está desligado nesta loja. | {"error":"module_disabled","module":"enhanced-rfq"} |
| 404 | Cotação inexistente. | {"error":"quote not found"} |
| 400 | A rodada não existe nesta cotação ou não está pending. | {"error":"failed to reject round","detail":"round is not in pending status (current: R)"} |
Levar ao carrinho
Passo final da negociação: pega a cotação já accepted e coloca os itens no carrinho com os preços acordados. A cotação vira converted. Diferente de levar ao carrinho em um passo, esta rota não aceita sozinha: exige a cotação já aceita.
/api/v1/storefront/quotes/{id}/convert-to-cartClienteColoca a cotação aceita no carrinho do cliente
Parâmetros de caminho
idintegerobrigatórioExemplo de requisição
curl -X POST "$BASE_URL/api/v1/storefront/quotes/42/convert-to-cart" \
-H "Authorization: Bearer $TOKEN"Resposta
{
"cart_id": "b7a3c1e9-6d24-4f8a-a0b5-93e1c7d2f468",
"quote_id": 42,
"items_added": 1,
"total_cents": 390000
}Campos da resposta
cart_iduuidquote_idintegeritems_addedintegertotal_centsintegerErros
| Status | Quando | Corpo de exemplo |
|---|---|---|
| 400 | O id não é um número. | {"error":"invalid quote id"} |
| 401 | Sem token de cliente no cabeçalho Authorization. | {"error":"missing authorization header"} |
| 403 | A cotação é de outro cliente. | {"error":"quote does not belong to this user"} |
| 403 | O módulo enhanced-rfq está desligado nesta loja. | {"error":"module_disabled","module":"enhanced-rfq"} |
| 404 | Cotação inexistente. | {"error":"quote not found"} |
| 400 | A cotação não está accepted, expirou ou não tem itens válidos. | {"error":"failed to convert quote to cart","detail":"quote must be in 'accepted' status to convert (current: negotiating)"} |