Aller au contenu principal

Thèmes & vitrines personnalisés

DZBuild livre 5 thèmes (Brico, Digital, Prestige, Showcase, Starter) et un customizer no-code. Mais si vous voulez un contrôle total — votre propre front-end React/Vue/Next.js/Flutter, votre design pixel-perfect, votre routing — l'API est faite pour vous.

Ce guide construit une vitrine headless de bout en bout :

  1. Rendu du catalogue (produits, catégories, variantes)
  2. Construction du panier (côté client ou serveur, à votre choix)
  3. Soumission de la commande via l'API
  4. Réception des webhooks au changement de statut

À la fin, votre vitrine custom sera 100% interopérable avec le tableau de bord marchand DZBuild — les commandes apparaissent, le stock décrémente, la livraison s'intègre avec les transporteurs du marchand, sans compromis.

Architecture

┌──────────────────────┐ ┌──────────────────────────┐
│ Vitrine custom │ HTTPS │ api.dzbuild.app/v1/* │
│ (React, Vue, Flutter)│ ──────► │ Authorization: Bearer … │
│ │ │ │
│ - Lit les produits │ ◄────── │ Réponses JSON │
│ - Affiche le panier │ │ │
│ - Soumet les commandes│ └──────────────────────────┘
└──────────────────────┘ │

Tableau de bord marchand DZBuild
- Confirme les commandes
- Gère le stock
- S'intègre aux transporteurs

Vous possédez l'UI. DZBuild possède la donnée et l'opérationnel. Le marchand se connecte à dzbuild.com/dashboard pour gérer les commandes confirmées dans votre UI custom.

Prérequis

  • Compte marchand DZBuild en plan Pro ou supérieur (Free n'inclut pas l'API — voir Plans).
  • Une clé API avec les bons scopes :
    • products:read — pour rendre le catalogue
    • orders:read — pour consulter les commandes (pages statut/tracking)
    • orders:write — pour créer les commandes depuis la vitrine
  • Un back-end (ou serverless / edge worker) qui détient la clé API. Ne livrez jamais le secret au navigateur — voir Sécurité.

Étape 0 — obtenir une clé

Dans le tableau de bord marchand :

Tableau de bord → Developer → API Keys → "+ Créer une clé plateforme"
• Nom : "Custom storefront — production"
• Scopes : products:read, orders:read, orders:write
• Sauvegarder

Copiez le secret (dzpk_live_…) — affiché une seule fois. Perdu ? Révoquez et créez-en un.

Test :

curl https://api.dzbuild.app/v1/whoami \
-H "Authorization: Bearer dzpk_live_..."
# attendu : { "data": { "key_id": "...", "store_id": 13, "type": "platform", "scopes": [...] } }

Étape 1 — rendre le catalogue

// pages/index.js (Next.js)
export async function getServerSideProps() {
const res = await fetch('https://api.dzbuild.app/v1/products?limit=50&status=active', {
headers: { 'Authorization': `Bearer ${process.env.DZ_KEY}` },
});
const { data } = await res.json();
return { props: { products: data.items } };
}

export default function Home({ products }) {
return (
<ul>
{products.map(p => (
<li key={p.id}>
<img src={`https://cdn.dzbuild.app/${p.store_id}/products/${p.primary_image}`} />
<h2>{p.name}</h2>
<p>{p.price} DZD</p>
<a href={`/product/${p.slug}`}>Voir</a>
</li>
))}
</ul>
);
}

Cachez la réponse — la liste des produits est mise en cache en périphérie pendant 30 s, donc les lectures répétées à grande échelle sont peu coûteuses.

Étape 2 — page produit avec variantes

const res = await fetch(`https://api.dzbuild.app/v1/products/${id}`, {
headers: { 'Authorization': `Bearer ${process.env.DZ_KEY}` },
});
const { data: product } = await res.json();

La réponse inclut variants[] — chaque entrée est un groupe de variantes avec ses options :

"variants": [
{ "id": 11, "name": "Color", "type": "color",
"options": [
{ "id": 14, "value": "Red", "color_code": "#ff0000", "image_id": 28, "stock": 12 },
{ "id": 15, "value": "Blue", "color_code": "#0000ff", "image_id": 29, "stock": 5 }
]
},
{ "id": 12, "name": "Size", "type": "text",
"options": [
{ "id": 16, "value": "S", "stock": 10 },
{ "id": 17, "value": "M", "stock": 10 },
{ "id": 18, "value": "L", "stock": 5 }
]
}
]

Affichez un sélecteur par groupe. Pour type: color, des swatches avec color_code ; type: text, des labels ; type: image_text, des thumbnails (le filename est sur options[].image_id — récupérez images[].url correspondant).

Gestion stock épuisé : options[].stock vaut null si le marchand ne suit pas le stock par variante. S'il est ≤ 0, grisez l'option. Le check définitif se fait côté serveur au confirm-time.

Étape 3 — construire un panier

Le panier est côté client (React state, Vuex, Pinia, localStorage…). Pas besoin d'appel API pour ajouter au panier. Chaque ligne :

{
product_id: 26,
product_name: "T-shirt",
base_price: 1500,
quantity: 1,
selected_variants: [
{ group_name: "Color", option_name: "Red", color_code: "#ff0000", price_adjustment: 0 },
{ group_name: "Size", option_name: "L", color_code: null, price_adjustment: 200 }
]
}

Calculez le total ligne côté client : (base_price + sum(price_adjustment)) × quantity. Affichez le total panier au client. (Le serveur recalcule à la soumission — ne faites jamais confiance au calcul client pour la facturation finale.)

Étape 4 — collecte des infos client + calcul livraison

Formulaire checkout standard : nom, téléphone, wilaya, commune, adresse. Utilisez la liste wilayas depuis GET /v1/store (à venir) ou hardcodez les 58 wilayas — liste stable.

Pour le coût de livraison, deux options :

  • Hardcodez les tarifs par wilaya + type de livraison dans la vitrine (le plus simple).
  • Lisez depuis la config marchand via GET /v1/store (prévu v1.1).

Passez le shipping calculé en shipping_cost. Le serveur ne recalcule pas le shipping pour vous actuellement.

Étape 5 — soumettre la commande

// Sur votre back-end (Next.js API route, Edge function, server…)
async function placeOrder(req, res) {
const cart = req.body;

const order = {
customer: {
name: cart.name,
phone: cart.phone,
email: cart.email || null,
wilaya_id: cart.wilaya_id,
commune: cart.commune,
address: cart.address || ''
},
delivery: {
type: cart.delivery_type,
desk_id: cart.desk_id || null,
desk_name: cart.desk_name || null
},
items: cart.items.map(line => ({
product_id: line.product_id,
quantity: line.quantity,
variants: line.selected_variants
})),
shipping_cost: cart.shipping_cost,
discount: 0,
payment_method: 'cod',
notes: cart.notes || null
};

const idempKey = req.headers['x-checkout-id'] || crypto.randomUUID();

const apiRes = await fetch('https://api.dzbuild.app/v1/orders', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.DZ_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': idempKey
},
body: JSON.stringify(order)
});

if (!apiRes.ok) {
const err = await apiRes.json();
return res.status(apiRes.status).json(err);
}
const { data: createdOrder } = await apiRes.json();
return res.status(201).json({
order_number: createdOrder.order_number,
total: createdOrder.amounts.total
});
}

Le client voit son order_number sur la page de succès. Le dashboard marchand affiche maintenant la nouvelle commande dans la liste pending, prête à confirmer.

Étape 6 — recevoir les webhooks (optionnel mais puissant)

Enregistrez un webhook pour réagir aux events de commande :

curl -X POST 'https://api.dzbuild.app/v1/webhooks' \
-H "Authorization: Bearer $DZ_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"url": "https://yourstorefront.com/api/dz-webhook",
"events": ["order.created", "order.confirmed", "order.shipped", "order.delivered"]
}'

Vous recevrez un secret dans la réponse — stockez-le. Chaque livraison webhook inclut un en-tête X-DZ-Signature à vérifier. Voir Vérifier les signatures.

Utilisez les webhooks pour :

  • Envoyer un SMS au client à la confirmation.
  • Mettre à jour CRM / Google Sheet / analytics.
  • Invalider une page « merci » après confirmation marchand.
  • Déclencher la livraison de produit téléchargeable après order.delivered.

Patterns courants

Vitrine multilingue

Stockez vos traductions en front. L'API renvoie les noms et descriptions tels que saisis par le marchand. Si le marchand utilise l'add-on Multi-language, GET /v1/products/{id} retournera (en v1.1) name_ar, name_fr, description_ar, description_fr à côté des champs canoniques. En attendant, seul le canonique est exposé — choisissez côté client ce que veut votre client.

Sélecteur stop-desk

Pour delivery.type = "desk" :

// 1. Lisez les stop desks du marchand pour la wilaya choisie
// (prévu v1.1 : GET /v1/store/desks?wilaya=16)
// En attendant, requêtez directement le transporteur (Yalidine, ZR, etc.)

// 2. Affichez la liste, le client choisit :
selectedDesk = { id: 7842, name: "Yalidine Bab Ezzouar" };

// 3. Passez à la commande :
order.delivery = {
type: "desk",
desk_id: selectedDesk.id,
desk_name: selectedDesk.name
};

Paiement en ligne (SlickPay / Edahabia)

Pour payment_method = "digital_payment" :

  1. Soumettez la commande via l'API normalement ; créée en pending avec payment_status = pending.
  2. Redirigez le client vers SlickPay / Edahabia checkout.
  3. Au succès, votre back-end reçoit un webhook SlickPay → appelez PATCH /v1/orders/{id} pour passer payment_status à paid (prévu v1.1 ; en attendant le paiement est mis à jour par le receveur webhook SlickPay existant du dashboard).

Retours & remboursements

Gérés actuellement dans le dashboard. Le support API pour POST /v1/orders/{id}/refund est sur la roadmap.

Sécurité

  • Ne mettez jamais la clé API dans du code exposé au navigateur. Les clés appartiennent à votre serveur / serverless / edge. Le navigateur appelle votre endpoint, votre endpoint appelle DZBuild.
  • HTTPS only entre vitrine et api.dzbuild.app. Le Worker rejette HTTP.
  • Idempotency-Key requis sur toutes les écritures. Sans = 400 idempotency_key_required.
  • Limites de taux par clé — pro 1500 req/min, pro_plus 5000, enterprise illimité. Voir Limites.
  • Une clé par environnement. Pas de partage dev/prod. Créez une clé staging séparée ; révoquez-la après staging.

Dépannage

ProblèmeCause probableFix
401 unauthorized "Missing Authorization header"En-tête Authorization: Bearer … oubliéAjoutez
401 unauthorized "Invalid API key"Typo, clé révoquée ou mauvais env varRe-créez via dashboard
403 forbidden "Missing scope: orders:write"La clé n'a pas le scopeRe-créez avec le scope
400 bad_request "Product N does not belong to this store"ID cross-store (clé store A, produit store B)Bonne clé
400 bad_request "items must be a non-empty array"Panier videNe soumettez pas vide
429 rate_limitedPolling trop agressifPassez aux webhooks ; ou upgradez

Apps de référence

Templates clonables :

  • dzbuild-storefront-nextjs — Next.js 14 + App Router + Tailwind. Liste produits, page produit avec variantes, panier, checkout.
  • dzbuild-storefront-flutter — App mobile Flutter sur la même API.

(Les deux sont des starter — adaptez librement.)

Mise en production

  1. Testez à fond avec une clé pilot sur un store de test.
  2. Créez une clé séparée prod (mêmes scopes, autre nom).
  3. Déployez avec la clé prod en env vars.
  4. Passez une vraie commande test ; confirmez sur dashboard.
  5. Testez un remboursement si vous en offrez.
  6. Enregistrez vos webhooks pointant sur l'URL prod.
  7. Surveillez GET /v1/usage chaque jour la première semaine.

Roadmap

FeatureETA
GET /v1/store avec rates wilaya + stop desksv1.1
GET /v1/products/{id}/combinations pour stock par combov1.1
POST /v1/orders/{id}/refund via APIv1.1
Upload image produit via R2 presigned URLv1.1
Champs multilingues sur réponses produitsv1.1

Besoin d'une de ces features plus tôt ? Contactez le support.