# Cart And Orders

Authenticated users keep their cart in `$_SESSION['cart']` as `product_id => quantity`. Prices and product data are always reloaded from `cms_shop` before an order is created.

For payment pricing, automatic/manual provider behavior, callback completion, purchase-history totals, and the full-page pending-payment section, read [Payment Discounts And Cart Payments](21-payment-discounts.md) first. This document remains the broader cart/order reference.

## File Index

| Area | File | Main methods/role |
|---|---|---|
| Schema | `installation/cms.sql` | `cms_orders`, `cms_orders_products`, `cms_payments.order_id`, legacy payment migration |
| Orders | `src/model/base/order.model.php` | `create`, `createManual`, `getCartProducts`, `getHistory`, `isPayable`, `markPaid`, `fulfill`, `expirePending` |
| Fulfillment | `src/model/base/store.model.php` | `getProduct`, `addPurchase`, `getPurchasesUnclaimedByTrxID`, `logPayment` |
| Cart endpoint | `src/pages/private/cart/cart.controller.php` | Add, update, remove, clear, checkout, JSON/HTML responses and duplicate-submit token |
| Cart UI | `src/pages/private/cart/` | Full cart, payment methods, pending order, AJAX updates and confirmations |
| Store UI | `src/pages/public/store/` | Product list and add/replace quantity requests |
| Payment helpers | `src/core/modules/PaymentModule/PaymentHelpers.php` | `getAvailableMethods`, `isAvailable`, `isManualPayment`, `getManualPaymentMessage`, `getImageURL`, `CalculatePriceForPaymentType`, `ShowPaymentMethods`, `startPayment` |
| Payment base | `src/core/modules/PaymentModule/PaymentModule.php` | Provider contract, audit notifications and return URL |
| PayPal | `src/core/modules/paypal/` | Creates order payment request and validates IPN callbacks |
| History | `src/pages/private/purchase-history/` | Orders, products, claim state and retry payment methods |
| Shared cart markup | `src/core/templates/components-form-base.template.php` | Product forms and overridable floating-cart template |
| Magic enhancement | `src/templates/magic/js/cart.js`, `src/templates/magic/css/cms.css` | Floating cart, token updates, toasts and styling |

## Database Diagram

```mermaid
erDiagram
    cms_shop ||--o{ cms_orders_products : "snapshotted from"
    cms_orders ||--|{ cms_orders_products : contains
    cms_orders ||--o{ cms_payments : audits
    cms_orders ||--o{ cms_orders_temp_txn : tracks
    cms_orders ||--o{ cms_orders_crypto_data : records
    cms_orders_products ||--o{ purchasesunclaimed : fulfills

    cms_shop {
        int product_id PK
        string name_item
        decimal price
        smallint Type
        string Value
        smallint min_quantity
        bool enabled
        bool showDetailsButton
        string detailsButtonLabel
        string detailsButtonURL
    }
    cms_orders {
        int order_id PK
        string owner
        decimal total
        decimal total_after_discount
        decimal discount_applied
        string currency
        enum status
        string payment_method
        string txn_id
        datetime creation_date
        datetime payment_date
    }
    cms_orders_products {
        int order_product_id PK
        int order_id
        int product_id
        string name_item
        decimal price
        smallint type
        string value
        int quantity
        string fulfillment_reference UK
    }
    cms_orders_temp_txn {
        int order_id
        string payment_method
        string txn_id
        datetime creation_date
    }
    cms_orders_crypto_data {
        bigint crypto_data_id PK
        int order_id
        string payment_method
        string reference_id
        string network
        string currency
        string transaction_hash
    }
    cms_payments {
        string payment_method PK
        string txn_id PK
        int order_id
        string username
        string payment_status
        decimal mc_gross
        string mc_currency
    }
    purchasesunclaimed {
        bigint UID PK
        string Owner
        smallint Type
        string Value
        bool Claimed
        string txn_id
    }
```

The diagram includes logical relationships; the schema does not enforce foreign keys. CMS tables use `Model::$connCMS`, while `purchasesunclaimed` uses the game connection `Model::$conn`, so fulfillment cannot be one transaction across split databases.

