⚡ Version 1.0.0 — Stable

TrellisCart Documentation

Everything you need to install, configure, extend, and run TrellisCart — an open-source ecommerce platform by Trellis Group LLC.

Requirements

ComponentMinimumRecommended
PHP8.08.2+
MySQL / MariaDBMySQL 5.7 / MariaDB 10.3MySQL 8.0+
Web ServerApache 2.4 / NginxApache with mod_rewrite
PHP ExtensionsPDO, pdo_mysql, json, mbstring+ sodium, curl, gd
HTTPSRequired for checkout and adminLet's Encrypt
Disk Space50MB + uploadsSSD hosting

What's Included

  • Full storefront with product catalog, search, and category browsing
  • Multi-step secure checkout with shipping and tax calculation
  • Admin panel with orders, products, customers, reports, and content management
  • PayPal Orders API v2 payment plugin with full webhook support
  • FedEx, UPS, and USPS shipping rate plugins
  • Zen-Cart importer for migrating existing stores
  • Signed auto-update system with Ed25519 verification
  • Role-based admin access control (staff, admin, superadmin)
  • Zone-based tax rates by state and country
  • Coupon and gift certificate system

Installation

Install TrellisCart on any standard PHP hosting environment in a few steps.

📋
Before You Begin

You will need: a web host with PHP 8.0+, a MySQL database, the ability to create database users, and HTTPS enabled on your domain.

Step-by-Step Installation

Create a database

In cPanel or your hosting panel, create a new MySQL database and a dedicated database user. Grant the user ALL PRIVILEGES on the new database. Write down the database name, username, and password.

Upload TrellisCart

Download the latest release ZIP from trelliscart.com. Upload and extract it to your server. The public/ directory should be your web root, or you can place TrellisCart in a subdirectory.

Run the installer

Visit https://yourstore.com/install/ in your browser. Enter your database credentials, store URL, and create your first admin account. The installer creates all database tables automatically.

Configure your store

Log into the admin panel at /admin/. Set your store name, email, currency, shipping methods, tax rates, and payment settings before accepting orders.

Delete the installer

After successful installation, delete or rename the install/ directory from your server. The installer is automatically locked after first use, but removal is best practice.

File Permissions

DirectoryPermissionReason
public/images/755Web server needs write access for uploads
storage/755Cache, backups, and session storage
includes/config.php640Protect credentials from other users
All other files644Web server read-only
⚠️
Production Warning

Do not accept live orders until you have configured and tested payment settings, tax rates, shipping rates, and email delivery. Use a test payment account first.

cPanel Shared Hosting

TrellisCart works on standard cPanel shared hosting. Common setup steps:

  • Use MySQL Databases to create database and user
  • Use File Manager or FTP to upload files
  • Use MultiPHP Manager to set PHP version to 8.0+
  • Use MultiPHP INI Editor to confirm register_argc_argv is Off
  • Use SSL/TLS to enable HTTPS before going live

Configuration

TrellisCart stores its configuration in includes/config.php and in the database via the admin panel.

config.php

The includes/config.php file is created by the installer and contains server-level settings that cannot be changed from the admin panel.

PHP
🔐
Never Commit config.php

The config file contains database credentials. Never commit it to a public repository. It is excluded by .gitignore by default.

Admin Panel Settings

All other settings are managed from the admin panel under Store → Configuration:

SettingLocationDescription
Store NameConfiguration → GeneralAppears in page titles and emails
Store EmailConfiguration → GeneralFrom address for all outgoing email
CurrencyConfiguration → GeneralDefault currency code and symbol
Products Per PageConfiguration → GeneralCatalog pagination
Free Shipping MinimumStore → ShippingOrder total for free shipping
Tax SettingsStore → Tax RatesZone-based or global tax rates
Mail ServerConfiguration → EmailSMTP host, port, credentials

Email Configuration

TrellisCart sends email via SMTP. Configure under Configuration → Email:

  • Transport: SMTP (recommended) or PHP mail()
  • Host: Your SMTP server hostname
  • Port: 587 (TLS) or 465 (SSL) — avoid port 25 on shared hosting
  • Username / Password: Your email account credentials
  • From Address: Must match your sending domain for deliverability

Use the Send Test Email button to verify your settings before going live.

Upgrades

TrellisCart uses a signed update system. Every release ZIP is signed with Ed25519 and verified before installation.

💡
Always Back Up First

The upgrade system creates an automatic backup before deploying. Store it securely. Test upgrades on a staging copy of your store first when possible.

Automatic Update Check

TrellisCart checks trelliscart.com/releases.json once per day for new versions. When an update is available, a notification appears in the admin dashboard.

Installing an Update

Go to Upgrade Center

In the admin panel, navigate to Admin → Upgrade Center. Available updates will be listed with version number and release notes.

