# Technical documentation — paymyfines-frontend

Evidence date: 2026-10-07.

Source: `git@bitbucket.org:overdrive/paymyfines-frontend.git`, `master` at `8279c82` (`8279c82cf55d1fe86d34ed3538a802506e11d8d2`).

## Stack

| Item | Evidence |
|---|---|
| UI | React `17.0.2`, React DOM `17.0.2`, Create React App `react-scripts` `4.0.3` |
| Routing | `react-router-dom` `5.2.0`, `history` via `src/utils/history.js` |
| Components | `react-bootstrap` `1.6.1`, Bootstrap `5.0.1` in dependencies. `public/index.html` also loads Bootstrap `4.3.1` from a CDN, plus W3.CSS |
| Auth and API client | `aws-amplify` `3.3.21` |
| Charts | `recharts` `2.1.0`, `chartist` / `react-chartist`, `plotly.js` / `react-plotly.js` (plotly is used by the pivot renderers, which no route imports) |
| Tables | `react-data-table-component`, `react-table` `7` |
| Editor | TinyMCE `5.8.2` and `@tinymce/tinymce-react`, used by `src/components/common/WYSIWYG.js` from admin settings |
| Other libraries in `package.json` | FullCalendar 5, `react-trello`, `react-pivottable`, `react-google-recaptcha`, `moment`, `lodash`, `styled-components`, `react-datepicker` |
| Package name | `role-based-access-control` version `0.1.0`, `"private": true` |
| Language | JavaScript. `jsconfig.json` sets `baseUrl` to `./src` so imports such as `config` and `components` resolve there |
| Formatting | `npm run pretty` runs Prettier 1 with tabs, single quotes, and always arrow parens. `.prettierrc.json` is present |
| Lockfile | `yarn.lock` is committed. `README.md` says `yarn install` then `yarn start`. No `package-lock.json` was in the tree |

`npm start` is `react-scripts --max-http-header-size=77777 start`.

## Application structure

```text
public/                 index.html, iveri.html, manifest, favicon
documentation/iveri.php redirect script
src/index.js            Amplify.configure and ReactDOM.render
src/App.js              renders routes
src/aws-config.js       region, bucket, API URL, Cognito ids
src/config/             Roles, RoutingConfig, reCAPTCHA site key
src/routes/             Router, allowed-route filter, switch
src/components/         screens
src/components/common/  nav, footer, tables, graphs, modal, WYSIWYG
src/utils/index.js      roles, ID check, UUID
src/contexts/           popup modal
src/hooks/              form fields, popup, window size
src/assets/             images
sonar-project.properties
sonar-scanner.bat
bitbucket-pipelines.yml
```

There is no `build/` directory, no `.github/` workflows, no Dockerfile, and no `.env` file in the clone.

`src/index.js` calls `serviceWorker.unregister()`.

## Routing

`src/routes/index.js` mounts one `Router` with a custom history and delegates to `Routing`. `Routing` filters `RoutingConfig` through `getAllowedRoutes` and renders `MapAllowedRoutes`. Only the path `/` is `exact`. A final route renders `NotFound` when `isAddNotFound` is set.

Child route objects under Admin and Profile are passed to the parent component as a `children` prop. They are not registered as nested `<Route>` elements by `MapAllowedRoutes`. Admin still has separate top-level routes for `/adminAnalytics` and `/adminSettings` so those URLs resolve. `/adminDashTest` exists only inside comments.

`NotFound` links a signed-in user to `/home`. That path is not in `RoutingConfig`.

## Auth

Amplify Auth in `src/index.js`:

| Setting | Value in `src/aws-config.js` |
|---|---|
| Region | `eu-west-1` |
| User pool | `eu-west-1_8MkXacpCX` |
| App client | `k607qc16a64ln1jq44kth31li` |
| `identityPoolId` | `eu-west-1:898145914743:userpool/eu-west-1_8MkXacpCX` |
| `mandatorySignIn` | `true` |

The identity pool string includes a user-pool path. Whether Amplify 3 accepts that value is **Unknown**. No `Auth.currentSession` result is attached as an Authorization header inside `unityRequests.js`. Login calls `Auth.currentSession()` and discards the return value.

