# Technical documentation — farmtrade (FarmTrade / AgMarket)

Evidence date: 2026-10-15.

Source: `git@bitbucket.org:overdrive/farmtrade.git`.

Default branch `master` at `f4f46ea9` (2020-07-03, Heinrich du Plessis, "changed service to httpd"). Product customisations are on `prod` at `429b1564` (2020-09-15) and `qa` at `a755fe81` (2020-09-16). `origin/HEAD` points at `master`. `master` is not an ancestor of `prod`. Merge base: `5a0e9596` (2020-06-23, "codedeploy"). `prod` is an ancestor of `qa`.

The scan-host path `~/projects/sonar_scan/repos/farmtrade` was not on this machine. Facts below come from git. Secret values are omitted. Files that contain them are named.

## Architecture

CS-Cart Multi-Vendor 4.11.4.SP3, PHP, MySQL, Apache `httpd`. One codebase serves the customer area, the admin area, the vendor area, and the REST API. Custom behaviour on the product line is a shipping add-on, a payment processor, a vendor-field add-on, an import API entity, and edits to mail and templates.

```text
index.php                 customer, AREA C
admin.php                 admin, AREA A (always present)
fa-admin.php              admin filename configured on prod/qa
vendor.php                vendor panel
api.php                   Tygh\Api
addressing.php            town lookup, product line only

app/addons/               stock add-ons + three custom add-ons on prod/qa
app/controllers/          stock controllers; orders.php patched on prod/qa
app/payments/payfast.php  product line only
app/Tygh/                 framework; ParcelPerfectAuth.php on prod/qa
app/functions/            stock functions
design/themes/            responsive, bright_theme
js/                       storefront and admin scripts
var/                      cache, languages, themes repository, upgrades
MySQL                     mysqli, prefix cscart_
```

Request path for a normal page: the entry script checks PHP 5.6.0, defines the area, includes `init.php`, and calls `fn_dispatch()`. `api.php` sets `API` and `NO_SESSION` and calls `$api->handleRequest()`. `addressing.php` also sets `API` and `NO_SESSION`, then calls Parcel Perfect and prints JSON.

## Application structure

Top level on `master`:

```text
admin.php
api.php
app/
appspec.yml
bitbucket-pipelines.yml
changelog.txt
codedeploy_deploy.py
config.local.php
config.php
copyright.txt
design/
images/
index.php
init.php
install.html
js/
phptest.php.txt
robots.txt
scripts/
store_closed.html
upgrades/
var/
vendor.php
```

`qa` adds, among the product files: `fa-admin.php`, `addressing.php`, `.gitignore`, `FarmTrade-Privacy-Policy-4-08-2020.pdf`, `sql/`, `images/loadinfo.gif`, `.config.local.php.swp`. `qa` does not contain `install.html`.

`changelog.txt` documents CS-Cart through 4.5.2.SP2 and says later history is at `http://docs.cs-cart.com/latest/history/index.html`. `config.php` sets `PRODUCT_VERSION` to `4.11.4.SP3`. Both statements are in the tree. The constant is the one the application defines.

`copyright.txt` is the Multi-Vendor licence. The program is licensed, not sold, from the copyright owners named in that file.

## Languages and frameworks

| Piece | Evidence |
|---|---|
| PHP | Entry scripts, `app/`. Minimum check 5.6.0. Webkul header says PHP 7.1. Host version unknown |
| Smarty templates | `design/themes/**/*.tpl` |
| JavaScript | `js/tygh/`, add-on scripts. Checkout uses `lite_checkout.js` |
| LESS | `design/themes/responsive/styles/data/Greeny.less` |
| MySQL | `database_backend` = `mysqli` |
| Python | Pipeline image `python:3.5.1`. `codedeploy_deploy.py`. `boto3==1.3.0` |
| Bash | `scripts/*` |
| CS-Cart / Tygh | `app/Tygh`, `fn_*` functions, add-on scheme 2.0 and 3.0 |