Download and verify

Click Download Update. TrellisCart downloads the ZIP and its .sig manifest, verifies the SHA256 hash and Ed25519 signature. If verification fails, the update is rejected.

Install

Click Install Update. Files are extracted to a staging directory first, then deployed to live. Database migrations run automatically. A backup is created before any changes.

Manual Update

You can also manually place a release ZIP in storage/updates/. The ZIP must have a matching .sig manifest file alongside it. Without the manifest, the update is refused.

Rollback

If an update causes problems, go to Upgrade Center → Backup History and click Rollback on the backup taken before the update. The rollback restores files and database from the backup.

⚠️
Superadmin Only

The Upgrade Center requires superadmin role. Staff and admin users cannot access it.

Products & Catalog

Manage your product catalog from the admin panel under Catalog → Products.

Adding a Product

Go to Catalog → Add Product

Fill in the product name, SKU, price, and description. SKU must be unique across your catalog.

Set pricing

Enter the regular price. Optionally set a sale price — if set, it displays as the active price with the regular price struck through.

Configure inventory

Enable stock tracking and enter the quantity. When stock reaches zero, the product shows as out of stock and cannot be added to cart.

Upload an image

Accepted formats: JPG, PNG, WebP, GIF. Maximum 5MB. Images are validated and renamed automatically on upload.

Assign to categories

Select one or more categories. Mark one as the primary category — this determines the product's canonical URL.

Product Fields Reference

FieldRequiredNotes
NameYesDisplayed on storefront and in orders
SKURecommendedMust be unique; used by importer for deduplication
PriceYesBase retail price
Sale PriceNoActive price when set; overrides regular price
Cost PriceNoInternal only; used for profit reports
Stock QtyNoRequired if Track Stock is enabled
Weight (lbs)RecommendedRequired for carrier rate calculation (FedEx, UPS, USPS)
ManufacturerNoBrand; shown on product page and filterable
Sort OrderNoLower number = appears first in listings

Stock Behavior

Stock is not decremented when an order is placed. It decrements when:

  • A PayPal payment is captured (via return URL or webhook)
  • An admin manually changes order status from Pending to Processing or Shipped

This prevents abandoned carts from permanently reducing inventory.

Categories

TrellisCart supports unlimited nested category levels. Categories can have images, descriptions, and SEO settings.

Creating Categories

Go to Catalog → Categories and click Add Category. Set the name, parent category (for subcategories), and optionally upload a category image.

Category Images

Category images are displayed on the homepage category grid and category listing pages. Accepted formats: JPG, PNG, WebP, GIF. Images are stored in public/images/categories/.

Nesting

To create a subcategory, select a parent when creating the category. TrellisCart supports unlimited depth, but three levels (top → sub → sub-sub) is recommended for usability.

Category Slugs

Slugs are generated automatically from the category name. They must be unique. You can edit the slug when creating or editing a category. Changing a slug after products are linked may affect URLs.

Shipping

TrellisCart supports flat-rate shipping, free shipping thresholds, and live carrier rates from FedEx, UPS, and USPS.

Flat Rate Shipping

Configure flat rates under Store → Shipping:

  • Standard: Default rate applied to most orders
  • Expedited: Optional faster shipping option
  • Overnight: Optional priority shipping option
  • Free Shipping Minimum: Order total above which shipping is free

Carrier Plugins

Live carrier rates require API credentials from each carrier. Configure under Plugins → [Carrier] Shipping.

CarrierAPI RequiredCredentials Needed
FedExFedEx Developer AccountClient ID, Client Secret, Account Number
UPSUPS Developer AccountClient ID, Client Secret, Account Number
USPSUSPS Web ToolsUser ID, Origin ZIP code
📦
Weight Required for Carrier Rates

Product weight in lbs must be set on each product for carrier rate calculation to work. Without weight, the carrier API cannot return accurate rates.

How Rates Are Calculated

At checkout, TrellisCart collects the customer's destination address and queries enabled carrier plugins. Available rates are displayed to the customer who selects their preferred method. The selected method and rate are stored on the order.

Tax Zones

TrellisCart supports per-state and per-country tax rates with a global fallback. Configure under Store → Tax Rates.

Zone-Based Tax

Enable Zone-Based Tax in the Tax Settings card to activate per-location rates. When enabled, the system looks up the customer's state and country at checkout and applies the matching rate.

Rate Lookup Order

  1. Exact state + country match (e.g., New Mexico + US)
  2. Country-wide rate with no state (e.g., all US states)
  3. Global catch-all rate (no country, no state)
  4. Global fallback rate from Settings

Adding a Tax Rate

Select country

Choose the country. The state dropdown populates automatically with all states or provinces for US and Canada.

Select state (optional)

Leave blank to apply to all states in the country. Select a specific state for a state-level rate.