Role gating reads `localStorage.roles` and does not subscribe to the Cognito session.

## API

One Amplify API name, `unity`:

```text
https://h8vzvoqq9c.execute-api.eu-west-1.amazonaws.com/development
```

Region `eu-west-1`. There is no second stage in this file.

`unityRequests(path, vars, action)` uses `API.get` when `action === "get"` and `API.post` otherwise. On failure it returns `{status: error.response.status}` and logs the status. It does not rethrow. Callers that only check `result` can treat a transport error as a soft failure.

`src/components/RequestsAPI.js` is a `fetch` wrapper with `mode: 'cors'`. No other file imports it.

### Calls made by screens

| Method | Path | Caller |
|---|---|---|
| GET | `/getfinesearch?Id=&noticeNumber=` | `LandingPage.js`, `QuickFineSearch.js` |
| GET | `/getallfines?...` | `OutstandingFines.js` |
| GET | `/queryinfringements?Id=&noticeNumber=` | `AARTOFineDetails.js` |
| GET | `/getoutstandingfines?Id=&noticeNumber=` | `CPAFineDetails.js` |
| GET | `/getcharges?Id=&noticeNumber=` | `CPAFineDetails.js` |
| GET | `/evidence?Id=&evidenceToken=&includeImage=true` | `CPAFineDetails.js` |
| GET | `/user/details?strCognitoUuid=` | `Login.js`, `Register.js` |
| GET | `/user/details?strEmail=` | `Register.js` |
| GET | `/user/details?userId=` | `Profile.js` |
| GET | `/transactions?userId=` | `Transactions.js` |
| GET | `/getsettings?userId=` | `Cart.js`, `AdminSettings.js` |
| GET | `/getgraphsettings?userId=` | `AdminSettings.js` |
| GET | `/getgraphs?userId=&startDate=&endDate=` | `AdminDashboard.js` |
| GET | `/reports?issuingAuthority=&startSettlementDate=&endSettlementDate=` | `AdminDashboard.js` |
| POST | `/recaptcha` | `Register.js` |
| POST | `/register` | `Login.js` (`API.post`), `Register.js` (`unityRequests`) |
| POST | `/updateregistration` | `Login.js`, `Register.js` |
| POST | `/update` | `Profile.js` |
| POST | `/postiveritransaction` | `Cart.js` |
| POST | `/postsettings` | `AdminSettings.js` |
| POST | `/postgraphsettings` | `AdminSettings.js` (create, edit, and delete) |

Checkout also builds a browser URL `{apiGateway}/iveripayment?transactionID=&veriLocation=&status=`. That URL is not called with Amplify. It is placed in the iVeri return fields.

Response shapes the UI branches on include `result`, `payload`, string payloads (`"User does not exist"`, `"No fines found"`, `"is incorrect"`, `"Third party did not respond"`, `"No response from iForce"`, `"No response from eNatis"`), and nested `eNatis` / `iForce` keys. A formal schema is not in the repository.

## Payments

`Cart.checkout` builds a hidden form and sets `action` to `https://portal.nedsecure.co.za/Lite/Authorise.aspx` with `method` POST.

Hardcoded Lite fields include `Lite_Version` `4.0`, `Lite_Currency_AlphaCode` `ZAR`, `Lite_Order_Terminal` `77777001`, `Ecom_Payment_Card_Protocols` `iVeri`, and `Lite_Merchant_ApplicationID` `{2ee048e5-8338-491b-aaea-dc91d43847f1}`.

`generateUUID` in `src/utils/index.js` returns a hex id. If the first character is a digit it prefixes `a`.

`public/iveri.html` redirects the browser to `http://pay-my-fines.s3-website-eu-west-1.amazonaws.com/PaymentNotification` plus the current query string.

`documentation/iveri.php` sends `Location` to `$_GET["veriLocation"]` plus `/PaymentNotification?` and the original query string. The cart no longer points at this script. A comment in `Cart.js` names `https://video.medmin.co.za/iveri.php`.

## Storage

Amplify Storage is configured for bucket `tmt-pmf-uploads` in `eu-west-1`, using the same identity pool id as Auth. `MAX_ATTACHMENT_SIZE` is `5000000`. No `Storage.` usage and no read of `MAX_ATTACHMENT_SIZE` were found under `src/`.

