# Business rules — sabullion_current_live

Evidence date: 2026-10-05. Source: `master` at `6cbf6777`.

Rules below are what the PHP does. They are not a signed business policy. Each rule is marked **Implemented** when the code states it, or **Inferred** when the label is a reading of that code. Live WooCommerce settings can override or never reach a hook. Whether a rule runs in production is **Unknown** where the active theme or an option is required.

Amounts are the literals in the source. They were not executed.

## Identity and locale

### BR-001 — The product name in code is Sa Bullion

`SITENAME` is the string `Sa Bullion`. Outgoing mail uses the display name `SA Bullion` and the address `business@sabullion.co.za`. Shop and email links use `https://sabullion.co.za`.

Provenance: **Implemented** (`config/site.php`, `config/mailer.php`, `functions.php`).

### BR-002 — VAT is a database value, not a hard-coded 15%

On load, the theme reads `sysVat.dblVat` through `$SA` and stores the rounded value on `$SystemSettings["VAT"]`. A comment records a country change from 14% to 15% on 2018-04-01 and says the column is the source. The running code uses the column.

Provenance: **Implemented**. The stored rate is **Unknown**.

### BR-003 — South African English files are shipped

`wp-content/languages` is an `en_ZA` pack. The site option that selects the locale is not in Git.

Provenance: **Implemented** as files. The active locale is **Unknown**.

## Access

### BR-004 — Cart and checkout require a WordPress login

If the visitor is not logged in and the request is the cart or the checkout, the theme redirects to `/my-account` and exits.

Provenance: **Implemented** (`wpse_131562_redirect`).

### BR-005 — The My Account dashboard sends a logged-in user to the shop

Request URI exactly `/my-account/` redirects to `/shop`. Orders, edit-address, edit-account, and my-wishlist URIs are not redirected by that function.

Provenance: **Implemented** (`account_redirect`). **Inferred:** the shop is the landing page after login. The code only matches those exact paths, including the trailing slash.

### BR-006 — Registration continues on the edit-account screen

`woocommerce_registration_redirect` returns `home_url('/my-account/edit-account/')` and sets `$_SESSION["isRegistering"]`. Saving account details while that flag is set clears it, adds the notice "Your account was created successfully and a password has been sent to your email address.", and redirects to the My Account permalink.

The new-account WooCommerce email action is removed. Password-change email is disabled.

Provenance: **Implemented** in the child theme and in `disable_account_creation_email`. **Requires Confirmation:** the notice says a password email was sent, and the new-account notification is also removed. Which email, if any, still sends the password was not traced past that removal.

### BR-007 — Account menu while registering is empty

If `$_SESSION["isRegistering"]` is true, `woocommerce_account_menu_items` returns an empty array. Otherwise the items are dashboard, orders, downloads (label "Download"), edit-address (label "Addresses"), edit-account (label "Account Details"), and customer-logout.

Provenance: **Implemented**.

### BR-008 — Calculator and upload HTTP routes do not check a user

Every `permission_callback` in `sab-api.php` returns true. That includes POST uploads of a customer file and a customer ID file, and GET routes that send email.

Provenance: **Implemented**. This is an access rule in code. It is not a judgement that the rule is acceptable.

### BR-009 — Logout attempts to destroy all session tokens

`wp_logout` calls `WP_Session_Tokens::get_instance( $user_id )` and `destroy_all()`, then `wp_clear_auth_cookie()`. `$user_id` is not set in that function.

Provenance: **Implemented**. Runtime result **Unknown**.

## Shop money

### BR-010 — Gateway id `cod` adds a card fee of 3% of the subtotal

Three places add or display this:

