# Order edit: coupon and shipping are recalculated (app handoff, 2026-10-24)

For: the mobile (Flutter) developer. Server change is **already live on the API**; this file is what the app
should change so it shows the same thing as the website.

Full contract of the edit screen: `docs/api/combined-orders-mobile.md` §2.3 and §4.3 – §4.5. This file only
describes what changed.

---

## 1. What changed on the server

When the customer edits a seller order (quantity, variant, remove a product), the server now recalculates
**both** the checkout's coupon and the checkout's shipping, on the preview and on the save:

| | Before (bug) | Now |
|---|---|---|
| **Percentage coupon** | kept its old amount (10% of 1,000,000 stayed 100,000 after the basket went down to 700,000) | follows the new basket: 10% of 700,000 = **70,000** |
| **Fixed-amount coupon** | stayed, but the checkout total was saved **without** it | stays, and is taken off the checkout total |
| **Coupon minimum** | an edit below the minimum was accepted and silently kept the discount | refused with `code: "coupon_dropped"` (this code was always documented, it now really happens) |
| **Shipping, basket goes down** below the free-shipping limit | shipping added | shipping added (unchanged) |
| **Shipping, basket goes up** past the free-shipping limit | shipping stayed (customer kept paying it) | shipping **taken off** (becomes 0) |

What did **not** change:

- The endpoints, the request bodies, the error codes and every existing response key.
- The coupon still belongs to the whole checkout (all seller orders together), never to one seller order.
- The other seller orders of the checkout never change because of an edit.
- A shipping cost staff set by hand, or a new tariff since the order was placed, is never applied by an edit:
  only what the edit itself causes.

**The app must not calculate any of this itself.** Show what the server returns.

---

## 2. Endpoints (unchanged)

| Call | Purpose |
|---|---|
| `GET /api/v2/order-edit/{order_id}` | open the edit screen (lines, variants, limits, `basket`) |
| `POST /api/v2/order-edit/{order_id}/preview` | what the change gives, nothing saved |
| `POST /api/v2/order-edit/{order_id}` | save (same `changes` + `basket`) |

Body of preview and save, as before:

```json
{"changes": [{"id": 7781, "quantity": 2}], "basket": "3f1c0a9e2b7d4c5a6e8f"}
```

(`basket` only on save.)

---

## 3. Response: one new key, two keys with a clearer meaning

Preview (and save, which adds `"saved": true`):

```json
{
  "result": true,
  "message": null,
  "data": {
    "ok": true,
    "code": "ok",
    "lines": [ … unchanged … ],
    "removed": [],
    "order_total": {"before": {"amount": 500000, "formatted": "500,000 ل.س"}, "after": {"amount": 200000, "formatted": "200,000 ل.س"}},
    "coupon": {
      "had_coupon": true,
      "code": "SAVE10",
      "share_before": {"amount": 100000, "formatted": "100,000 ل.س"},
      "share_after":  {"amount": 70000,  "formatted": "70,000 ل.س"}
    },
    "shipping_added": {"amount": 0, "formatted": "0 ل.س"},
    "shipping": {
      "before": {"amount": 0, "formatted": "0 ل.س"},
      "after":  {"amount": 0, "formatted": "0 ل.س"}
    },
    "total": {"before": {"amount": 900000, "formatted": "900,000 ل.س"}, "after": {"amount": 630000, "formatted": "630,000 ل.س"}}
  }
}
```

| Key | Meaning |
|---|---|
| `coupon.had_coupon` | the checkout has a coupon |
| `coupon.share_before` / `coupon.share_after` | **the checkout's coupon** before / after the edit (the names are old; there is one coupon per checkout, not a share per seller order) |
| `shipping` **(new)** | the checkout's shipping before / after the edit. Shows shipping going up **and** down |
| `shipping_added` | kept for old app versions: the shipping added by the edit, **never negative** (0 when shipping went down or did not move). New code should use `shipping` instead |
| `order_total` | this seller order's products after the edit (before coupon and shipping) |
| `total` | **what the customer pays for the whole checkout**: products of every seller order + shipping − coupon. This is the main number to show |

Amounts are in SYP (`amount` is a number, `formatted` is ready to display).

---

## 4. What to change in the app

### 4.1 Edit screen summary (under the lines, refreshed after every preview)

