Mesrai
Back to blog
// essayTechnical Deep Dive

Tax Inclusive vs Tax Exclusive: Mesrai's Single Source-of-Truth Rule

Real PR catch: ambiguous price field — listing inclusive, checkout exclusive. basePricePaise + applyTax convention.

Mesrai TeamAugust 24, 20268 min read

A product schema with a single `price` field. Listing page renders it as tax-inclusive; checkout treats it as tax-exclusive. Totals differ by the tax amount. Mesrai flagged the missing convention.

The vulnerable diff

tsmodels/product.ts
// models/product.ts
interface Product {
  id: string;
  name: string;
  price: number;  // BUG: tax inclusive? tax exclusive? Code disagrees.
}

// listing/Card.tsx — treats price as inclusive
return <span>{product.price}</span>;

// checkout/total.ts — treats price as exclusive, adds 18% GST
const total = product.price * 1.18;

What is wrong

Tax convention is a business / regulatory choice, but the code has to encode it consistently. Storing 'price' without specifying inclusive vs exclusive leaves the interpretation to whichever code path is reading. Over time different paths inevitably disagree. The fix is to pick one convention (typically: store exclusive of all taxes; compute tax + total at display per region), pin it in the field name (`basePricePaise`), and run the convention through types.

The attack

Symptom:

textexploit
Product page: ₹1180  (treats price as inclusive — what user sees)
Cart line:     ₹1180  (same)
Checkout total: ₹1392.40 (treats price as exclusive, adds 18% GST)

User: "why did my cart go up at checkout?"
Support: 'system bug, will be fixed soon'.

Sometimes shows up as 'price changes between cart and checkout' bug reports.

Mesrai's review comment

textreview-comment.txt
mesraipilot · Bot · reviewed 1 min ago

[mesrai] [code-review] [Logic] [Tax-Convention] [medium]

`price` field has no tax convention. Code paths disagree. Pin to
one explicit convention.

Recommended shape: store exclusive, compute inclusive per region:

  interface Product {
    id: string;
    name: string;
    basePricePaise: number;   // tax-exclusive, integer paise
  }

  interface TaxedPrice {
    basePricePaise: number;
    taxPaise: number;
    totalPaise: number;
    taxBp: number;
  }

  function applyTax(p: Product, region: TaxRegion): TaxedPrice {
    const taxBp = region.gstBp;  // 1800 for IGST 18%
    const taxPaise = Math.round((p.basePricePaise * taxBp) / 10_000);
    return {
      basePricePaise: p.basePricePaise,
      taxPaise,
      totalPaise: p.basePricePaise + taxPaise,
      taxBp,
    };
  }

Rendering, cart, checkout all consume `applyTax(p, region)` — single
source of truth. Cluster 8 post 71 (integer paise) and post 78
(unit naming) compose here.

The fix

tsmodels/product.ts + pricing/apply-tax.ts (fixed)
// models/product.ts — fixed
export interface Product {
  id: string;
  name: string;
  basePricePaise: number;   // exclusive, integer paise (named explicitly)
}

// pricing/apply-tax.ts
export interface TaxedPrice {
  basePricePaise: number;
  taxPaise: number;
  totalPaise: number;
  taxBp: number;
}

export function applyTax(p: Product, region: { gstBp: number }): TaxedPrice {
  const taxPaise = Math.round((p.basePricePaise * region.gstBp) / 10_000);
  return {
    basePricePaise: p.basePricePaise,
    taxPaise,
    totalPaise: p.basePricePaise + taxPaise,
    taxBp: region.gstBp,
  };
}

Field renamed to `basePricePaise` — convention encoded in name. Single `applyTax` function used everywhere — convention enforced at one boundary.

Why human review missed it

Tax-convention bugs are subtle because the field looks correct in isolation. The bug is at the integration between code paths. Mesrai catches `price` / `amount` fields without a convention suffix or type brand and recommends the renaming.

Related rules + further reading

Mesrai rule pack: logic/tax-convention-explicit — flags currency fields without inclusive/exclusive convention.

Indian GST Act: invoice fields must specify taxable value, tax, total.

Common in cross-border e-commerce.

Takeaway

Pick one tax convention. Encode in field name. Single derive function. Mesrai catches every ambiguous price field.

// try

See it on your next PR.

Free for individuals. Install in two minutes. Mesrai reviews every commit.