1. `bbloomer_add_checkout_fee_for_gateway` on `woocommerce_cart_calculate_fees`: if `chosen_payment_method` is `cod` or `$_SESSION["selectedPayment"]` is `cod`, add a fee named `Card Fee` of `subtotal * 0.03`.
2. `custom_handling_fee` on the same hook: if `chosen_payment_method` is `cod`, add a fee named `Card Fee` of `subtotal * 0.03` with taxable set to false.
3. The child `woocommerce/cart/cart-totals.php`: if `$_SESSION["selectedPayment"]` is `cod`, display the 3% figure and call `add_fee` for `Card Fee` again.

Provenance: **Implemented**. **Inferred:** a single checkout can attach the fee more than once because the hooks are separate. The amount WooCommerce actually persists was not executed. **Requires Confirmation.**

**Inferred:** the authors labelled a `cod` gateway "Card Fee". WooCommerce's own id `cod` means cash on delivery. Which tender the shop offered under that id is **Unknown / Requires Confirmation**.

### BR-011 — Shipping rate `free_shipping:1` adds a delivery fee

Base `150` plus `0.008` times the cart subtotal. The fee name in `functions.php` is `Delivery Fee`.

The child cart template uses `$_SESSION["selectedShipping"] == "free_shipping:1"` and the same formula. Its `add_fee` calls are commented out, so the template computes a display amount and does not itself add the fee.

Both the `>= 150000` branch and the other branch assign the same sum. A commented VAT line would have multiplied by `0.15` and is not executed.

Provenance: **Implemented**. **Inferred:** the 150000 comparison does not change the fee. Whether 150000 was meant to select a different tariff is **Requires Confirmation**.

`custom_selected_shipping_methods` is read in `functions.php`. `selectedShipping` is read in the cart template. Which script writes those session keys was not confirmed in this pass. **Unknown** if both stay in sync.

### BR-012 — Krugerrand figure is gold plus 3%

For the latest `gold_price` row, ZAR, USD, EUR, and GBP Krugerrand values are `round(gold + gold / 100 * 3, 2)`.

Provenance: **Implemented** in `api/gold/index.php`. This is separate from the shop card fee, which is also 3% and applies to a WooCommerce subtotal.

### BR-013 — Out of stock is labelled Awaiting Stock

`envy_stock_catalog` prints "In Stock" or "Awaiting Stock". The loop button is "Buy" or "Read More". The single add-to-cart text function sets "Buy", or "Read more" when a non-variable product is out of stock and the view is not the single product page.

Provenance: **Implemented**.

### BR-014 — Internet Explorer 11 does not stay on `/shop/`

User agent containing `Trident/7.0` and request URI `/shop/` navigates to `https://sabullion.co.za/shop-not-compatible-with-internet-explorer/`.

Provenance: **Implemented**.

### BR-015 — Product admin stores three price parts

Meta keys `metal_value`, `premium_value`, and `vat_value`, labels Metal Value, Premium Value, VAT Value. Empty POST becomes an empty string.

Provenance: **Implemented**. No rule was found in the functions that were read that forces WooCommerce price = metal + premium + VAT. That equation is **not** stated here.

### BR-016 — Checkout copy

- Order notes field is removed.
- Button text is `SUBMIT ORDER`.
- Translated string `Billing & Shipping` becomes `Billing Details`.
- Cart title is `{count} products in your cart`.
- "GO BACK TO PRODUCTS" links to `https://sabullion.co.za/shop/`.
- Breadcrumb home is that shop URL.
- On a single product the crumb at index 1 has an empty label.
- Shipping package name is the HTML `<h3 class='shipping_title'>Shipping</h3>`.

Provenance: **Implemented**.

### BR-017 — Every WooCommerce email is Cc'd to the business mailbox

Header gains `Cc: <business@sabullion.co.za>` for every `$email_id`.

Provenance: **Implemented** in the child theme. The filter does not check `$email_id`.

### BR-018 — Every WooCommerce email attachment list includes an FNB PDF URL

The URL is `https://sabullion.co.za/wp-content/uploads/2022/03/FNB-Bank-Confirmation.pdf`. The filter does not check the email id.

