# Business rules — pmf-frontend-ah (PayMyFines)

Evidence date: 2026-10-06. Source: `master` at `3e8d2a5`.

Rules are labelled **Documented** (copy or README), **Implemented** (enforced by this UI), or **Inferred**. External systems may add rules this repository cannot see. Those are marked **Unknown / Requires Confirmation**.

## Operator and offer

1. **Documented.** The terms say the site is provided by TMT Services & Supplies (Pty) Ltd, registration number `2000/022850/07`, referred to as PayMyFines, and that the website is `https://all.paymyfines.co.za`.
2. **Documented.** The service is to check outstanding traffic fines and to pay them on the website, except where a warrant of arrest has been issued for failure to pay (`TermsAndConditions.js` clause 5.1.3).
3. **Documented.** Payment is online by credit card, through a system the terms describe as using 128-bit encryption. The payer can review, correct, and withdraw before the final order (clause 5.1.4).
4. **Documented.** PayMyFines reserves the right to limit how many transactions one credit card may make on the site per day (clause 5.1.4). No such limit is implemented in this repository.
5. **Documented.** The footer describes PayMyFines as a platform for viewing and paying traffic fines, and gives `payments@paymyfines.co.za`, `help@paymyfines.co.za`, and PO Box 234, Century City, Cape Town, 7446.
6. **Inferred.** eNatis (AARTO) and iForce are the two fine sources. The UI never uses the words as a contract; it branches on payload shape and sets `api` to `eNatis` or `iForce`. Affiliation images for eNatis, AARTO, and RTIA are in the footer.

## Who may do what

7. **Implemented.** With no `localStorage.roles` entry the user is a guest. Guests may open home, about, login, register, forgot password, quick search, both fine-detail routes, payment notification, terms, and privacy.
8. **Implemented.** Outstanding fines, profile, transactions, and cart require a role in `SUPER_ADMIN`, `ADMIN`, `MANAGER`, or `CUSTOMER`.
9. **Implemented.** Admin dashboard and settings require `SUPER_ADMIN` or `ADMIN`.
10. **Implemented.** Add to Cart is not rendered unless `isLoggedIn()` is true (a `roles` entry exists).
11. **Implemented.** `isLoggedIn` does not check that the Cognito session is still valid.
12. **Inferred.** `MANAGER` is accepted wherever `CUSTOMER` is accepted. No screen creates that role. **Unknown** who receives it.
13. **Inferred.** `SUPER_ADMIN` and `ADMIN` see the same routes. **Unknown** whether the API treats them differently.
14. **Requires confirmation.** Route checks `JSON.parse` the stored role. Login stores `strRole` as returned. The rule "one user has one role name" versus "the API returns a JSON array of roles" cannot be settled from this repo.
15. **Implemented.** These screens do not re-check role on the server. Hiding a route is not an API rule.

## Identity

16. **Implemented.** ID type `02` is a South African ID number: pattern `YYMMDD` plus sequence digits, and the last digit must match `mod10CheckDigit` of the preceding digits.
17. **Implemented.** ID type `03` (foreign) and `04` (business registration) pass validation whenever the field is non-blank. No checksum.
18. **Implemented.** Registration defaults the ID type to `02`. The business-account checkbox switches it to `04` and back to `02`.
19. **Implemented.** Registration requires the ID confirmation field to equal the ID number. Login's first-time details modal does not ask for a confirmation copy.
20. **Implemented.** Name and surname must match the name pattern (letters and a small set of separators). Phone must be an optional `+` and 10 to 13 digits. Email must match the email pattern.
21. **Implemented.** An additional ID is rejected locally if it equals the primary ID or another additional ID. The user must click Update before `POST /update`.
22. **Implemented.** The profile response field `maxNumberIds` is coerced to a boolean and then ignored. This UI does not enforce a maximum number of IDs.
23. **Implemented.** On profile save the UI sets `txtBillAddress` to `Home`.
24. **Inferred.** Fines and transactions are filtered by the ID list stored at login and rewritten on profile update (`id:type`). The outstanding-fines "ALL" option sends `userId` instead of an ID.

## Search and payability

25. **Implemented.** A notice search removes `-` characters before the API call.
26. **Implemented.** A guest search sends `Id=GUEST`. A signed-in search sends `UserID`.
27. **Implemented.** Home search treats payload text containing `is incorrect` as an invalid fine number, and treats both sources returning a "No fines found" string as "Fine not found".
28. **Implemented.** If the payload has an eNatis array and an iForce fines array, the object used for navigation is the eNatis row, because it is assigned second.
29. **Implemented.** A row with `infringementNoticeNumber` is opened as AARTO. A row with `noticeNumber` is opened as CPA.
30. **Implemented.** An eNatis fine can be added to the cart only when `paymentNotAllowedCode !== 3` and `feesList.debt.length > 0`. Otherwise the UI sets `customerPaymentAllowed` false, shows status and amount `N/A`, and disables add-to-cart.
31. **Implemented.** On the CPA detail screen, `paymentNotAllowedCode === 3` replaces the amount with the text `NAG`.
32. **Requires confirmation.** The terms' warrant-of-arrest exclusion is not named in code. Whether code `3` or `NAG` is that exclusion is not stated.
33. **Implemented.** Cart add is idempotent per infringement number. A second add does not create a second line.
34. **Implemented.** Bulk add on the outstanding list skips rows whose `customerPaymentAllowed` is not true.
35. **Inferred.** iForce rows on the outstanding list are not passed through the `paymentNotAllowedCode !== 3` branch. That branch is in the eNatis `else`. An iForce row is payable unless `customerPaymentAllowed` is already false.

