Tax Extension¶
br.dev.bcp.shopping.tax extends checkout and order.
Overview¶
This extension carries the Brazilian tax breakdown in the transaction: per line item, the tax type, rate, calculation base, amount, and destination federative authority, plus the product's fiscal classification (NCM). It supports the price-transparency requirements of Law 12,741/2012 ("De Olho no Imposto") and Consumer Defense Code art. 31.
The tax type vocabulary is intentionally open. Brazil's 2026-2033 tax reform changes the mix year over year (ICMS/ISS/DIFAL transitioning out; IBS/CBS/IS transitioning in), and a closed enum would break the schema at every turn.
The authority field exposes the federative level each tax funds
(federal, state, municipal), so the consumer can see how much goes to
each level, as required by the price-transparency law (Law 12,741/2012).
These are the three spheres the law recognizes — there is no fourth level to
invent. IBS, although a single tax, is split between state and municipal
government with their own rates (the Federal Revenue calculator returns
gIBSUF and gIBSMun separately, and the NF-e breaks them out into distinct
groups): it is represented as two entries, state and municipal, each
with its own rate and amount. That same separability underlies IBS/CBS split
payment at settlement (LC 214/2025, from 2027).
The behavior field states where the amount lives in the price
arithmetic: inclusive when the tax is already contained in the item price
(ICMS, which is calculated "inside" the price — LC 87/96 art. 13, §1, I) and
exclusive when it is charged on top of it. Without this distinction,
subtotal + Σtaxes does not reconcile with total, and a consumer or agent
that adds the taxes to the subtotal overstates the price. The transition
makes this unavoidable: between 2026 and 2032 the same item carries tax
from both systems — ICMS inside the price and IBS/CBS calculated on top
(LC 214/2025 art. 12, which excludes them from their own base) — and only the
seller knows which is which.
In a consumer sale, prices are advertised with tax already included (Consumer
Defense Code art. 31), so the norm in the protocol is inclusive, including
for IBS/CBS: the seller grosses up the price at pricing time, and the
incidence base ends up below the item price. That is why base can
diverge from price without being an error.
2026 is a test year. The CBS (0.9%) and IBS (0.1%) rates are trial rates: disclosure on the fiscal document is mandatory, but collection is waived for sellers that meet the accessory obligations, and the amount is offsettable against PIS/Cofins — the tax burden does not increase (Federal Revenue, 2026 Guidance). The tax appears in the protocol with
behavior: inclusive, and the consumer-facing total does not change.
Discovery¶
{
"capabilities": {
"br.dev.bcp.shopping.tax": [
{
"version": "2026-07-28",
"extends": ["br.dev.bcp.shopping.checkout", "br.dev.bcp.shopping.order"],
"spec": "https://bcp.dev.br/draft/specification/tax",
"schema": "https://bcp.dev.br/draft/schemas/shopping/tax.json"
}
]
}
}
The capability does not declare profile config: calculation runs behind the
seller's Federal Revenue calculator port (internal seller configuration), and
the tax regime, where relevant, is a seller identity attribute (AgentFacts),
not a tax setting.
Schema Composition¶
checkout.line_items[]andorder.line_items[]are extended withncmandtaxes[](composed overtypes/line_item.jsonandtypes/order_line_item.json).- Aggregated tax totals use the
taxcategory the core already supports intotals— the extension does not create a new slot, it makes populating it mandatory (normative rule 1). - Schema:
schemas/shopping/tax.json.
Fields¶
tax_detail entries in line_item.taxes[]¶
| Field | Type | Required | Description |
|---|---|---|---|
type |
open string | Yes | icms, icms_st, difal, fcp, iss, ipi, pis, cofins (current system); ibs, cbs, is (Reform). |
rate |
number | No | Applied rate as a decimal fraction, such as 0.009 for 0.9%. Indicative only: amount is authoritative. |
base |
integer | No | Calculation base in minor currency units. May be lower than the item price (tax charged on top within a tax-inclusive price). |
amount |
integer | Yes | Tax amount in minor currency units. |
authority |
open string | No | federal, state, municipal. IBS is represented as two entries (state + municipal). |
behavior |
open string | Yes | inclusive (already contained in the price) or exclusive (charged on top). |
New line item fields¶
| Field | Type | Required | Description |
|---|---|---|---|
ncm |
string (8 digits) | No | Mercosur fiscal classification; input to calculation, the Selective Tax, and the NF-e. |
taxes |
array of tax_detail |
No | One entry per tax incidence. |
Normative Rules¶
- With this capability active, every checkout and order response MUST
include at least one
tax-category entry intotalswith the approximate total tax amount (Law 12,741/2012). This rule is normative at the specification level — it is not expressed as a JSON Schema constraint, to keep code generation clean. Since the schema does not reach it, it is enforced by the binding (bcp_infra/tax/bcp_mapping.py), which fails fast if the quote carries tax but the total lacks thetaxline. - Prices shown to consumers MUST already include taxes (Consumer Defense Code art. 31).
- Clients MUST tolerate unknown tax
typevalues (Reform transition). - Clients MUST NOT add entries with
behavior: inclusiveto the subtotal (they are already in the price), nor recomputeamountfrombase × rate(the rate is indicative; rounding is the seller's).
Example¶
Checkout response excerpt:
{
"line_items": [
{
"id": "li_1",
"item": { "id": "prod_1", "title": "Ceramic vase", "price": 10000 },
"quantity": 1,
"ncm": "69120000",
"taxes": [
{ "type": "cbs", "rate": 0.009, "base": 10000, "amount": 90, "authority": "federal", "behavior": "inclusive" },
{ "type": "ibs", "rate": 0.001, "base": 10000, "amount": 10, "authority": "state", "behavior": "inclusive" }
],
"totals": [{ "type": "subtotal", "amount": 10000 }]
}
],
"totals": [
{ "type": "subtotal", "amount": 10000 },
{ "type": "tax", "display_text": "Taxes (Law 12,741/2012)", "amount": 100 },
{ "type": "total", "amount": 10000 }
]
}