Enter rate

Enter the combined rate as a percentage. For example, New Mexico: 5.125% state + 3.75% county = 8.875%. Enter 8.8750.

Multiple Rates (Stacking)

Multiple active rates for the same state are summed in priority order. Use this for separate state and county rates. Lower priority number = applied first.

Global Fallback

The global rate applies when no zone rate matches. Set it to 0 if you only collect tax in specific states.

Payment Setup

TrellisCart includes a PayPal Orders API v2 plugin. Card payments require a separate gateway plugin with client-side tokenization.

PayPal Setup

Create a PayPal Developer App

Go to developer.paypal.com → My Apps & Credentials → Create App. Get your Client ID and Client Secret.

Enter credentials in admin

Go to Plugins → PayPal Payments. Enter Client ID, Client Secret, and select Sandbox or Production mode.

Create a webhook

In PayPal Developer Dashboard, create a webhook pointing to https://yourstore.com/plugins/paypal/webhooks/paypal.php. Subscribe to: PAYMENT.CAPTURE.COMPLETED, PAYMENT.CAPTURE.DENIED, PAYMENT.CAPTURE.REFUNDED, CUSTOMER.DISPUTE.CREATED, CUSTOMER.DISPUTE.RESOLVED.

Enter Webhook ID

Copy the Webhook ID from PayPal Developer Dashboard and paste it into the Webhook ID field in TrellisCart PayPal settings. This enables signature verification on incoming webhooks.

🔐
Webhook ID Required

TrellisCart rejects all PayPal webhooks if the Webhook ID is not configured. Without it, payment capture events cannot be processed automatically.

Order Payment Flow

  1. Customer selects PayPal at checkout
  2. TrellisCart creates a PayPal order and redirects customer to PayPal
  3. Customer approves payment on PayPal
  4. Customer is returned to your store and the capture is attempted
  5. On success: order status → Processing, stock decremented, coupon use incremented
  6. PayPal also sends a webhook confirming capture (idempotent — safe if both fire)

Card Payments

Raw card data is never collected or stored by TrellisCart. Card payments require a gateway plugin (Stripe, Authorize.net) that tokenizes card data on the client side before any data reaches your server. Orders placed via card stay in Pending status until the gateway confirms payment.

Coupons

Create discount codes under Catalog → Coupons. TrellisCart supports percentage, fixed amount, and free shipping coupon types.

Coupon Types

TypeEffectExample
PercentReduces subtotal by a percentageSAVE10 → 10% off
FixedReduces subtotal by a flat amountSAVE5 → $5.00 off
Free ShippingSets shipping cost to $0.00FREESHIP → no shipping charge

Coupon Options

  • Minimum Order: Coupon only applies if subtotal meets this amount
  • Max Uses: Limits total redemptions across all customers (0 = unlimited)
  • Active: Toggle to enable or disable without deleting

Coupon Use Timing

Coupon use count increments only after payment is confirmed — not when the order is placed. This prevents abandoned orders from consuming coupon uses.

Zen-Cart Import

Migrate your existing Zen-Cart store to TrellisCart using the built-in importer. Go to Admin → Zen-Cart Import.

📋
Zen-Cart Versions Supported

The importer is tested with Zen-Cart 1.5.x through 2.x. Database table prefix is configurable — common prefixes are zen_ and zen6b_.

What Gets Imported

DataNotes
CategoriesFull tree with parent-child relationships preserved
ProductsName, SKU, price, description, weight, stock, manufacturer
Product ImagesCopied from source path to TrellisCart; random safe filenames assigned
Category ImagesCopied from same source images folder
Manufacturers / BrandsBrand names and URLs
CustomersName, email, address — passwords are NOT migrated (security)
OrdersPast order history with line items
ReviewsProduct reviews with ratings
CouponsActive coupon codes
🔒
Passwords Not Migrated

Customer passwords are never imported. Zen-Cart uses a different hashing scheme. Customers will need to use the password reset flow to set a new password on their first login.

Running the Import

Connect to source database

Enter your Zen-Cart database host, port, name, username, password, and table prefix. Click Test Connection to verify.

Set image path (optional)

Enter the full server path to your Zen-Cart images/ folder (e.g. /home/user/public_html/zencart/images/). Required to copy product and category images.

Select data to import

Choose which data types to import. You can run the importer multiple times — existing records are matched by SKU and skipped if Skip Duplicates is enabled.

Run import

Click Run Import. Progress is shown in real time. A summary report is displayed when complete.

Re-running the Import

You can safely re-run the importer. With Skip Duplicates enabled, existing products (matched by SKU) are skipped. To do a full fresh import, clear the relevant tables in phpMyAdmin first.

Admin Users & Roles

TrellisCart uses role-based access control with three built-in roles. Manage users under Admin → Admin Users.

Roles