Show these rows. The website shows the same rows:

1. **New total**: `total.after.formatted`, and the old one: `total.before.formatted`.
2. **Coupon**, only when `coupon.had_coupon == true` **and** `share_before.amount != share_after.amount`:
   `Coupon: share_before.formatted → share_after.formatted`
   (Arabic, RTL: `الكوبون: 100,000 ل.س ← 70,000 ل.س`).
3. **Shipping**, only when `shipping.before.amount != shipping.after.amount`:
   `Shipping cost: shipping.before.formatted → shipping.after.formatted`
   (Arabic: `تكلفة الشحن: 10,000 ل.س ← 0 ل.س`).
   Highlight it (e.g. orange) when it went **up** (`after > before`); show it normally when it went down.

Replace the old "Extra shipping" row (which read `shipping_added`) with row 3. If `shipping` is missing (an old
server), fall back to the old row: show "Extra shipping +`shipping_added.formatted`" when `shipping_added.amount > 0`.

### 4.2 Error `coupon_dropped`

It can now really happen (a customer lowers the quantity below the coupon's minimum). The answer is
`result: false`, `data.code: "coupon_dropped"`, with the text in `message`. Show `message` (it explains that the
coupon would be lost and suggests keeping more products or cancelling), keep **Save** disabled, and let the
customer change the quantity again. Nothing was saved.

### 4.3 After a successful save

As before: show `message` and **reload the order page**
(`GET /api/v2/purchase-history/combined/{order_id}`). Its coupon, shipping and total lines already come from
the server and will show the new values. Do not patch the old numbers locally.

### 4.4 Never compute in the app

Don't recalculate percentages, free-shipping limits or totals in the app, and don't cache the totals from the
edit screen. The server decides everything (rounding, minimums, the free-shipping limit of the address's city).

---

## 5. Examples to test with

All amounts in SYP. "Free shipping from 600,000" and "shipping 10,000" are the test city's settings; use any
city/coupon on the test server with similar values.

**A. Percentage coupon goes down with the basket.**
Checkout: seller A 500,000 + seller B 500,000, coupon 10% (min 300,000), shipping free.
Edit B down to 200,000 →
`coupon` 100,000 → **70,000**, `shipping` 0 → 0, `total` 900,000 → **630,000**. Seller A is unchanged.

**B. Percentage coupon goes up with the basket.**
Same checkout after A. Raise B from 200,000 to 400,000 →
`coupon` 70,000 → **90,000**, `total` 630,000 → **810,000**.

**C. Fixed coupon stays.**
Checkout 1,000,000, coupon 20,000 fixed (min 300,000). Edit down to 700,000 →
`coupon` 20,000 → 20,000 (no coupon row shown), `total` 980,000 → **680,000**.

**D. Below the coupon minimum → refused.**
Checkout 1,000,000, coupon min 900,000. Edit down to 800,000 →
`result: false`, `code: "coupon_dropped"`, nothing saved.

**E. Shipping taken off.**
One seller order 400,000 + shipping 10,000 (free from 600,000). Raise to 700,000 →
`shipping` 10,000 → **0**, `shipping_added` 0, `total` 410,000 → **700,000**.

**F. Shipping added.**
One seller order 700,000, shipping 0 (free from 600,000). Lower to 400,000 →
`shipping` 0 → **10,000**, `shipping_added` 10,000, `total` 700,000 → **410,000**.

**G. Coupon and shipping together.**
One seller order 800,000, coupon 10% = 80,000, shipping 0 (free from 600,000). Lower to 400,000 →
`coupon` 80,000 → **40,000**, `shipping` 0 → **10,000**, `total` 720,000 → **370,000**.

For each one: check the preview summary, save, then check the order page shows the same coupon, shipping and
total.

---

## 6. Checklist

- [ ] Edit summary shows the coupon row `before → after` when the coupon changes.
- [ ] Edit summary shows the shipping row `before → after` from the new `shipping` key, up and down.
- [ ] Fallback to `shipping_added` when `shipping` is absent.
- [ ] `coupon_dropped` shows `message` and keeps Save disabled.
- [ ] Order page reloaded after save; no local recalculation anywhere.
- [ ] Arrow direction follows the language (RTL Arabic: `←`, LTR English: `→`).
- [ ] Examples A–G give the numbers above on the test server.