Libraries under `app/lib/vendor/` include Twig, Smarty, PHPMailer, Guzzle, Stripe's PHP library, Google reCAPTCHA, Symfony components, and Phinx. Those are the stock CS-Cart vendor directory. Presence in the tree does not mean each library is called by FarmTrade code.

## Stock add-ons in the tree

Directories under `app/addons/` on `master` (also present on the product line):

`access_restrictions`, `advanced_import`, `age_verification`, `anti_fraud`, `attachments`, `backend_google_auth`, `banners`, `barcode`, `bestsellers`, `blog`, `buy_together`, `call_requests`, `catalog_mode`, `customers_also_bought`, `data_feeds`, `default_secondary_currency`, `direct_payments`, `discussion`, `divido`, `ebay`, `email_marketing`, `facebook_pixel`, `form_builder`, `gdpr`, `geo_maps`, `gift_certificates`, `google_analytics`, `google_export`, `google_sitemap`, `graphql_api`, `help_tutorial`, `hidpi`, `hybrid_auth`, `image_zoom`, `janrain`, `master_products`, `mobile_app`, `mve_plus`, `my_changes`, `myob`, `newsletters`, `onekpay`, `payment_dependencies`, `payments_by_country`, `paypal`, `paypal_adaptive`, `paypal_for_marketplaces`, `pingpp`, `polls`, `price_list`, `product_variations`, `quickbooks`, `recaptcha`, `replain`, `required_products`, `reward_points`, `rma`, `rss_feed`, `searchanise`, `seo`, `sms_notifications`, `social_buttons`, `step_by_step_checkout`, `store_locator`, `storefront_rest_api`, `stripe`, `stripe_connect`, `suppliers`, `tags`, `tech_support_chat`, `vendor_categories_fee`, `vendor_communication`, `vendor_data_premoderation`, `vendor_debt_payout`, `vendor_locations`, `vendor_plans`, `vendor_privileges`, `vendor_terms`, `warehouses`, `watermarks`, `wishlist`.

Activation is a database flag. Git does not show which are on.

## Custom add-ons and classes (prod / qa)

| Path | Role |
|---|---|
| `app/addons/sil_parcel_perfect/` | Silicon Overdrive Parcel Perfect add-on. Hook `place_order`. |
| `app/Tygh/ParcelPerfectAuth.php` | Token and HTTP client used by addressing, shipping, and the status-A path. |
| `app/Tygh/Shippings/Services/Parcelperfect.php` | CS-Cart shipping service `IService`. |
| `app/addons/product_import/` | API entity `ImportProducts`. |
| `app/addons/wk_vendor_custom_registration/` | Webkul vendor fields, version 2.0. |
| `app/payments/payfast.php` | PayFast processor. |
| `js/addons/sil_parcel_perfect/parcel_perfect_address.js` | Checkout autocomplete. |
| `addressing.php` | Public JSON lookup. |
| `sql/payfast_mysql_install.sql` | Processor and language rows. |
| `sql/vendor_custom_reg_install.sql` | Vendor-field install SQL beside the add-on. |

`sil_parcel_perfect/func.php` also declares a second, non-namespaced `ParcelPerfectAuth`. `addressing.php` imports `Tygh\ParcelPerfectAuth`. The two classes are not the same code. The functions `fn_sil_parcel_perfect_get_delivery_quote` and `fn_sil_parcel_perfect_create_managed_pickup` are not registered in `init.php`.

## Database

| Setting | master | prod | qa |
|---|---|---|---|
| Engine key | `mysqli` | `mysqli` | `mysqli` |
| Name | `farmtrade` | `db_agmarket` | `db_agmarket` |
| Prefix | `cscart_` | `cscart_` | `cscart_` |
| Sessions | `database` | `database` | `database` |
| Cache | `file` | `file` | `file` |
| Locks | `database` | `database` | `database` |

`db_host`, `db_user`, `db_password`, and `crypt_key` are set in the tracked `config.local.php`. Values are omitted.

Custom tables and columns, created by add-on install SQL when that install is run:

| Object | Purpose |
|---|---|
| `cscart_sil_parcel_perfect_data` | Latest token and the timestamp stored at insert |
| `cscart_wk_vendor_custom_sections` and description, fields, field descriptions, field values | Webkul vendor form |
| `cscart_companies.firstname`, `.lastname`, `.wk_vendor_custom_fields` | Extra company columns |
| `cscart_orders.fax` | Stock column. The place-order hook writes a Parcel Perfect quote id into it |
| `cscart_shipments`, `cscart_shipment_items` | Stock tables. Status A inserts a waybill row |

`payfast_mysql_install.sql` writes `cscart_payment_processors` and `cscart_language_values` with the prefix spelled out as `cscart_`, not as the placeholder `?:`.

There are no project migration files beyond that SQL and the add-on `<queries>` blocks. Schema changes apply when an administrator installs the add-on or runs the SQL. Whether that has been done on a given database is **Unknown / Requires Confirmation**.

## APIs

### CS-Cart REST

`api.php` hands the request to `Tygh\Api`. Stock entities ship with the product, including `storefront_rest_api` and `graphql_api` add-ons. This document does not restate every stock entity.

### ImportProducts

Entity file: `app/addons/product_import/Tygh/Api/Entities/ImportProducts.php`.

| Method | Router type | Result |
|---|---|---|
| `index` | GET | Empty OK if vendor auth passes, otherwise 401 |
| `create` | POST | Import products and, for admins, categories |
| `update` | UPDATE | Same import path |
| `delete` | not the router | Root user only. Disables product codes for company id 1. Prints the count array, returns an empty OK body |

Privilege names returned: `create_things`, `edit_things`, `delete_things`, `view_things`. No other file in the add-on defines those privileges.

Product and company id are concatenated into SQL strings in this entity.

### addressing.php

| Request | Response |
|---|---|
| Not GET | 400, `["Bad Request"]` |
| GET without `town` | 400, town required |
| Token missing | 500, unable to retrieve token (file spelling: `retreive`) |
| `Quote.getPlacesByName` success | 200 and the result objects (`town`, `place`, `pcode` are what the checkout script reads) |
| Lookup error or empty results | 200 and one blank place object |

The script calls Parcel Perfect with the token as `token_id` on the query string.

### Parcel Perfect methods used

| Call | Caller |
|---|---|
| `Auth.getSalt`, `Auth.getSecureToken` | `ParcelPerfectAuth::retrieveToken` |
| `Quote.getPlacesByName` | `addressing.php`, shipping `getRequestData` |
| `Quote.getPlacesByPostcode` | shipping `getRequestData`, only after an empty name lookup |
| `Quote.requestQuote` | `getSimpleRates` |
| `Quote.updateService` | `processResponse` |
| `Collection.quoteToCollection` | admin order status `A` |

The service URL is the add-on setting, with query parameters `params`, `method`, `class`, and optional `token_id`. cURL sets `CURLOPT_SSL_VERIFYPEER` and `CURLOPT_SSL_VERIFYHOST` to false. Timeout is the add-on setting, or 10 seconds.

`getInfo()` returns carrier name language key `carrier_parcelperfect` and tracking URL `https://epx.pperfect.com/`.

### PayFast

Outbound: browser redirect to `www.payfast.co.za` or `sandbox.payfast.co.za` with merchant fields and a signature. The passphrase is appended for live mode only.

Inbound:

- `payment_notification.notify?payment=payfast&order_id=`
- `payment_notification.return`
- `payment_notification.cancel`

Notify processing is described in the PRD (WF-3) and business rules BR-018 through BR-023.

## External integrations