- `cms_orders.owner` and `purchasesunclaimed.Owner` use `StoreModel::$ownerField`: username by default and UID for supported stream sources.
- Order-product fields are snapshots. Later changes or disabling in `cms_shop` must not alter an existing order. Manual sales also create a paid order snapshot.
- `cms_orders_products.fulfillment_reference` maps to `purchasesunclaimed.txn_id`; one order line may produce several game rows. Pack products are expanded by the existing fulfillment logic.
- `cms_payments` is provider audit data, not the purchase-history source. An order may have multiple payment notifications/attempt records.
- `cms_orders_temp_txn` keeps provider references for the order lifecycle. Successful PayRam completion keeps the matching temporary row and stores the prefixed PayRam reference in `cms_orders.txn_id`; cancelled and expired temporary rows also remain for audit.
- `cms_orders_crypto_data` stores PayRam network, token, transaction hash, addresses, block, amount, and confirmation details separately from the order transaction ID.
- PayRam may return `filledAmountInUsd` as null. Its adapter uses `filledAmount` for the configured near-full partial-payment tolerance and maps the expected amount to `mc_gross` while retaining the actual received value in `net_money`.
- The cart has no table: it exists only in the authenticated PHP session.

## Cart Flow

1. Store forms post `cart_action=add`, product, quantity and token to the private `cart` route.
2. Add/update validates the product and minimum quantity, then updates the session cart. Remove and clear update the session directly.
3. Only `checkout` and `pay-order` validate the token. It prevents duplicate/replayed submissions; authentication, authorization and account ownership are enforced separately.
4. Do not add token checks to add, update, remove or clear, and do not treat token entropy or rotation as an authentication/CSRF security boundary.
5. AJAX responses return `message`, `token` and the complete `cart` object. Full cart forms also request `cartHtml`; store requests do not.
6. JavaScript updates all token inputs, the floating cart and API toast. Normal POST requests redirect to the cart with a session message.

`cart-items.view.php` is the AJAX-replaced fragment. `cart.view.php` renders it inside `#cart-content` and renders the pending order outside that element. Pending-order history must only be queried for the full cart page; never query or render it from `cartRender()` or the fragment.

The floating cart is always shown on the store and shown elsewhere when it has items. Its default markup and hidden item template are in `FormComponentsBase`, so themes may override `showFloatingCart()` and `showFloatingCartItem()`.

## Checkout Flow

1. Checkout reloads and validates every cart product through `OrderModel::getCartProducts()`.
2. `OrderModel::create()` writes a `PENDING` order and immutable product snapshots in one CMS transaction, then the session cart is cleared.
3. `PaymentHelpers` exposes enabled providers with an implementation file, manual URL methods, and manual message methods. Each method displays its configured percentage and calculated final price. Negative percentages are shown as `+ Fee`; positive percentages are shown as `- Discount`, followed by `To pay` and the final price.
4. A provider receives the local order and calculated final price. A manual URL method leaves the new order pending, clears the cart, returns to the cart, and shows its configured URL with instructions to contact the GM. A manual message method renders translated payment instructions in the same payment-information block used by provider checkout HTML.
5. The cart also shows the newest pending order, with payment methods, even when the cart contains items.
6. Pending orders become `CANCELLED` after 24 hours when orders are loaded or payment is attempted.

## Payment And Fulfillment

PayPal sends the local `order_id` as its invoice and charges the calculated final price. The callback validates the provider response, account owner, currency, merchant, status and transaction ID, then `PaymentModule` recalculates the price from the stored original total before completing the payment.

On completed payment:

1. A provider audit row is stored in `cms_payments` and linked by `order_id`; its money fields contain the amount received from the provider.
2. The order changes from `PENDING` to `PAID`, storing the calculated final total in `total_after_discount` and the configured percentage in `discount_applied`.
3. Every `cms_orders_products` row is passed to the existing `StoreModel::addPurchase()` method.
4. Each line uses its own `fulfillment_reference` as `purchasesunclaimed.txn_id`. Existing rows are checked first so callback retries do not grant the line twice.

## History And Claims

Purchase history reads `cms_orders` and `cms_orders_products`, not payment rows. Paid orders display `total_after_discount` with the original `total` struck through. It loads product snapshots from the CMS connection and aggregates game-side `purchasesunclaimed` rows by fulfillment reference through the game connection. It returns `claim_status` as `0` or `1`; the view translates it to `Not claimed` or `Claimed`. A paid line is claimed only when all matching rows have `Claimed = 1`.

Order statuses are `PENDING`, `PAID`, `REJECTED` and `CANCELLED`. The CMS creates claim rows, but the game server remains responsible for granting them and updating `Claimed`. The admin Orders Management page may approve `PENDING` or `REJECTED` orders without using the customer payment-age check.