RoleAccess Level
staffView orders and catalog; upload media only
adminEdit orders, catalog, customers, coupons, content, and store settings
superadminFull access including admin users, upgrades, imports, and plugins

Staff Permissions

  • View orders (read-only)
  • View product catalog (read-only)
  • Upload media images

Admin Permissions

Everything staff can do, plus:

  • Edit orders and update order status
  • Edit products, categories, manufacturers
  • View and edit customers
  • View reports
  • Edit banners, newsletters, coupons, and gift certificates
  • Change store settings

Superadmin Permissions

Everything admin can do, plus:

  • Manage admin user accounts and roles
  • Run upgrades and rollbacks
  • Run Zen-Cart importer
  • Manage and configure plugins
  • Edit CMS pages (raw HTML)
  • View activity logs
💡
Principle of Least Privilege

Assign the lowest role that meets each user's needs. Reserve superadmin for store owners and trusted developers only.

Managing Orders

View and manage all orders from Sales → Orders.

Order Statuses

StatusMeaning
PendingOrder placed; payment not yet confirmed
ProcessingPayment confirmed; being prepared for shipment
ShippedTracking number added; en route to customer
DeliveredConfirmed delivered
CancelledOrder cancelled before fulfilment
RefundedPayment refunded

Stock and Coupon Accounting

When an admin changes an order from Pending → Processing (for COD or bank transfer orders), TrellisCart automatically decrements stock and increments coupon use count. This only happens once — subsequent status changes do not re-decrement.

Adding a Tracking Number

Open an order, scroll to the Shipping section, and enter the tracking number and carrier. The customer receives an email notification with a link to track their shipment.

Issuing a Refund

PayPal refunds can be initiated from the order detail page. For other payment methods, process the refund through your payment provider and then manually update the order status to Refunded.

Saved Carts

Abandoned carts are visible under Sales → Saved Carts. You can view cart contents and send a recovery email to the customer.

Email Setup

TrellisCart sends transactional emails for orders, account creation, and password resets.

Emails Sent Automatically

EmailTrigger
Order ConfirmationPayment confirmed (status → Processing)
Order Status UpdateAdmin changes order status with notify enabled
Welcome EmailNew customer account created
Password ResetCustomer requests password reset
Cart RecoveryAdmin sends recovery email from Saved Carts

SMTP Configuration

Go to Configuration → Email and set:

  • Transport: SMTP
  • Host: Your SMTP server (e.g. smtp.yourdomain.com)
  • Port: 587 for TLS, 465 for SSL
  • Encryption: TLS (recommended) or SSL
  • Username / Password: Your email credentials
  • From Name / Address: What customers see as the sender
💡
Improve Deliverability

Set up SPF, DKIM, and DMARC DNS records for your sending domain. Without these, transactional emails may land in spam folders.

Media & Images

Upload and manage images from Content → Media.

Upload Restrictions

  • Allowed types: JPG, PNG, WebP, GIF (validated by file content, not extension)
  • Maximum size: 5MB per file
  • SVG: Not allowed (SVG can contain JavaScript)
  • Filenames: Randomized on upload — original filename is not used

Image Locations

TypePath
Product imagespublic/images/products/
Category imagespublic/images/categories/
Banner imagespublic/images/
General mediapublic/images/
🔒
PHP Execution Blocked in Image Folders

The public/images/ directory has an .htaccess rule that prevents PHP execution. Even if a malicious file were uploaded, it cannot be executed.

Plugin System

TrellisCart plugins live in the plugins/ directory. Each plugin is a self-contained folder with its own PHP files, admin settings, and frontend handlers.

Plugin Structure

plugins/
└── myplugin/
    ├── myplugin.php         ← Main plugin file (required)
    ├── admin/
    │   └── settings.php     ← Admin settings page
    ├── includes/
    │   └── class-api.php    ← Plugin classes
    └── webhooks/
        └── handler.php      ← Webhook endpoint

Main Plugin File

The main plugin file (myplugin.php) must define plugin metadata and register the plugin with TrellisCart:

PHP '0',
    'api_key'    => '',
    'api_mode'   => 'sandbox',
];

Accessing TrellisCart Functions

All TrellisCart core functions are available in plugins after including bootstrap:

PHPrequire_once dirname(__DIR__, 2) . '/includes/bootstrap.php';

Plugin Settings Page

Plugin admin pages go in admin/settings.php inside your plugin folder. Require admin access at the top:

PHPrequire_once dirname(__DIR__, 3) . '/includes/bootstrap.php';
tc_require_admin('admin'); // or 'superadmin'

Storing Plugin Settings

Use the tc_configuration table to store plugin settings. All core config functions work for plugins:

PHP// Read a setting
$apiKey = tc_config('myplugin_api_key', '');

