PrestaShop gives you order documents and a browser Print button. What it does not give you is a ticket appearing in the back room the moment a customer pays: that requires connecting the order lifecycle to a printer reachable over the internet.
The short answer: hook actionOrderStatusPostUpdate, check that the new status means "paid", build the ticket, and POST it. A ready-made module does this with settings instead of code; a small custom module does it exactly your way. Neither needs a computer running in the shop.
Two routes
| Module | Custom hook | |
|---|---|---|
| Setup | Install and configure | ~20 lines of PHP |
| Layout control | Template settings | Total |
| Multi-shop | Handled | Yours to handle |
| Best for | Most shops | Specific routing or logic |
The PrestaShop connector covers the first route; the module documentation walks through installation and receipt settings.
The hook route
PrestaShop fires actionOrderStatusPostUpdate after an order's status has changed and been persisted. That is the right moment: the earlier actionOrderStatusUpdate fires before the change is committed.
public function hookActionOrderStatusPostUpdate($params)
{
/** @var OrderState $newStatus */
$newStatus = $params['newOrderStatus'];
$order = new Order((int) $params['id_order']);
// Only print once payment is accepted; `paid` covers the standard states.
if (!$newStatus->paid) {
return;
}
$lines = [
'ORDER ' . $order->reference,
date('d/m/Y H:i'),
'',
];
foreach ($order->getProducts() as $product) {
$lines[] = (int) $product['product_quantity'] . 'x ' . $product['product_name'];
}
$lines[] = '';
$lines[] = 'TOTAL: ' . number_format($order->total_paid, 2) . ' ' . $this->context->currency->iso_code;
$ch = curl_init('https://www.expedy.fr/api/v2/printers/' . Configuration::get('MYMOD_PRINTER_UID') . '/print');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_HTTPHEADER => [
'Authorization: ' . Configuration::get('MYMOD_API_SID') . ':' . Configuration::get('MYMOD_API_TOKEN'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'printer_msg' => implode("\n", $lines),
'origin' => 'ps-' . $order->id,
]),
]);
curl_exec($ch);
curl_close($ch);
}
PrestaShop-specific pitfalls
Status changes are not unique. A payment module can set the same paid status twice, and a merchant can re-apply it manually from the back office. Store a flag (an order meta row or a dedicated table), and return early if the order has already printed. Without it, every re-save is another ticket.
newOrderStatus->paid is the reliable test. Hard-coding status ids breaks the day someone adds a custom status or you deploy to a shop configured differently. The paid flag is what payment modules actually set.
Multi-shop needs a printer per shop. In a multi-shop installation, read the printer id from the shop context rather than from a single global configuration value, or every shop prints to the same machine.
Timeouts block the customer. The hook runs inside the request that confirms the order. A five-second CURLOPT_TIMEOUT is a ceiling on how long a shopper might wait if the API is unreachable: keep it low, and treat delivery failures asynchronously rather than making the checkout depend on a printer.
Product names carry anything. Accented characters are fine; emoji and unusual symbols are not. Transliterate before building the ticket: this is the most common cause of a printer that "randomly stops".
Ticket layout
Bold headers, a logo, a QR code to the order, a barcode, automatic cut: all of it is markup inside printer_msg. See the receipt layout reference and, for a European invoice-style ticket, EAN-13 barcodes.
Start with a test ticket
Before touching PrestaShop at all, create a free account and print a ticket with a plain curl. Verifying the print chain independently means that when something misbehaves later, you already know whether to look at PrestaShop or at the printer.
Related: Shopify and WooCommerce follow the same pattern with different hooks.
FAQ
Which PrestaShop hook should I use to print an order?
actionOrderStatusPostUpdate, which fires after the status change has been persisted. Test newOrderStatus->paid rather than hard-coding status ids, so custom statuses and differently configured shops keep working.
Why does the same order print twice?
Payment modules can apply the same paid status more than once, and merchants can re-apply it manually. Store a printed flag on the order and return early when it is already set.
Does it work with a multi-shop installation?
Yes, but read the printer id from the shop context instead of a single global configuration value, otherwise every shop prints to the same machine.
Do I need a computer in the shop?
No. A cloud printer connects over 4G, Wi-Fi or Ethernet on its own, and an existing USB printer can be reached through a Raspberry Pi adapter.