## Money

36. **Implemented.** Currency sent to iVeri is `ZAR`.
37. **Implemented.** The convenience fee is the settings value whose key trims to `convenience`. It is multiplied by the number of cart keys, once per line, including a line the user cannot pay if it is already in the cart. The fee is not a percentage.
38. **Implemented.** Displayed cart fine total: sum of `feesList.debt[].debtAmt` for an AARTO-shaped object, otherwise `totalAmountDue`. Cart total = that sum + convenience.
39. **Implemented.** The iVeri product lines use those same display amounts, in cents (`amount * 100`). A further line named `Convenience Fee` carries the convenience total in cents. `Lite_Order_Amount` is the cart total in cents.
40. **Implemented.** The Unity transaction stores `strAmount` as the cart total (rands, convenience included), `intPaid` `NO`, `strStatus` `Sent to iVeri`. Each infringement stores `strConvenienceAmount` as the single-line fee.
41. **Discrepancy, implemented.** For an AARTO infringement record, `strAmount` is `fine.payment.amount`, not the summed debt used on the iVeri line. **Requires confirmation** which figure is the amount the motorist owes.
42. **Implemented.** Checkout builds the iVeri form even when `/postiveritransaction` does not return `result === true`.
43. **Implemented.** Bill-to last name is set from `strName`, the same field as first name. `strSurname` is not copied into `Ecom_BillTo_Postal_Name_Last`.
44. **Implemented.** Success return deletes the local cart and, after 9 seconds, opens home. Fail, error, try-later, and any other status keep the cart and, after 9 seconds, open `/Cart`.
45. **Implemented.** Logout deletes the cart.
46. **Unknown / Requires Confirmation.** When Unity marks `intPaid` paid. This screen does not send a second payment update.

## Registration, passwords, and templates

47. **Implemented.** Register does not call Cognito until `/recaptcha` returns `result === true` and `payload === 'true'`.
48. **Implemented.** Passwords in the register and forgot-password forms must be non-blank and match. No length or character-class rule is applied in this UI. Cognito may still reject the password. That policy is **Unknown**.
49. **Implemented.** Clicking Submit on register is the acceptance of the terms and privacy policy. There is no unticked checkbox to block submit.
50. **Implemented.** An unverified Cognito email is sent to `/register?verify={email}` rather than into the app.
51. **Implemented.** If Unity says the email already exists, the client calls `/updateregistration` to attach the Cognito uuid.
52. **Implemented.** Forgot-password writes the new password into `localStorage` under the key `asdf` before `Auth.signUp`, and register deletes that key after reading it for `Auth.signIn`.
53. **Implemented.** The forgot-password probe uses confirmation code `000000`. `UserNotFoundException` takes the `Auth.forgotPassword` path. The other classified errors take the sign-up path. **Requires confirmation** that this split is intentional. See the PRD workflow W5.
54. **Implemented.** An admin cannot save a settings template that does not contain the substring `{code}`.
55. **Implemented.** The convenience-fee editor is shown only for the settings key `convenience ` with a trailing space. The cart looks up the trimmed key `convenience`. **Requires confirmation** of the key stored by the API.
56. **Implemented.** Convenience save sends `parseInt` of the field, so a non-integer is truncated. An empty field becomes `NaN`. No client range check (negative, zero) was found.

## Content and conduct

57. **Documented.** Terms clause 1 limits use to lawful purposes and lists prohibited content (unlawful, harmful, infringing, spam, and similar). This SPA has no public posting form, so those clauses are not enforced in the UI.
58. **Documented.** Terms clause 5.1.5 says complaints go through a "Contact Us" service or contact details in clause 9. This tree's contact path is the footer mailto links. Clauses 9 and 10 are not in the component.
59. **Documented.** Privacy copy says card numbers are used only to process payment, and that calls to the customer number may be monitored. The customer number itself is not in the component. Card numbers are typed on the iVeri page, not into a React field in this repo.

## Analytics and consent

60. **Implemented.** Matomo (site id 2) and Google Analytics `G-6M1WRZYG8D` run from `index.html` without a consent control in this UI.
61. **Documented.** The privacy page says use of the services is consent to the policy, and it describes cookies. No cookie banner is implemented.

## Rules not to invent

- Fee percentages, discounts, and refunds: not described in code. Refund behaviour is **Unknown**.
- How long a "Sent to iVeri" transaction may stay unpaid: **Unknown**.
- Office hours, SLA, or who answers `help@paymyfines.co.za`: **Unknown**.
- Whether guests may legally see another person's fine by notice number: the UI allows the search. A product rule forbidding it was not found.