// Save a setting
tc_update(tc_table('configuration'),
    ['config_value' => $newValue],
    'config_key=?', ['myplugin_api_key']
);
🔒
Security Requirements

Every plugin admin page must call tc_require_admin() or tc_require_permission(). Every POST handler must verify the CSRF token with tc_csrf_verify(). Never skip these checks.

PHP Function Reference

Core functions available throughout TrellisCart and in plugins after loading bootstrap.

Database

FUNC tc_rows(string $sql, array $params = []): array

Execute a SELECT query and return all rows as an associative array. Returns an empty array if no rows found.

$products = tc_rows(
    'SELECT * FROM ' . tc_table('products') . ' WHERE is_active=? ORDER BY sort_order',
    [1]
);
FUNC tc_row(string $sql, array $params = []): ?array

Execute a SELECT query and return the first row, or null if not found.

$product = tc_row(
    'SELECT * FROM ' . tc_table('products') . ' WHERE product_id=?',
    [$productId]
);
FUNC tc_val(string $sql, array $params = []): mixed

Execute a SELECT query and return the first column of the first row.

$count = tc_val('SELECT COUNT(*) FROM ' . tc_table('orders') . ' WHERE status_id=?', [1]);
FUNC tc_insert(string $table, array $data): int

Insert a row and return the new auto-increment ID.

$orderId = tc_insert(tc_table('orders'), [
    'customer_id' => $customerId,
    'status_id'   => 1,
    'total'       => 49.99,
]);
FUNC tc_update(string $table, array $data, string $where, array $params): int

Update rows matching the WHERE clause. Returns number of rows affected.

tc_update(tc_table('orders'),
    ['status_id' => 2],
    'order_id=?', [$orderId]
);
FUNC tc_table(string $name): string

Return the full table name with the configured prefix. Always use this instead of hardcoding table names.

$table = tc_table('products'); // Returns e.g. "tc_products"

Configuration

FUNC tc_config(string $key, string $default = ''): string

Read a configuration value from the database. Returns default if not found.

$storeName = tc_config('store_name', 'My Store');
$taxRate   = (float) tc_config('tax_rate', '0');

Authentication

FUNC tc_require_admin(string $minRole = 'staff'): void

Redirect to admin login if not logged in, or show 403 if role is insufficient. Call at the top of every admin page.

tc_require_admin();            // Any logged-in admin
tc_require_admin('admin');     // Admin or superadmin
tc_require_admin('superadmin'); // Superadmin only
FUNC tc_require_permission(string $permission): void

Show 403 if the logged-in admin does not have the specified permission.

tc_require_permission('orders_edit');
tc_require_permission('catalog_edit');
tc_require_permission('upgrade_manage');
FUNC tc_csrf_token(): string + tc_csrf_verify(string $token): bool

Generate and verify CSRF tokens. Include in every form and verify in every POST handler.

// In form:
echo '';

// In POST handler:
if (!tc_csrf_verify($_POST['csrf_token'] ?? '')) {
    die('Invalid request');
}

Helpers

FUNC e(string $value): string

HTML-escape a value for safe output. Always use when outputting user or database data in HTML.

echo e($product['name']); // Safe output
FUNC tc_sanitize(string $input): string

Strip HTML tags and trim whitespace from input. Use for plain text fields.

$name = tc_sanitize($_POST['name'] ?? '');
FUNC tc_redirect(string $url): never

Redirect to a URL. Relative paths (starting with /) are prepended with the store base URL automatically.

tc_redirect('/account/');         // Relative
tc_redirect('https://example.com'); // Absolute
FUNC tc_base(): string

Return the store's base URL without trailing slash. Use for building links.

$url = tc_base() . '/catalog/search.php?q=' . urlencode($query);
FUNC tc_admin_log(string $action, string $detail = ''): void

Write an entry to the admin activity log.

tc_admin_log('plugin_action', 'Processed payment for order #' . $orderId);

Cart

FUNC tc_finalize_paid_order(int $orderId, string $captureId): bool

Idempotent order finalization after confirmed payment. Updates status to Processing, decrements stock, increments coupon use. Safe to call multiple times — skips if already finalized.

// In a payment plugin after successful capture:
tc_finalize_paid_order($orderId, $captureTransactionId);

Hooks & Events

TrellisCart's hook system lets plugins respond to store events without modifying core files.

📋
Hook System

The hook system is designed for extensibility in future versions. Core integration points are available now via direct plugin file inclusion. A formal event dispatcher is planned for v1.1.

Current Integration Points

FilePurpose
plugins/paypal/return.phpPayPal return URL — payment capture on return from PayPal
plugins/paypal/cancel.phpPayPal cancel URL — customer cancelled on PayPal
plugins/paypal/webhooks/paypal.phpPayPal webhook endpoint — receives payment events
plugins/fedex/rate.phpFedEx rate query — called during checkout shipping step
plugins/ups/rate.phpUPS rate query
plugins/usps/rate.phpUSPS rate query

