Derniers articles
Aucun résultat pour cette recherche. Essayez un autre terme.
Des guides techniques avec du vrai code, pas des promesses en l'air. Chaque intégration couvre deux choses : afficher les résultats Heurix à la place de la recherche native, et garder votre catalogue synchronisé.
Honnêteté d'abord : ce sont des guides d'intégration destinés à un développeur, pas des applications "installer en un clic" depuis les stores Shopify/PrestaShop/etc. — ça viendra si la demande le justifie, mais on n'a pas voulu vous le faire croire avant que ce soit vrai.
Le relais (Cloudflare Worker) :
// worker.js — déployé sur Cloudflare Workers (gratuit pour ce volume)
export default {
async fetch(request, env) {
const url = new URL(request.url);
const q = url.searchParams.get("q") || "";
const res = await fetch("https://api.heurix.fr/v1/index/" + env.HEURIX_CATALOG + "/search", {
method: "POST",
headers: {
"Authorization": "Bearer " + env.HEURIX_API_KEY, // secret Cloudflare, jamais exposé
"Content-Type": "application/json"
},
body: JSON.stringify({ q, limit: 20 })
});
return new Response(await res.text(), {
headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "https://votre-boutique.myshopify.com" }
});
}
}
Dans le thème (Online Store 2.0), remplacez le rendu de sections/search-results.liquid par un appel à ce relais :
<div id="heurix-results"></div>
<script>
const params = new URLSearchParams(window.location.search);
fetch("https://votre-worker.workers.dev/search?q=" + encodeURIComponent(params.get("q") || ""))
.then(r => r.json())
.then(data => {
document.getElementById("heurix-results").innerHTML = data.hits.map(h =>
`<a href="/products/${h.product.handle}" class="heurix-hit">
<span>${h.product.name}</span><span>${h.product.price} €</span>
</a>`
).join("");
});
</script>
Enregistrez un webhook Shopify (Admin API ou shopify.app.toml pour une app custom) sur les sujets products/create, products/update, products/delete, pointé vers le même Worker :
// Ajout dans worker.js : gestion du webhook Shopify
async function handleWebhook(request, env) {
const product = await request.json();
await fetch("https://api.heurix.fr/v1/index/" + env.HEURIX_CATALOG + "/items", {
method: "POST",
headers: { "Authorization": "Bearer " + env.HEURIX_API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
rulepack: "mode",
items: [{
id: String(product.id),
name: product.title,
ref: product.variants?.[0]?.sku || "",
description: product.body_html?.replace(/<[^>]+>/g, "") || "",
stock: product.variants?.reduce((s, v) => s + (v.inventory_quantity || 0), 0) || 0,
handle: product.handle,
price: product.variants?.[0]?.price
}]
})
});
}
Pensez à vérifier la signature HMAC du webhook Shopify (X-Shopify-Hmac-Sha256) avant de traiter la charge utile, pour ne pas accepter de requêtes forgées.
Dans un module custom, on hook displaySearchResults ou on surcharge le contrôleur de recherche :
<?php
// modules/heurixsearch/heurixsearch.php
class HeurixSearch extends Module
{
public function search($query)
{
$ch = curl_init('https://api.heurix.fr/v1/index/' . Configuration::get('HEURIX_CATALOG') . '/search');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . Configuration::get('HEURIX_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['q' => $query, 'limit' => 24]),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
return $response['hits'] ?? [];
}
}
Dans le template de résultats (search.tpl), on boucle sur ces résultats à la place de la recherche native PrestaShop.
Trois hooks suffisent pour rester à jour en temps réel :
<?php
public function install()
{
return parent::install()
&& $this->registerHook('actionProductAdd')
&& $this->registerHook('actionProductUpdate')
&& $this->registerHook('actionProductDelete');
}
public function hookActionProductUpdate($params)
{
$product = $params['product'];
$this->pushToHeurix([
'id' => (string) $product->id,
'name' => $product->name,
'ref' => $product->reference,
'description' => strip_tags($product->description_short),
'stock' => (int) StockAvailable::getQuantityAvailableByProduct($product->id),
]);
}
private function pushToHeurix($item)
{
$ch = curl_init('https://api.heurix.fr/v1/index/' . Configuration::get('HEURIX_CATALOG') . '/items');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . Configuration::get('HEURIX_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['rulepack' => 'mode', 'items' => [$item]]),
]);
curl_exec($ch);
curl_close($ch);
}
wp_remote_post(), la fonction HTTP native de WordPress, plutôt que du cURL brut.
<?php
// Dans functions.php ou un plugin dédié
add_filter('woocommerce_product_query_meta_query', function ($meta_query, $query) {
if (!is_search()) return $meta_query;
$response = wp_remote_post('https://api.heurix.fr/v1/index/' . HEURIX_CATALOG . '/search', [
'headers' => [
'Authorization' => 'Bearer ' . HEURIX_API_KEY,
'Content-Type' => 'application/json',
],
'body' => json_encode(['q' => get_search_query(), 'limit' => 24]),
]);
$hits = json_decode(wp_remote_retrieve_body($response), true)['hits'] ?? [];
$ids = array_map(fn($h) => (int) $h['product']['id'], $hits);
// Réordonne les résultats WooCommerce natifs selon le classement Heurix
$query->set('post__in', $ids ?: [0]);
$query->set('orderby', 'post__in');
return $meta_query;
}, 10, 2);
Cette approche a un avantage : elle réutilise l'affichage natif WooCommerce (fiches produit, filtres existants), Heurix ne fait que reclasser les résultats — moins de template à réécrire.
<?php
add_action('woocommerce_update_product', 'heurix_sync_product');
add_action('woocommerce_new_product', 'heurix_sync_product');
function heurix_sync_product($product_id) {
$product = wc_get_product($product_id);
wp_remote_post('https://api.heurix.fr/v1/index/' . HEURIX_CATALOG . '/items', [
'headers' => [
'Authorization' => 'Bearer ' . HEURIX_API_KEY,
'Content-Type' => 'application/json',
],
'body' => json_encode([
'rulepack' => 'mode',
'items' => [[
'id' => (string) $product_id,
'name' => $product->get_name(),
'ref' => $product->get_sku(),
'description' => wp_strip_all_tags($product->get_short_description()),
'stock' => $product->get_stock_quantity() ?? 0,
]],
]),
]);
}
add_action('woocommerce_delete_product', function ($product_id) {
wp_remote_request('https://api.heurix.fr/v1/index/' . HEURIX_CATALOG . '/items/' . $product_id, [
'method' => 'DELETE',
'headers' => ['Authorization' => 'Bearer ' . HEURIX_API_KEY],
]);
});
Un module custom avec un plugin sur le bloc de résultats de recherche :
<?php
// app/code/Heurix/Search/Model/SearchClient.php
namespace Heurix\Search\Model;
use Magento\Framework\HTTP\Client\Curl;
class SearchClient
{
public function __construct(private Curl $curl) {}
public function search(string $query): array
{
$this->curl->addHeader('Authorization', 'Bearer ' . HEURIX_API_KEY);
$this->curl->addHeader('Content-Type', 'application/json');
$this->curl->post(
'https://api.heurix.fr/v1/index/' . HEURIX_CATALOG . '/search',
json_encode(['q' => $query, 'limit' => 24])
);
$data = json_decode($this->curl->getBody(), true);
return $data['hits'] ?? [];
}
}
Injectez ce service dans un plugin sur Magento\CatalogSearch\Model\Layer\Filter ou remplacez directement le template de résultats de recherche par un rendu basé sur ces données.
Un observer sur les événements standards du catalogue :
<?php
// app/code/Heurix/Search/Observer/SyncProduct.php
namespace Heurix\Search\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Framework\Event\Observer;
class SyncProduct implements ObserverInterface
{
public function __construct(private \Magento\Framework\HTTP\Client\Curl $curl) {}
public function execute(Observer $observer)
{
$product = $observer->getEvent()->getProduct();
$this->curl->addHeader('Authorization', 'Bearer ' . HEURIX_API_KEY);
$this->curl->addHeader('Content-Type', 'application/json');
$this->curl->post('https://api.heurix.fr/v1/index/' . HEURIX_CATALOG . '/items', json_encode([
'rulepack' => 'mode',
'items' => [[
'id' => (string) $product->getId(),
'name' => $product->getName(),
'ref' => $product->getSku(),
'stock' => (int) $product->getExtensionAttributes()?->getStockItem()?->getQty(),
]],
]));
}
}
Déclaré dans events.xml sur catalog_product_save_after et catalog_product_delete_after.
Ces guides sont un point de départ technique, pas un module clé en main. Si votre configuration sort de l'ordinaire, écrivez-nous.
Testez le Search API sur votre catalogue, sans carte bancaire.
Voir les tarifs