Browser keys written by the app include `roles`, `userDetails`, `userProfileDetails`, `IDNumbers`, `cartItems`, `cartCheckout`, `backFilters`, `outstandingFines`, `issuingAuthList`, and `financeIssuingAuthList`. The forgot-password path also writes `asdf`.

## Analytics and admin Google sign-in

`public/index.html`:

- Matomo tracker `//analytics.siliconsoftware.net/matomo.php`, site id `2`, domains `*.master.d39rr3ejryd4rk.amplifyapp.com`.
- Google tag `G-6M1WRZYG8D`.
- Script `https://apis.google.com/js/client:platform.js`.

`AnalyticsUtils.js` initialises `gapi.auth2` with client id `447800593954-oilip0f25n5gcqj4fe2f1jr5eng6q9ps.apps.googleusercontent.com`. `AnalyticsReport.js` calls the Analytics Reporting API v4 with `viewId: "YOUR_VIEW_ID"`.

reCAPTCHA site key used by `Register.js` is exported from `src/config/google.js` as `GoogleAPIKey`. The widget is `react-google-recaptcha`. The file name says Google API. The only use found is the reCAPTCHA `sitekey` prop.

## Infrastructure and deployment

`bitbucket-pipelines.yml` defines one step, SonarQube analysis, pipe `sonarsource/sonarqube-scan:3.0.2`, on branch pattern `{master}`. Variables `SONAR_HOST_URL` and `SONAR_TOKEN` are expected from the repository or workspace. Clone depth is `full`. The step does not run `yarn`, tests, or a deploy.

`sonar-project.properties` sets `sonar.projectKey=overdrive_paymyfines-frontend_e3690f7e-56ce-46dd-9ab9-9a9b17703aec`.

`sonar-scanner.bat` is a Windows launcher. It sets `SONAR_SCANNER_HOME` to `C:\sonar-scanner\bin\..` and runs scanner CLI jar `4.6.2.2472`.

No ECS, Docker, Serverless, or Amplify build spec (`amplify.yml`) is in the repository. Hosting is only implied by the Matomo Amplify hostname and the S3 website URL in `public/iveri.html`. The live host is **Unknown / Requires Confirmation**.

## Security observations from the code

These are descriptions of what the tree does, not a pentest.

- Cognito ids, the API Gateway URL, the reCAPTCHA site key, the iVeri merchant application id, and the terminal id are committed in client source. A production bundle would expose them.
- Route permission is `localStorage`, not a server session check on navigation.
- `ForgotPassword.js` can write a password to `localStorage` key `asdf`. `Register.js` reads and deletes that key.
- `/paymentNotification` trusts query parameter `status`.
- `documentation/iveri.php` redirects to a caller-supplied `veriLocation`.
- `public/iveri.html` redirects to a fixed S3 website over `http`.
- Guest fine lookups send `Id=GUEST`.

## Background work

No queue, cron, or worker process is defined in this repository. Payment return handling on the API (`/iveripayment`) is outside this tree.

## Development and testing

| Command | Defined in `package.json` |
|---|---|
| `yarn start` / `npm start` | CRA dev server with a larger HTTP header size |
| `yarn build` | `react-scripts build` |
| `yarn test` | `react-scripts test` |
| `yarn storybook` | Storybook 5 on port 6006 |
| `yarn pretty` | Prettier over `src/**/*.js` |

The only test file found is `src/App.test.js`. It renders `App` and unmounts it. It does not assert product behaviour.

Storybook has one story: `src/components/common/JumbotronWrapper.stories.js`.

The Bitbucket pipeline does not run these scripts.

## Monitoring

Matomo and the Google tag are the only monitoring hooks in the page. There is no application log shipper, error tracker, or health endpoint in this repository. API errors are `console.log` or a popup.

## Related repositories

Silicon Ark has separate directories for `paymyfines-frontend-new`, `paymyfines-worker`, `paymyfines-pdf-application`, `pmf-frontend-ah`, and `pmf-backend-ah`. This codebase does not import them. `README.md` mentions Amplify and Lambda. The Lambda implementation is not here. How this UI lines up with those projects is **Unknown / Requires Confirmation**.
