Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

An API endpoint for integrations

An API is an address that is called not by a person in a browser but by another program: a warehouse system sends stock levels, an accounting system takes orders. OpenCart has no full REST API. For an exchange with an external system you usually write your own controller that accepts JSON and checks an access key.

The controller

Create a storefront controller in the extension:

// extension/acme_hello/catalog/controller/api/stock.php
namespace Opencart\Catalog\Controller\Extension\AcmeHello\Api;
 
class Stock extends \Opencart\System\Engine\Controller
{
    public function index(): void
    {
        $json = ['status' => 'ok'];
 
        $this->response->addHeader('Content-Type: application/json');
        $this->response->setOutput(json_encode($json));
    }
}

Check it from the console:

curl "http://shop.test/index.php?route=extension/acme_hello/api/stock"

The command prints the JSON answer with the key status.

The access key

The address is open to everyone, so the first thing the controller does is check a secret key. The key is stored in the module settings — add a field module_acme_hello_api_key to the settings form — and comes in a request header:

$key = (string) ($this->request->server['HTTP_X_API_KEY'] ?? '');
$expected = (string) $this->config->get('module_acme_hello_api_key');
 
if ($expected === '' || !hash_equals($expected, $key)) {
    $this->response->addHeader($this->request->server['SERVER_PROTOCOL'] . ' 401 Unauthorized');
    $this->response->addHeader('Content-Type: application/json');
    $this->response->setOutput(json_encode(['error' => 'Invalid API key']));
 
    return;
}

A request without the right key gets the status 401. hash_equals() compares strings in a way that does not let the key be guessed by response time.

Receiving data

The body of the request is JSON, it is read from php://input:

$items = json_decode(file_get_contents('php://input'), true) ?: [];
 
foreach ($items as $item) {
    $this->db->query(
        "UPDATE `" . DB_PREFIX . "product` SET `quantity` = '" . (int) ($item['quantity'] ?? 0) . "'
         WHERE `sku` = '" . $this->db->escape((string) ($item['sku'] ?? '')) . "'"
    );
}
 
$json = ['updated' => count($items)];

In a finished extension the query is moved into a model.

curl -X POST "http://shop.test/index.php?route=extension/acme_hello/api/stock" \
    -H "X-API-Key: your-secret-key" -H "Content-Type: application/json" \
    -d '[{"sku": "MUG-001", "quantity": 15}]'

The command prints the JSON answer with updated equal to 1, and the product with the SKU MUG-001 in Catalog → Products has the quantity 15.

Data read from php://input does not pass through $this->request, so it is not escaped by the engine. Cast numbers and escape strings yourself.

The built-in API

OpenCart has an API of its own, with keys created in System → Users → API: a name, a key and a list of allowed IP addresses. It is used by the admin panel itself when a staff member edits an order: routes under api/ assemble a cart and recalculate the totals. The set of routes and the way of logging in changed between releases 4.0 and 4.1, so relying on it in an external integration is risky. To study it, look at the controllers in catalog/controller/api/ of your copy.

Common mistake

The key is passed in the address: ...&key=your-secret-key. Addresses are written to web server logs, browser history and proxy logs, and the key leaks. Pass the key in a header and accept requests only over HTTPS.

Thanks for reading!

Donate to the site — we work hard for you.