Provenance: **Implemented** as an appended string. WooCommerce attachments are normally filesystem paths. Whether a URL is accepted by the mailer was not executed. **Unknown**.

### BR-019 — Billing phone is copied onto the user

On `woocommerce_checkout_update_user_meta`, if `billing_phone` was posted, it is saved with `sanitize_text_field` to user meta `billing_phone`.

Provenance: **Implemented**.

## Calculators and mail

### BR-020 — Gold history needs a currency and a period

When both query parameters are present, rows come from `gold_price` between that period and today. The currency parameter is inserted into the select list. When either is missing, the latest row is used instead (see the PRD edge note: the "missing" JSON errors are inside the branch that already has both values).

Provenance: **Implemented**.

### BR-021 — Illustration emails on the public host copy the retail coins team

When `SERVER_NAME` is `sabullion.co.za`, investment email CCs `coins@sabullion.co.za` ("The Retail Coins Team") and BCCs `imran.obrien@gmail.com`. Minted-bar email CCs the same coins address. The minted-bar BCC to that Gmail address is commented out.

When `SERVER_NAME` is `lamp`, investment email BCCs `rachel@overdrive.co.za`.

Provenance: **Implemented**. A comment says the coins CC was requested by Imran on 2018-06-05. The comment is not the rule; the `AddCC` call is.

### BR-022 — Illustration mail invites the reader to an application wizard

The body links to `https://sabullion.co.za/dashboard/login.php?client/details/sa-natural` and gives telephone `021 447 3240` for SA Bullion Investor Services. Subject includes a reference number from the request.

Provenance: **Implemented**. The wizard is not a file in this commit.

### BR-023 — Non-lamp mail uses the Office 365 mailbox

`config/mailer.php` default branch: SMTP, host `smtp.office365.com`, port `587`, `SMTPSecure` `tls`, username `business@sabullion.co.za`, From and Reply-To that address. TLS peer verification is turned off (`verify_peer`, `verify_peer_name` false, `allow_self_signed` true). The password literal is in the file and is omitted here.

`lamp` uses `isSendMail()` and host `lamp`.

Provenance: **Implemented**.

### BR-024 — Logged-in shortcodes print the current user's id, name, and email

`[show_loggedin_as]` fills utility-bill hidden fields. `[show_id_loggedin_as]` fills identity-document hidden fields. Nothing is printed when `$user_login` is empty.

Provenance: **Implemented**.

## Content templates

### BR-025 — Named page templates exist for buy, sell, invest, and editorial pages

The template list is in `features.md` F-004. Assigning a template to a page is a WordPress editor action and is not in Git.

Provenance: **Implemented** as files. Which pages use them is **Unknown**.

### BR-026 — News-style category slugs have templates

Slugs implied by the file names: `company-news`, `gold-nuggets`, `goldnuggets`, `position-papers`, `press-coverage`, `press-releases`, `quarterly-report`, `updates`, `videos`. Two gold-nugget spellings both have templates.

Provenance: **Implemented** as file names. **Inferred** that those category slugs were used. The terms are not in Git.

## Rules that are not in force

Commented code is not a rule:

- Pickup locations "Melbourne Road Shop" and "Perth Road Shop".
- Hiding shipping calculation on the cart.
- The PDF download REST route.
- Delivery-fee VAT at 15% inside the fee function (the line is commented).
- The 150000 branch does not implement a different price. See BR-011.

## Unknown

- Shipping zones, tax classes, coupon rules, and which payment gateway ids exist. Those are WooCommerce settings.
- Minimum or maximum order quantities.
- Who may upload a customer file beyond "the route does not check a WordPress user". The body of `upload-shop-customer-file.php` was not fully reviewed for file-type limits.
- Whether `$SystemSettings["VAT"]` is the rate the shop charges. WooCommerce tax configuration was not in Git. The theme loads the column; a shop tax class may ignore it.