Adding a Shipping Plugin

See the Shipping Plugins guide for the interface a shipping plugin must implement.

Adding a Payment Plugin

See the Payment Plugins guide for the interface a payment plugin must implement.

Shipping Plugins

Build a shipping rate plugin by creating a rate.php file in your plugin folder that returns available shipping methods.

How Shipping Plugins Work

  1. During checkout, TrellisCart scans enabled plugins for a rate.php file
  2. Each rate.php receives destination info and cart contents and returns available rates
  3. All rates from all enabled plugins are merged and displayed to the customer

rate.php Interface

PHP 'mycarrier_ground',
        'label' => 'My Carrier Ground (3-5 days)',
        'cost'  => 7.99,
    ],
    [
        'id'    => 'mycarrier_express',
        'label' => 'My Carrier Express (1-2 days)',
        'cost'  => 14.99,
    ],
];

Rate Object Fields

FieldTypeDescription
idstringUnique method identifier stored on order
labelstringDisplayed to customer at checkout
costfloatShipping cost in store currency
estimated_daysintOptional delivery estimate

Payment Plugins

Payment plugins handle the checkout payment step. TrellisCart never stores raw card data — all payment plugins must use client-side tokenization or redirect flows.

🔒
Never Collect Raw Card Data

Do not build payment plugins that collect card numbers, expiry dates, or CVV codes on your server. Use gateway-hosted fields (Stripe Elements, Authorize.net Accept.js) or redirect flows (PayPal) only.

Payment Flow

  1. Customer selects payment method at checkout step 2
  2. Customer reviews order at checkout step 3 and clicks Place Order
  3. Order is created in Pending status
  4. Customer is redirected to gateway or gateway-hosted fields collect a token
  5. On successful payment: call tc_finalize_paid_order($orderId, $captureId)
  6. Customer is shown order confirmation

Using tc_finalize_paid_order()

This function handles everything after a confirmed payment: status update, stock decrement, coupon increment. It is idempotent — safe to call from both the return URL and a webhook.

PHP// After confirming payment with your gateway:
$success = tc_finalize_paid_order($orderId, $gatewayTransactionId);

if ($success) {
    tc_redirect('/checkout/confirmation.php');
} else {
    // Already finalized or order not found
    tc_redirect('/checkout/confirmation.php');
}

Webhook Handlers

Payment webhooks must independently verify the request comes from the gateway before taking any action. Never trust a webhook payload without cryptographic verification.

PHP// Example: verify webhook signature before processing
$rawBody  = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';

if (!myGateway_verifySignature($rawBody, $signature, $webhookSecret)) {
    http_response_code(400);
    exit;
}

$event = json_decode($rawBody, true);
// Process event...

Security Guide

Security practices built into TrellisCart and steps you should take before going live.

Built-In Security Features

FeatureDetails
Password hashingArgon2ID with high memory and time cost
Session securitySession regeneration on login, secure + HttpOnly + SameSite cookies
CSRF protectionSession-bound token required on all POST requests
SQL injectionAll queries use PDO prepared statements with parameterized values
XSS protectionAll output escaped with e(); CMS content strip_tags'd
File upload validationfinfo MIME detection + getimagesize + 5MB limit + random filenames
PHP execution in uploadsBlocked by .htaccess in image directories
Admin brute forceIP + username lockout with exponential backoff
Update signingEd25519 + SHA256 mandatory on all updates
Security headersCSP, HSTS, X-Content-Type-Options, X-Frame-Options
Role-based accessGranular permissions per admin action and page

Before Going Live

  • Enable HTTPS and redirect all HTTP traffic to HTTPS
  • Set TC_DEBUG to false in config.php
  • Use a strong, unique SECRET_KEY (32+ random characters)
  • Change all default passwords immediately
  • Delete or rename the install/ directory
  • Configure the updater public key in UpgradeManager.php
  • Set up the PayPal Webhook ID before accepting PayPal payments
  • Restrict admin access by IP in your server config where possible
  • Ensure storage/backups/ is not web-accessible
  • Test order flow with a PayPal sandbox account before going live
🚨
Reporting Security Issues

Do not post security vulnerabilities publicly. Report them privately through the official contact method at trelliscart.com/security. See SECURITY.md in the release package for the full policy.

Password Policy

  • Customer passwords: minimum 12 characters
  • Admin passwords: use a password manager to generate long random passwords
  • Never reuse passwords across services
  • Admin accounts are locked after 5 failed login attempts with exponential backoff

Sensitive File Locations

These files contain sensitive information and must be protected:

FileContainsProtection
includes/config.phpDatabase credentials, secret keyNot web-accessible; excluded from git
bin/private.keyRelease signing private keyExcluded from git; store offline
storage/backups/Database dumps, file backupsNot web-accessible via .htaccess

