# PHASE 09: COD RULES, VERIFICATION, FULFILMENT, COURIER AND SETTLEMENT

The heaviest phase. Read `docs/01-BUSINESS-REQUIREMENTS.md` sections 4.2 to 4.8, `docs/06-DATA-MODEL.md` in full, and decisions D-04, D-05, D-07, D-08, D-09, D-20.

Consider splitting this across two sessions: 9a is the COD and verification logic, 9b is fulfilment, courier and settlement.

## 9a. COD and verification

1. `BC_COD_Gateway extends WC_Payment_Gateway`, id `bc_cod`. Our own gateway, not core `cod`, so settings, availability and icon are ours. `is_available()` delegates to `BC_COD_Rules`.
2. `BC_COD_Rules`: pure logic, no hooks, fully unit-testable. `is_allowed_for_cart()`, `requires_verification( $total, $city_recognised, $max_qty, $phone, $address )`, `max_cod_value()`. Above `cod_max_value`, COD is simply unavailable at checkout and the payment card explains that orders over that amount need advance payment. Add that string to the microcopy set. All thresholds read from `bc_settings`, **never hard-coded**.
   Verification triggers: total in the mid band, total above the high band, any line quantity above the configured trigger, phone with a prior RTO or cancellation, `_bc_city_recognised` false, address shorter than the configured minimum. Store which trigger fired in `_bc_verification_reason`.
3. `BC_Blocklist`: per-phone RTO counter read from `bc_order_stats`. At the configured count, COD is unavailable at checkout for that number and the payment card explains why. This is decision D-08 and it is the highest-return feature in the build.
4. `BC_Verification`: the state machine. `mark_verified()`, `mark_failed()`, `record_attempt()`, `cancel_with_reason()`. Each takes a channel (`whatsapp`, `call`, `sms`) and writes `_bc_verification_channel`, so the owner can later see which channel actually works. Each writes meta, fires `bc_verification_changed`, and lets `BC_Order_Timeline` write the note. Hooks `woocommerce_checkout_order_created` to set the initial state.
5. **The verification admin experience.** This is where the owner spends their day, and it has to be a thirty-second job per order. In the HPOS order list, when filtered to Verification Required, each row shows without opening the order: order ID and age in hours, name, **phone as a `tel:` link**, city, total, a short trigger badge (High Value, Prior RTO, Unknown City, Large Quantity, Short Address), attempt count, prior order and prior RTO count for that phone from `bc_order_stats`, and the items in one line. That is ten pieces of information and every one of them is there so the owner does not have to open the order.
   Three inline AJAX buttons on the row: Confirm, Log Attempt, Cancel with reason. **No page reload between orders.** Nonce plus `bc_verify_orders` capability on every call.
6. `BC_WhatsApp_Link`: builds `https://wa.me/92XXXXXXXXXX?text=` with the message pre-filled with order number, items, total and city. One click, no retyping. This is what turns a five-minute job into a thirty-second one.

## 9b. Fulfilment, courier, settlement

7. `BC_Fulfilment`: owns `_bc_fulfilment_status` through its five values. Auto-advances when the order status moves. Fires `bc_fulfilment_changed`.
8. `BC_Courier_Registry` and `BC_Courier_Manual implements BC_Courier_Interface`. Couriers are configured in settings with name, tracking URL template, COD charge and RTO charge. `get_tracking_url()` builds from the template. The registry exposes a `bc_couriers` filter so a future API courier registers itself without touching this code.
9. `BC_Order_Metabox`: one panel on the order edit screen, registered for both the HPOS screen id and the legacy one, with sections for Verification, Fulfilment, Courier, COD and Settlement. Saves on `woocommerce_process_shop_order_meta` with a nonce and a capability check.
10. Order list columns and filters for fulfilment status, courier and COD status, wired through `woocommerce_order_list_table_prepare_items_query_args`.
11. RTO handling: moving an order to RTO restores stock if tracked, records `_bc_rto_charge` from the courier config, never marks COD collected, increments the phone's RTO count, and requires a reason.
12. `BC_Settlements`: a WooCommerce submenu page. Create a settlement (courier, date, reference, gross, charges, net, notes), then select delivered orders with COD collected and attach them. Attaching writes `_bc_settlement_id` on each and sets `_bc_cod_status` to settled. **Marking COD collected and attaching a settlement must both fire `bc_cod_changed` and `bc_settlement_changed`**, which `BC_Order_Timeline` already listens for from phase 1. Money movement without an audit note is the one gap that matters most here.
13. Ageing report: **every order delivered more than 21 days ago that is not attached to a settlement**, split into two groups: COD collected but unsettled (the courier owes money), and COD never marked collected (nobody knows what happened to the cash). The second group is the one that catches a real shortfall, so do not filter it out.
14. `BC_Abstract_PK_Gateway`, `BC_Payment_Result` and `BC_Gateway_Registry` exactly as specified in technical architecture section 4, plus `README-adding-a-gateway.md`. `BC_Payment_Result` is the value object `handle_callback()` returns: success flag, transaction ID, status, amount, raw payload, error message. Without it the abstract method's return type has no class and any subclass fatals. Filenames follow the autoloader convention, `class-pk-gateway.php` and `class-payment-result.php`, not `abstract-class-*.php`. **Implement no provider.**

## Acceptance criteria

- [ ] Every QA test in section E, plus D10 and D16
- [ ] An order under the low threshold auto-confirms, one in the mid band lands in Verification Required with the correct reason
- [ ] Each of the six verification triggers fires independently and is recorded
- [ ] The verification list row shows all nine pieces of information without opening the order
- [ ] Confirm, Log Attempt and Cancel all work inline with no page reload, and each writes an order note
- [ ] The WhatsApp link opens with the message fully pre-filled
- [ ] A phone with two RTOs cannot select COD at checkout, and the card explains why
- [ ] Moving to RTO restores stock, records the charge, leaves COD uncollected and increments the counter
- [ ] A settlement covering five orders links all five and removes them from the ageing report
- [ ] An order delivered 22 days ago with no settlement appears on the ageing report
- [ ] `BC_Payment_Result` exists and every interface and abstract class filename matches the autoloader convention
- [ ] Marking COD collected and attaching a settlement both write order notes
- [ ] An order delivered 25 days ago with COD never marked collected appears on the ageing report, in the second group
- [ ] The gateway abstraction exists, is documented, and no provider code ships
- [ ] Every AJAX endpoint verifies a nonce and a capability
- [ ] The order list with 500 seeded orders loads in under 3 seconds

## Stop

No dashboard, no emails, no analytics. Next phases.