| System | Direction | Auth in repo | Failure behaviour in code |
|---|---|---|---|
| Parcel Perfect | Outbound HTTPS from PHP | Username and password in add-on settings. Token via salt and MD5. | Salt error: `die`. Empty lookup: blank JSON or skipped quote details. Token miss on addressing: HTTP 500 |
| PayFast | Browser redirect out. ITN and return in | Merchant id and key from the payment method. Sandbox uses the plugin's public test merchant | Bad signature, IP, validate, or amount: no status update. Order not in `N`: stop |
| Amazon SES | Outbound SMTP | Hardcoded in `PhpMailerTransport.php` when method is `smtp`. Port 587, TLS, host `email-smtp.eu-west-1.amazonaws.com` | PHPMailer error string via `SendResult` on send failure. Not otherwise handled in the diff |
| AWS CodeDeploy and S3 | Pipeline outbound | Bitbucket variables `PROD_*` / `QA_*` | Not described in the YAML beyond the script failing if a command fails |
| Google Fonts | Browser fetches the Greeny woff2 URL | None | Stock stylesheet |
| CloudFront | Config key only | None in the lines read | **Unknown / Requires Confirmation** whether a distribution exists |

## Infrastructure and deployment

Pipeline image: `python:3.5.1`.

`prod` step name "Deploy on PROD". `qa` step name "Deploy on QA" (present on the `qa` tip, not on `master`).

Steps: export AWS and CodeDeploy variables, `apt-get install zip`, `pip install boto3==1.3.0`, `zip -r /tmp/artifact.zip *`, `python codedeploy_deploy.py`.

AppSpec 0.0, os `linux`:

| Hook | Script | runas | timeout |
|---|---|---|---|
| BeforeInstall | `scripts/install_dependencies` | root | 300 |
| ApplicationStop | `scripts/remove_archives.sh` | root | 300 |
| ApplicationStart | `scripts/start_server` | root | 300 |

`install_dependencies` runs `yum install -y httpd`. `start_server` runs `service httpd start`. File owner and group: `apache`. Destination: `/var/www/html/farmtrade.co.za/`.

`scripts/stop_server` is not an AppSpec hook. The assignment `isExistApp = `pgrep httpd`` includes spaces around `=`.

`remove_archives.sh` walks `/opt/codedeploy-agent/deployment-root/` and deletes revision directories other than the last successful install recorded under `deployment-instructions`.

The pipeline zips `*`, so the artifact includes `config.local.php` and, on the product line, the Vim swap file and the PDF.

## Environment configuration

| Key | Role |
|---|---|
| `http_host`, `https_host` | Store host. Differs by branch. See business rule BR-030 |
| `http_path`, `https_path` | Empty. Sample `/store/cart` paths are commented |
| `customer_index`, `admin_index`, `vendor_index` | `index.php`, `admin.php` or `fa-admin.php`, `vendor.php` |
| `demo_mode` | false |
| `cdn_backend` | `cloudfront` |
| `store_prefix` | empty |
| `cache_backend` | `file` |
| `session_backend`, `lock_backend` | `database` |

Add-on settings for Parcel Perfect are stored by CS-Cart in the database after install, not in `config.local.php`.

`.gitignore` on `qa` lists `permissions.sh`, `var/cache`, `config.local.php`, and `var/backups`. `config.local.php` is still tracked. Ignoring a file does not untrack it.

## Auth

Stock CS-Cart login: email and password posts, session in MySQL, user-agent binding on (`SESS_VALIDATE_UA`). Password length constant 8.

Admin area is whatever account CS-Cart marks as admin. The filename `fa-admin.php` hides the default `admin.php` name only if nothing links to `admin.php`. The file itself was not removed and still sets `AREA` A.

Vendor terms checkbox: browser `required`, no posted field. See business rule BR-003.

API import: company membership or admin user type. Disable: `is_root == Y` only.

`addressing.php` does not authenticate.

Parcel Perfect: email-shaped username, password mixed with a salt using MD5, token id on later calls.

PayFast: signature over the posted fields, optional passphrase, source IP unless debug is on, server-side validate call, amount check.

No captcha requirement was added by the custom diffs. Stock add-on `recaptcha` is in the tree. Whether it is enabled is **Unknown / Requires Confirmation**.

## Background work, queues, storage, monitoring

No queue worker, cron entry, or daemon was found in the custom files. Parcel Perfect calls and mail happen inside the web request that quotes, places, or notifies.

CS-Cart's own scheduled tasks, if any are configured, live in the database. **Unknown / Requires Confirmation.**

Storage config in `config.local.php` points directories at the local tree (`dir` root and `var`). `cdn_backend` is `cloudfront` without an id in the keys that were read.