Best Practices

Recommendations for running TrellisCart reliably and securely in production.

Development Practices

  • Never develop directly on a live store — use a staging environment
  • Test all payment flows with sandbox accounts before going live
  • Use version control for any customizations
  • Keep config.php out of version control
  • Use TC_DEBUG = true only in development — never in production

Plugin Development

  • Always call tc_require_admin() at the top of admin pages
  • Always verify CSRF tokens on POST requests with tc_csrf_verify()
  • Use tc_table() for all table names — never hardcode
  • Use prepared statements — never string concatenation for SQL values
  • Escape all output with e()
  • Log plugin actions with tc_admin_log()
  • Use tc_finalize_paid_order() for all payment confirmations
  • Never store API keys in PHP files that could be committed to git

Operations

  • Back up the database daily — use the Upgrade Center backup tool or your host's backup service
  • Monitor the admin activity log for unusual actions
  • Review low stock alerts regularly
  • Test email delivery monthly — send a test email from Configuration → Email
  • Check for TrellisCart updates regularly — security patches are released as updates
  • Review admin user list quarterly — remove inactive accounts

Performance

  • Optimize product images before upload — aim for under 200KB per image
  • Enable PHP OPcache on your server for significant performance improvement
  • Use a CDN for static assets (images, CSS, JS) on high-traffic stores
  • Keep the products-per-page setting reasonable (12–24) to avoid slow catalog pages

Release Signing

TrellisCart uses Ed25519 asymmetric cryptography to verify update packages. Every update ZIP must be signed before it can be installed.

How It Works

  1. You generate an Ed25519 keypair once using bin/generate-keypair.php
  2. The public key goes into UpgradeManager.php — this is what stores use to verify
  3. The private key stays with you, offline, never shared
  4. For each release, you run bin/sign-release.php which creates a .sig manifest
  5. The manifest contains the ZIP's SHA256 hash and an Ed25519 signature over that hash
  6. When a store installs an update, it verifies the hash and signature before extracting anything

Generating Your Keypair

BASHphp /path/to/trelliscart/bin/generate-keypair.php

This creates bin/private.key and bin/public.key. Copy the public key output and paste it into includes/classes/UpgradeManager.php at the RELEASE_PUBLIC_KEY constant.

🔐
Protect Your Private Key

Back up bin/private.key to a secure offline location immediately. If you lose it, you cannot sign future releases. Never commit it to git — it is excluded by .gitignore by default.

Signing a Release

BASHphp /path/to/trelliscart/bin/sign-release.php /path/to/trelliscart-1.0.0.zip

This creates trelliscart-1.0.0.zip.sig. Upload both the .zip and the .zip.sig to your download server.

Updating releases.json

After signing, add the SHA256 hash to your releases.json file and upload it to trelliscart.com/releases.json:

JSON{
  "latest": {
    "version": "1.0.1",
    "download_url": "https://trelliscart.com/downloads/trelliscart-1.0.1.zip",
    "sha256": "paste-sha256-hash-here",
    "min_php": "8.0.0"
  }
}

What Happens Without a Signature

If a ZIP is placed in storage/updates/ without a matching .sig file, the upgrade system refuses to install it and shows an error. This is intentional — unsigned updates are never installed.

Database Schema

TrellisCart uses a prefixed table structure. The default prefix is tc_. All tables use InnoDB with utf8mb4 charset.

Core Tables

TableDescription
tc_configurationKey-value store settings (store name, email, etc.)
tc_admin_usersAdmin accounts with role and hashed password
tc_customersCustomer accounts
tc_address_bookCustomer saved addresses
tc_customer_groupsCustomer groups with discount percentages

Catalog Tables

TableDescription
tc_productsProduct master data
tc_categoriesCategory tree with self-referential parent_id
tc_products_to_categoriesMany-to-many product/category link
tc_manufacturersBrand/manufacturer records
tc_product_imagesAdditional product images
tc_option_namesProduct option names (Size, Color, etc.)
tc_option_valuesOption values with price modifiers
tc_product_attributesProduct/option value links
tc_specialsTime-limited sale prices
tc_featuredFeatured product list
tc_reviewsCustomer product reviews

Order Tables

TableDescription
tc_ordersOrder master with billing, shipping, totals, payment
tc_order_itemsLine items for each order
tc_order_historyStatus change log per order
tc_order_statusesStatus definitions (Pending, Processing, etc.)
tc_saved_cartsAbandoned cart recovery data
tc_couponsCoupon codes and usage tracking
tc_gift_certificatesGift certificate balances

System Tables

