# Delivery Charge & GST

How an order's money is split, and where each part goes.

## Setup

```bash
node src/scripts/create-order-charges.js
```

Defaults seeded into `platform_settings` (all admin-editable, no redeploy):

| setting | default |
|---|---|
| `gst_percent` | 12 |
| `delivery_charge` | 50 |
| `free_delivery_above` | 999 |

---

## The split

```
subtotal        = Σ unit_price × qty       goods only
delivery_charge = per vendor shipment      added on top
total_amount    = subtotal + delivery      ← what the buyer pays (Razorpay amount)

taxable_value   = subtotal ÷ (1 + gst%)    ← breakup only
gst_amount      = subtotal − taxable_value ← already inside the price

commission      = subtotal × fee%          ← goods only
vendor_earning  = subtotal − commission    ← delivery is not the vendor's
```

A ₹500 order at 12% GST, 5% commission, ₹50 delivery:

| | |
|---|---|
| buyer pays | **₹550** |
| of which delivery | ₹50 → platform (it pays the courier) |
| goods | ₹500 (₹446.43 + ₹53.57 GST) |
| platform commission | ₹25 |
| vendor's wallet | **₹475** |

### Two rules that matter

**GST is inclusive.** The listing price already contains it, so breaking it out
is presentation — the buyer pays the same either way. Changing the GST rate
re-slices the same price for the invoice; it never changes what anyone pays, and
never changes the meaning of an existing price.

**Commission is charged on the subtotal, never the total.** Charging it on the
total would mean the platform taking a cut of the courier fee it pays itself,
and a cut of the government's tax.

---

## Delivery is per shipment, not per item

A cart of three things from one shop pays delivery **once**. The charge is
worked out per `(checkout_id, vendor_id)` and then shared across that vendor's
order rows by line value, with the last row absorbing the rounding.

That share-out keeps one invariant true, which the payment and refund paths both
depend on:

```
Σ order.total_amount  ===  the amount Razorpay was asked for
```

Free over the threshold, flat below it:

```
delivery = subtotal >= free_delivery_above ? 0 : delivery_charge
```

---

## Quote before paying

The buyer has to see the final figure before Razorpay opens. Both quote
endpoints price with the **same code** checkout uses, so what is shown is what
is charged.

`GET /api/vendors/cart/quote/:user_id`

```json
{
  "subtotal": 600.00,
  "delivery_charge": 100.00,
  "gst_amount": 64.29,
  "taxable_value": 535.71,
  "total_amount": 700.00,
  "shipment_count": 2,
  "shipments": [
    { "vendor_id": 3, "shop_name": "Cric Store", "subtotal": 500.00,
      "delivery_charge": 50.00, "amount_for_free_delivery": 499.00,
      "total_amount": 550.00, "items": [ … ] }
  ],
  "unavailable_items": []
}
```

`GET /api/vendors/store/quote?product_id=&variant_id=&quantity=` — the Buy Now
equivalent, same shape for one item.

`amount_for_free_delivery` is there so the app can nudge: *"add ₹499 more for
free delivery"*.

Unavailable cart items are excluded from the pricing (checkout would refuse them
anyway) and listed separately so they can be flagged.

---

## Rates

GST resolves per category, then the platform default — the same shape as
`commission_percent`:

| level | where |
|---|---|
| category | `vendor_categories.gst_percent` |
| default | `platform_settings['gst_percent']` |

`NULL` falls through; a stored **0 is a real rate** (zero-rated goods).

Both the rate and the amounts are **snapshotted onto the order** when it is
placed, so changing a rate later never rewrites an existing order.

### Admin

| method | path | |
|---|---|---|
| `GET` | `/api/vendors/admin/charges` | current settings + category GST overrides + a worked example |
| `PUT` | `/api/vendors/admin/charges` | `{ user_id, gst_percent?, delivery_charge?, free_delivery_above? }` |
| `PUT` | `/api/vendors/admin/charges/category/:category_id` | `{ user_id, gst_percent }`, `null` clears it |

In the panel: **`/admin/commission` → Delivery & GST**, and a GST column beside
the fee column in the categories table.

---

## Refunds

Refunds come out of the **goods first, delivery last**. That matches who held
the money — the vendor only ever earned from the goods, so a refunded delivery
charge comes entirely from the platform, which is the one that charged it.

| refund on a ₹550 order (₹500 goods, ₹50 delivery, 5% fee) | vendor | fee back | delivery back |
|---|---|---|---|
| ₹200 (part of the goods) | −₹190 | −₹10 | — |
| ₹550 (everything) | −₹475 | −₹25 | −₹50 |
| ₹50 after the goods were already refunded | — | — | −₹50 |

`refundSplit()` returns those three parts separately, and only
`commission_share` is written to `platform_earnings`. A refunded delivery charge
is the platform returning money it collected for a courier — a real cost, but
not commission, and booking it as one would overstate "returned on refunds" in
the revenue report.

A **return** refunds `unit_price × quantity` — goods only, no delivery, since
the courier has already been paid. A **cancellation** refunds the full
`total_amount`, delivery included.

---

## Existing orders

Backfilled with `subtotal = total_amount` and `delivery_charge = 0` — they were
sold with no delivery charge and no tax breakup, so nothing about them changes.
`refundSplit` falls back to `total_amount` when an order has no `subtotal`, so
old refunds behave exactly as they did.

## Not included

- **GST on the platform's commission.** A marketplace charges the vendor 18% GST
  on the commission itself, which would reduce `vendor_earning` further. Needed
  for compliance, not implemented — say the word.
- **GST on the delivery charge.** The breakup covers goods only.