Cache is files under `var/cache`. `qa`'s gitignore names `var/cache` and `var/backups`.

Monitoring: none in the repository. `error_log` calls tagged `AUDIT_TEMP_EXIT` and `AUDIT_REENTRY` write the Parcel Perfect URL (including `token_id` when present) and the response body to the PHP error log. Those calls are still active on the product line.

`phptest.php.txt` prints `test`. It is not wired to a route.

SonarQube files named in the task-creation scan are not in git.

## Security notes

These are observations from the files. They are not an invitation to use the details.

- `config.local.php` is tracked and contains `db_password` and `crypt_key`. `.gitignore` lists the file and does not remove it from history.
- `.config.local.php.swp` is tracked on the product line. It is a Vim swap of the config file. It was not opened for this document.
- `PhpMailerTransport.php` on `prod`/`qa` contains an AWS access key id and an SMTP password in source. Admin SMTP settings are commented out. The values are omitted here. They should be treated as exposed and rotated if they were ever real. **Requires confirmation** from a person who owns the AWS account.
- Parcel Perfect cURL disables TLS peer and host verification.
- `ParcelPerfectAuth::makeCall` writes the full service URL and the response to the error log.
- `addressing.php` is unauthenticated.
- `ImportProducts` builds SQL by concatenating request fields.
- `fn_sil_parcel_perfect_get_delivery_quote` and `fn_sil_parcel_perfect_create_managed_pickup` embed a static Basic Authorization header. They are not registered as hooks. The header value is omitted.
- PayFast sandbox merchant id and key in `payfast.php` are the plugin's published test merchant, used only when the processor mode is `sandbox`.
- `robots.txt` does not disallow `addressing.php`, `api.php`, `admin.php`, or `fa-admin.php`.

## Development and testing

History is short: the first commit is "Init CS-Cart" on 2020-06-19. Authors on the product line include Kagiso Malepe, JacquesOverdrive, Joshua Gatley-Dewing, Jaco Brits, Heinrich du Plessis, heinrichoverdrive, and maanie. There are no git tags and no commit after 2020-09-16.

Stock unit tests are under `app/Tygh/Tests/Unit/`. No farmtrade test, PHPUnit config change, or CI test step was found. The pipeline deploys; it does not run tests.

`changelog.txt` is the upstream CS-Cart changelog, not a FarmTrade change log.

## Branch and config matrix

| | master `f4f46ea9` | prod `429b1564` | qa `a755fe81` |
|---|---|---|---|
| Host | `qa.farmtrade.co.za` | `agmarket.co.za` | `qa.agmarket.co.za` |
| Database | `farmtrade` | `db_agmarket` | `db_agmarket` |
| Admin index | `admin.php` | `fa-admin.php` | `fa-admin.php` |
| Custom add-ons | no | yes | yes |
| Pipeline | `prod` branch only | `prod` and `qa` steps (same file as `qa`) | `prod` and `qa` steps |
| AppSpec path | `/var/www/html/farmtrade.co.za/` | same path (no diff against `qa`) | `/var/www/html/farmtrade.co.za/` |

`tokenMan` (`5e805c26`) is merged into `qa`. `ShipmentAddressing`, `parcelperfect-auth`, and `dns-change` are earlier lines whose work is represented on `qa` through merges. They were not fully re-read as separate trees after `qa` was read.

## Unknown / Requires Confirmation

- Live branch, live host, and whether FarmTrade and AgMarket are one store.
- PHP version and MySQL version on the CodeDeploy host.
- Which add-ons and payment methods are enabled.
- The title of order status `A` and the PayFast status map.
- Whether company id `1` is the vendor the disable API should target.
- Whether the committed database password, `crypt_key`, and SES SMTP secret are still in use.
- Whether CloudFront, Bitbucket deploy variables, and the CodeDeploy groups still exist.
- Page 31 contents, and whether the privacy-policy PDF is shown anywhere in the live theme.
- Whether the empty Parcel Perfect collection date and the unused `$date` variable are accepted by the courier.