TableDescription
tc_tax_ratesZone-based tax rates by country/state
tc_tax_classesTax class definitions
tc_zonesGeographic zone definitions
tc_countriesCountry codes and names
tc_currenciesCurrency definitions and rates
tc_bannersStorefront banner content
tc_pagesCMS page content
tc_newsletter_subscribersEmail newsletter signups
tc_admin_activity_logAdmin action audit trail
tc_upgradesUpdate and rollback history
tc_db_migrationsApplied database migration tracking

File Structure

Overview of the TrellisCart directory layout.

trelliscart/
├── index.php                  ← Entry point (includes public/index.php)
├── page.php                   ← CMS page renderer
├── includes/
│   ├── bootstrap.php          ← Loaded by every page; sets up DB, session, auth
│   ├── config.php             ← Database credentials and constants (not in git)
│   ├── functions/
│   │   ├── database.php       ← tc_rows(), tc_row(), tc_insert(), etc.
│   │   ├── helpers.php        ← e(), tc_sanitize(), tc_redirect(), etc.
│   │   ├── cart.php           ← Cart functions and tc_finalize_paid_order()
│   │   ├── auth.php           ← tc_require_admin(), RBAC, login functions
│   │   └── mail.php           ← Email sending via SMTP
│   └── classes/
│       ├── UpgradeManager.php ← Signed update system
│       └── ZenCartImporter.php← Zen-Cart migration tool
├── admin/                     ← Admin panel pages
│   ├── login.php
│   ├── index.php              ← Dashboard
│   ├── orders/
│   ├── catalog/
│   ├── customers/
│   ├── reports/
│   ├── store/
│   ├── content/
│   ├── system/
│   └── upgrade.php
├── public/
│   ├── index.php              ← Storefront homepage
│   ├── css/
│   ├── js/
│   └── images/
│       ├── products/          ← Product images (PHP execution blocked)
│       └── categories/        ← Category images
├── account/                   ← Customer account pages
├── cart/                      ← Cart page
├── catalog/                   ← Search, category, product pages
├── checkout/                  ← Checkout steps 1-4
├── plugins/
│   ├── paypal/
│   ├── fedex/
│   ├── ups/
│   └── usps/
├── templates/
│   ├── storefront/            ← Header, footer, product card templates
│   └── admin/                 ← Admin sidebar, topbar templates
├── install/
│   ├── index.php              ← Web installer
│   ├── schema.sql             ← Full database schema
│   └── migrations/            ← Incremental schema updates
├── storage/
│   ├── backups/               ← Update backups (not web-accessible)
│   ├── updates/               ← Uploaded update ZIPs
│   ├── staging/               ← Temporary extraction during updates
│   └── cache/                 ← Rate limiting and config cache files
└── bin/
    ├── generate-keypair.php   ← One-time Ed25519 keypair generator
    └── sign-release.php       ← Sign a release ZIP for distribution

Key Configuration Files

FilePurposeIn Git?
includes/config.phpDB credentials, store URL, secret keyNo — excluded by .gitignore
bin/private.keyRelease signing private keyNo — excluded by .gitignore
bin/public.keyRelease signing public keyOptional — safe to commit
storage/version.txtCurrent installed versionNo

Changelog

Version history for TrellisCart.

v1.0.0 — Initial Release

April 2026 — Trellis Group LLC

Features

  • Full storefront with category browsing, product pages, search, and featured products
  • Multi-step checkout with address, shipping method selection, payment, and confirmation
  • Complete admin panel — orders, products, categories, customers, reports, content, and settings
  • PayPal Orders API v2 plugin with full webhook signature verification
  • FedEx, UPS, and USPS live shipping rate plugins
  • Zone-based tax rates by state and country with stacking support
  • Coupon system — percent, fixed, and free shipping types with max-use limits
  • Gift certificate system
  • Zen-Cart importer — products, categories, customers, orders, reviews, coupons, and images
  • Signed update system with Ed25519 + SHA256 mandatory verification
  • Staged update extraction with rollback support
  • Role-based admin access control — staff, admin, superadmin
  • Granular per-action AJAX permissions
  • Admin brute-force protection with IP + username lockout
  • Customer login rate limiting
  • Full security header suite — CSP, HSTS, X-Content-Type-Options, X-Frame-Options
  • File upload validation via finfo + getimagesize + size limits
  • Idempotent order finalization — safe for webhook + return URL simultaneous capture
  • CMS pages and banner management
  • Newsletter subscriber management
  • Admin activity log
  • Saved cart recovery with email sending
  • Low stock reporting
  • Sales and product reports

Security

  • Argon2ID password hashing throughout
  • Session regeneration on login (admin and customer)
  • CSRF protection on all POST requests
  • PDO prepared statements for all database queries
  • Raw card data never collected or stored
  • PayPal webhook signature verification mandatory
  • Update signing mandatory — unsigned updates refused
  • PHP execution blocked in upload directories