Genuka API — Guide d’intégration e-commerce
Public visé : développeurs front-end / full-stack qui construisent un site e-commerce
pour une entreprise déjà inscrite sur Genuka.
Version d’API :2023-11· Préfixe de toutes les routes :/2023-11
1. Objectif et périmètre
Ce guide décrit, endpoint par endpoint et payload par payload, comment bâtir une boutique en ligne au-dessus degenuka-api :
catalogue · variantes · stock · collections · panier · authentification client · adresses ·
réductions · livraison & retrait (pickup) · création de commande · paiement ·
suivi de livraison · documents de commande · multi-devise · multi-boutique ·
contenu éditorial (blogs, articles, pages CMS) · réservation de services (créneaux) ·
chat storefront · factures publiques.
Tous les faits ci-dessous ont été vérifiés directement dans le code routes/api/V202311/,
controllers app/Http/Controllers/V202311/, pipeline app/Processes/Order/, resources
app/Http/Resources/V202311/). Quand un comportement est calculé côté serveur, c’est signalé.
2. Concepts clés
2.1 Multi-tenant
Une requête storefront est toujours scopée à une entrepriseCompany), et de
préférence à une boutique Shop). Le scoping passe par des en-têtes HTTP, pas par
l’URL.
| En-tête | Obligatoire | Rôle |
|---|---|---|
| X-Company: <company_id> | Oui pour toutes les routes storefront / customer | Identifie l’entreprise. Middleware check.company. Absent → 422 { "message": "Please provide a Company Id" }. |
| X-ShopId: <shop_id> | Recommandé | Scope le catalogue, le stock, les prix et le checkout sur la boutique active. À défaut, l’API lit X-Shop. |
| Authorization: Bearer <token> | Oui pour les routes client connecté auth:customer) | Jeton OAuth (Passport) renvoyé par login / register (ou par POST /customers/orders avec authenticate). |
| Content-Language: fr | Optionnel | Langue des libellés / e-mails. |
Le middleware tenant.scope applique automatiquement les scopes entreprise/boutique sur
les modèles : vous ne « voyez » jamais les données d’une autre entreprise.
2.2 Le problème de l’amorçage (bootstrap)
X-Company est l’ID de l’entreprise — vous ne l’avez pas encore au tout premier appel.
Pour le récupérer à partir du nom de domaine de la boutique, utilisez la route racine
hors scope :
X-Company sur toutes les requêtes suivantes.
2.3 Entités centrales
Company— branding, devise par défaut, devises additionnelles, moyens de paiement, config storefrontShop— point de vente ; peut porter sa propre devise ; relié à des entrepôtsProduct/ProductVariant— un produit a une ou plusieurs variantes ; le prix et le stock vivent au niveau varianteStock(entrepôt) — relié aux boutiques viashop_warehouseCustomer,AddressOrder—billing(JSON),shipping(JSON),discounts,deliveries,payments,mediasShippingFee,PickupLocation,Delivery,DeliveryTrackingPaymentMethod,PaymentCollection(catalogue groupé, imbricable) ;Blog/Article/Page(CMS storefront)Availability/Unavailability/CalendarEvent(réservation de services par professionnel)InboxConversation/Message(chat storefront)
3. Parcours storefront de référence
- Résoudre l’entreprise
/company/from-domain) → fixerX-Company - Charger les boutiques, choisir une boutique active → fixer
X-ShopId - Lister les produits / variantes (filtres, pagination)
- Calculer la disponibilité à partir des champs
estimated_quantity*etfollow_stock - (Optionnel) Authentifier le client — le checkout invité est possible
- Gérer les adresses du client
- Vérifier un code promo / charger les promos automatiques
- Choisir livraison ou retrait
- Créer la commande
POST /customers/orders) - Récupérer le lien de paiement (immédiat ou différé)
- Suivre la commande et la livraison
Panier : Genuka ne stocke pas de panier côté serveur. Le panier est géré par le front
(local/session) jusqu’à la création de la commande.
4. Référence des endpoints
4.1 Contexte entreprise & boutique
| Méthode | Endpoint | Notes | |---|---|---| |GET | /2023-11/company/from-domain?domain=<domain> | Bootstrap, hors X-Company. Accepte aussi ?companyId=<id>. Réponse : CompanyResource public (en cache). |
| GET | /2023-11/companies/{company} | Entreprise par ID, hors scope. |
| GET | /2023-11/company?domain=<domain> | Variante scopée (nécessite X-Company). |
| GET | /2023-11/company/filters?companyId=<id> | Filtres storefront configurés storeFrontFilters). |
| GET | /2023-11/shops | Liste paginée des boutiques. |
| GET | /2023-11/shops/{shop} | Détail boutique. Variante : /shops/handle/{slug}. |
| GET | /2023-11/shops/count | Nombre de boutiques. |
GET /company/from-domainetGET /companies/{company}sont mis en cache 4 semaines
(cléspublic_company_domain_{domain}/public_company_id_{companyId}). C’est le point
d’amorçage : un seul appel par session suffit.
CompanyResource (public)* — champs exposés app/Http/Resources/V202311/Public/CompanyResource.php) :
| Champ | Type | Description |
|---|---|---|
| id | ULID | Identifiant entreprise (= X-Company). |
| name, description, type | string | Identité. |
| handle, company_code | string | Slug et code public. |
| logoUrl | string | URL directe du logo. |
| logo | objet média | Média complet microthumblargewebp), ou null. |
| storefront | objet | Config du thème actif ThemeConfiguration is_active=1), renvoyée telle quelle. |
| storeFrontFilters | objet | Filtres pré-calculés (voir ci-dessous). |
| paymentMethods[] | tableau | Moyens de paiement activés (voir ci-dessous). |
| currency | { code, name } | Devise par défaut. |
| currencies[] | { code, label, value } | Devises additionnelles + taux. |
| variables | objet | Variables custom metadata.variables). |
| website | objet domaine | Domaine principal { id, domain, main, is_active, … }). |
| hasBlogs | bool | L’entreprise publie-t-elle un blog. |
| contact | objet | Coordonnées publiques metadata.contact). |
| address | AddressResource | Adresse de l’entreprise. |
| business_id | string | RCCM metadata.rccm). |
| tax_id | string | Identifiant fiscal metadata.fiscalID). |
storeFrontFilters* (aussi via /company/filters) contient :
paymentMethods[]* PaymentMethodResource) — un par moyen activé :
processor∈cash,notchpay,stripe,paydunya,taramoney,pawapay,paypal,
mamoni,flutterwave(enumApp\Enum\PaymentMethods). Un type fonctionnel l’accompagne
côté config :card,mobile_money,bank_transferApp\Enum\PaymentMethodType).
Les valeurs secrètes desconfigurations(cléssecretprivatetoken) sont masquées en sortie.
Au checkout,billing.methodreprend la valeurprocessordu moyen choisi (§4.8).
ShopResource* — champs exposés /shops, /shops/{shop}) :
| Champ | Description |
|---|---|
| id, name, slug, description | Identité boutique. |
| currency_code, currency_name | Devise propre à la boutique (prioritaire sur l’entreprise). |
| address | AddressResource (avec latitudelongitude). |
| warehouses[] | Entrepôts rattachés { id, name, description }) — base du stock. |
| domains[] | Domaines de la boutique { domain, main, is_active }). |
| logoUrl, logo | Logo (URL + média). |
| metadata, company_id | — |
/shops accepte filter[search] (nom/description) et sort=name|currency_code|created_at|….
/shops/{shop} accepte aussi ?companyId= pour résoudre par couple company_idslug.
Route filtres : GET /2023-11/company/filters?companyId=<id> renvoie directement le bloc
storeFrontFilters (collections + bornes de prix + options) — pratique pour bâtir la facette
de recherche sans charger toute la CompanyResource.
Bonne pratique : fixez la boutique active très tôt et propagez X-ShopId partout
(catalogue, stock, checkout, suivi).
4.2 Catalogue produit
| Méthode | Endpoint | Notes | |---|---|---| |GET | /2023-11/products | Liste paginée. |
| GET | /2023-11/products/count | { "count": <int> }. |
| GET | /2023-11/products/{product} | Détail ProductDetailResource). |
| GET | /2023-11/products/handle/{handle} | Détail par slug (réponse en cache). |
| GET | /2023-11/products/{product}/similar?limit=5 | Produits similaires. |
| GET | /2023-11/products/handle/{handle}/similar | Similaires par slug. |
| GET | /2023-11/productVariants | Liste de variantes paginée. |
| GET | /2023-11/productVariants/{productVariant} | Détail variante. |
Pagination (standard Laravel + Spatie QueryBuilder) :
?per_page=20 (défaut 15, max 100) ou ?page[size]=20&page[number]=2.
La réponse liste est enveloppée : { "data": [...], "links": {...}, "meta": {...} }.
Filtres disponibles sur /products filter[<nom>]=...) :
| Filtre | Exemple | Effet |
|---|---|---|
| search | filter[search]=tshirt | Titre, handle, vendeur, titre/SKU/code-barres des variantes. |
| published | filter[published]=1 | Produits publiés. |
| type | filter[type]=physical | Type de produit. |
| has_stocks | filter[has_stocks]=1 | Uniquement les produits disponibles. |
| priceRange | filter[priceRange]=1000,5000 | Fourchette [min,max]. |
| collections | filter[collections]=id1,id2 | Par collection. |
| shops | filter[shops]=shop_id | Par boutique. |
| tags | filter[tags]=promo,ete | Par tags. |
| optionValues | filter[optionValues]=Rouge | Par valeur d’option. |
| created_at / updated_at | filter[created_at]=... | Plage de dates { start, end }. |
Includes ?include=...) :
variants, variants.stocks, variants.warehouseQuantities, collections, shops,
tags, options, supplier, service.professionals…
Tris ?sort=..., préfixe - pour décroissant) :
price, -created_at, -updated_at, title, has_stock_first, total_orders, stocks, tags.
4.3 Stock & disponibilité
Le prix et le stock vivent au niveau variante. Les resources variantes exposent :estimated_quantity— quantité estimée totale (toutes boutiques), ounullsi aucune variante ne suit le stockestimated_quantity_by_shop—{ shop_id: quantité }estimated_quantity_by_warehouse—{ warehouse_id: quantité }stocks— détail par entrepôt (filtré sur la boutique active siX-ShopId)
Le suivi de stock se règle par variante via la colonne *follow_stock**.
Si follow_stock = false, la variante est toujours considérée disponible, quelle que
soitestimated_quantity. Sifollow_stock = true, basez la disponibilité sur
Au niveau produit,estimated_quantity(globale ou par boutique selonX-ShopId).
ProductDetailResource expose un booléen follow_stock (vrai si
au moins une variante suit le stock) et remaining_stocks (restant par entrepôt pour la boutique).
⚠️ Le stock affiché côté front est indicatif. La disponibilité est **revérifiée au moment
de la création de commande** (voir §4.8). Ne vous fiez jamais au stock client comme source de vérité.
4.4 Authentification client
Routes publiquesX-Company requis, pas de token) :
| Méthode | Endpoint | Champs |
|---|---|---|
| POST | /2023-11/customers/register | password (requis) ; first_name ou last_name ; email ou phone (uniques par entreprise). |
| POST | /2023-11/customers/login | email ou phone + password. |
| POST | /2023-11/customers/logout | — |
| POST | /2023-11/customers/password/reset-link | email. Renvoie un token. |
| POST | /2023-11/customers/password/forgot | email. |
| POST | /2023-11/customers/password/reset | token, email, password (confirmé, min 8). |
Réponse register / login 200) :
token est un jeton OAuth (Passport). Placez-le ensuite en Authorization: Bearer <token>.
Mauvais identifiants → 401.
Connecter le client dès le checkout : vous pouvez aussi obtenir un token directement à la
création de commande, sans appelregisterloginséparé — voir le paramètreauthenticateen §4.8.
4.5 Profil & adresses (client connecté)
Routesauth:customer Bearer requis) :
| Méthode | Endpoint |
|---|---|
| GET | /2023-11/customers/profile |
| PUT | /2023-11/customers/profile |
| PUT | /2023-11/customers/profile/password current_password, new_password) |
| GET | /2023-11/customers/addresses |
| POST | /2023-11/customers/addresses |
| GET | /2023-11/customers/addresses/{address} |
| PUT | /2023-11/customers/addresses/{address} |
| DELETE | /2023-11/customers/addresses/{address} |
CustomerResource renvoie entre autres : addresses[], default_address, contacts[],
tags[], medias[], custom_fields.
4.6 Livraison & retrait
| Méthode | Endpoint | Notes | |---|---|---| |GET | /2023-11/shipping-fees | Frais de livraison disponibles (paginé ; filter[active], filter[search], filter[created_at], filter[updated_at] ; sort=name, orders_count). |
| GET | /2023-11/pickup-locations | Points de retrait (paginé ; filter[shop], filter[active], filter[search]). |
ShippingFeeResource* — champs :
typevaut toujoursfixed(enumShippingFeeType) : leamountest un forfait. Le
ciblage géographique se fait par rattachement à des entrepôts warehouse_ids), pas par des
zones tarifaires côté resource. Filtrez les frais pertinents selon la boutique / l’entrepôt active(s).
PickupLocationResource* — mêmes champs de base id, name, amount, active, shop,
warehouses, warehouse_ids) plus :
Les coordonnées GPS du point de retrait sont dansaddress.latitude/address.longitude
(utiles pour une carte). Les horaires d’ouverture, s’ils existent, vivent dans metadata /
address.metadata (pas de champ dédié).
Au checkout, présentez les deux options puis renseignez la commande (voir §4.8) :
- Livraison :
shipping.mode = "delivery"+shipping_fee_id - Retrait :
shipping.mode = "pickup"+pickup_location_id
shipping_fee_idetpickup_location_idsont mutuellement exclusifs. Le montant de
livraison est résolu côté serveur depuis le frais ou le point de retrait choisi ; à défaut,
il prend shipping.amount.
4.7 Réductions
| Méthode | Endpoint | Notes | |---|---|---| |GET | /2023-11/discounts?code=WELCOME10 | Vérifie un code. 200 + DiscountResource, ou 404 si invalide. |
| POST | /2023-11/discounts/automatic | Promos automatiques applicables. Body : { "customer": { "id" } } ou { "customer": { "email", "phone" } }. |
Au moment de la commande, transmettez les remises validées dans discounts[] (voir §4.8).
Deux niveaux existent : order-level discounts[]) et product-level
products[].discounts[]). Le moteur applique et persiste les montants.
4.8 Création de commande
- Fonctionne en checkout invité (l’objet
customercrée/retrouve le client) ou connectéBearer→ le client authentifié est utilisé). - Pilotée par le pipeline
UpsertOrderProcess(résolution boutique → client → produits → remises → taxes → livraison → facturation → écriture → médias → notifications).
⚠️ Les totaux sont calculés par le serveur
ManageBillingTask recalcule subtotal, discount, out_of_tax_total,
sum_positive_taxes, sum_negative_taxes, total et net_to_pay à partir des
products, du shipping et des discounts. N’envoyez pas ces montants : ils seraient
écrasés. Côté client, vous fournissez les lignes, le mode de paiement, le mode de
livraison et les adresses — c’est tout.
Formule appliquée :
Payload recommandé (minimal et correct)
Dans cet exemple : 2 × 15 000 = 30 000 de produits + le montant du shipping_fee_id.
LeNotes sur les champs :totalet lenet_to_payrenvoyés intègrent ces frais — pas besoin de les calculer.
customer.force = true: crée le client même si un email/téléphone proche existe (utile en checkout invité). Àfalse, un conflit lève une erreur.billing.method:cash,bank,stripe,paydunya,pawapay,paypal,mamoni,taramoney…shipping.mode:delivery,pickup,postal,online,other.address_id(existante) ouaddress(objet{ first_name, last_name, line1, city, country, … }) pour billing/shipping.- Devise résolue dans l’ordre :
currency_code(requête) → boutique → entreprise →XAF. authenticate(booléen) : sitrue, un token OAuth est émis pour le client de la commande et renvoyé dans la réponse (voir « Connecter le client au checkout » ci-dessous). Pratique pour authentifier automatiquement un acheteur en checkout invité.
Livrer à un tiers (cadeau, destinataire ≠ acheteur)
L’adresse de livraisonshipping.address) est indépendante de l’acheteur : elle porte
ses propres first_name, last_name, email, phone. Pour expédier à quelqu’un d’autre,
laissez l’acheteur dans customer (et billing) et décrivez le destinataire dans
shipping.address :
ManageShippingTask) :
line1,city,countrysont obligatoires dansshipping.address, sinon400.- Mettez
"is_new": true* : sinon, pour un client connecté, le serveur réutilise une adresse existante qui matcheline1citycountry.is_newforce la création d’une nouvelle adresse de livraison. - N’utilisez pas un
shipping.address_idexistant de l’acheteur pour livrer ailleurs : passer unaddress_idavec un objetaddressécrase cette adresse. Pour un tiers, envoyez toujours un objetaddressneuf. - L’adresse est enregistrée avec
is_shipping = true,is_billing = false. Comme un nom de destinataire est fourni, elle ne devient pas l’adresse primaire de l’acheteuris_primaryrestefalse) — ses propres adresses restent intactes. - En retrait
mode = "pickup"), aucune adresse n’est requise : seulpickup_location_idcompte. Indiquez le destinataire tiers viametadata.note.
Réponse
200 + OrderDetailResource, avec un champ additionnel payment_link :
payment_link* est renseigné automatiquement quanddeferred_paymentest absent ou
false(paiement immédiat). Sideferred_payment = true, il vautnullet vous générez
le lien plus tard (§4.9).
Connecter le client au checkout authenticate)
Par défaut, POST /customers/orders ne renvoie pas de token : seuls register et login
en émettent. Pour authentifier automatiquement l’acheteur juste après le checkout (y compris en
checkout invité, où le client n’a pas de mot de passe), ajoutez "authenticate": true au payload.
La réponse contient alors un token OAuth et la ressource customer, en plus de data :
token en Authorization: Bearer <token> pour accéder au profil, aux
adresses et à l’historique de commandes du client — sans appel registerlogin séparé.
Le token est émis pour le client de la commande (celui résolu/créé par le pipeline). En
checkout connecté (requête déjà porteuse d’unBearer),authenticaterenvoie simplement un
nouveau token pour ce même client.
Autres routes commande
| Méthode | Endpoint | Auth | Notes | |---|---|---|---| |GET | /2023-11/customers/orders/{orderId} | publique X-Company) | Détail d’une commande. |
| GET | /2023-11/customers/orders | Bearer | Commandes du client connecté (paginé). |
| PUT | /2023-11/customers/orders/{order} | Bearer | Mise à jour (même pipeline). |
4.9 Paiement
Deux modes : A. Paiement immédiat —deferred_payment absentfalse à la création. Le payment_link
est renvoyé directement dans la réponse de POST /customers/orders. Redirigez le client dessus.
B. Paiement différé — deferred_payment = true, puis :
amount_dueoptionnel (défaut :order.amount_due).success_url/cancel_url(alias :callback).- Réponse :
200 { "url": "https://..." }, ou400 { "message": "Payment link could not be generated" }.
billing.method = processor du moyen, §4.1) :
Stripe, Paydunya, Pawapay, PayPal, Mamoni, Taramoney, Flutterwave, NotchPay (+ cash hors ligne).
Les webhooks de chaque provider mettent à jour le statut de paiement de la commande de façon asynchrone.
Endpoints provider directs POST /2023-11/{stripe|paydunya|pawapay|mamoni|paypal|taramoney}/checkout)
existent mais sont des détails d’implémentation : passez parpayment_link//payments.
4.10 Suivi de livraison
OrderDetailResource inclut delivery (la première) et deliveries[].
| Méthode | Endpoint | Notes |
|---|---|---|
| GET | /2023-11/delivery/{delivery} | Détail livraison. |
| GET | /2023-11/deliveries | Liste paginée (filtres tracking_number, order_id, status, mode, scheduled_at, search…). |
| GET | /2023-11/delivery/{delivery}/last_position | Dernière position connue. |
DeliveryResource* — champs principaux :
status∈idle,pending,taken_in_charge,partial_delivery,delivered,cancelled
(enum App\Enum\DeliveryStatus). Une commande peut être livrée en plusieurs fois :
deliveries[]porte chaque tournée, etdelivery_statusindique l’avancement (quantité livrée,
séquence, livraison partielle).Position — réponse de
/last_position (snapshot DeliveryTracking, pas un flux temps réel) :
Si aucune position n’a encore été enregistrée : { "message": "Aucune position disponible" }.
Pour un suivi « live », faites du polling sur cet endpoint. Affichez tracking_number,
tracking_urlet laposition(lat/lng +headingpour orienter une icône sur la carte).
4.11 Documents de commande (médias)
La tâcheManageMediasTask lit le tableau medias[] (ou son alias images[]) à la
création et à la mise à jour. Formats acceptés (clés exactes) :
{ "id": "..." }lors d’unPUTconserve un média déjà attaché (les médias absents du tableau sont supprimés).- Lecture :
OrderDetailResourceexposemedias(publics) etprivate_medias(signature/privés).
4.12 Collections (catalogue groupé)
Les collections regroupent des produits (univers, catégories, sélections) et peuvent être imbriquéesparent_collection_id + sub_collections). Elles servent à bâtir la navigation et
les pages de catégorie. Le filtre filter[collections]=id1,id2 de /products (§4.2) s’appuie dessus.
| Méthode | Endpoint | Notes |
|---|---|---|
| GET | /2023-11/collections | Liste paginée. |
| GET | /2023-11/collections/count | Nombre de collections. |
| GET | /2023-11/collections/{collection} | Détail CollectionMinimalModel). |
| GET | /2023-11/collections/handle/{handle} | Détail par slug. |
| GET | /2023-11/collections/{collection}/products | Produits de la collection ProductDetailResource). |
| GET | /2023-11/collections/handle/{handle}/products | Idem par slug. |
Filtres filter[…]) : title, handle, content (partiels), search, withProducts
(collections non vides uniquement), updated_at (plage { start, end }).
Includes : tags, productsCount, subCollections, products.
Tris : title, handle, products_count, sub_collections_count, created_at, updated_at.
Champs CollectionMinimalModel) : id, title, handle, content, parent_collection_id,
metadata, medias[], et products[] (uniquement si ?include=products).
/collections/{id}/productsrenvoie desProductDetailResourcecomplets (prix, variantes,
stock, médias) — c’est l’endpoint à câbler sur une page catégorie. Pour ne lister que les
collections ayant du stock à afficher, ajoutez filter[withProducts]=1.
4.13 Contenu éditorial — Blogs, Articles, Pages (CMS)
Genuka expose un CMS léger pour le storefront : un blog contient des articles ; les pages sont des contenus statiques (À propos, CGV, FAQ…).CompanyResource.hasBlogs (§4.1)
indique si un blog existe.
| Méthode | Endpoint | Notes |
|---|---|---|
| GET | /2023-11/blogs | Liste des blogs filter[search], include=articles, sort=title). |
| GET | /2023-11/blogs/{blog} · /blogs/handle/{handle} | Détail blog. |
| GET | /2023-11/articles | Liste d’articles (paginée). |
| GET | /2023-11/articles/count | Nombre d’articles. |
| GET | /2023-11/articles/{article} · /articles/handle/{handle} | Détail article. |
| GET | /2023-11/pages | Liste des pages CMS. |
| GET | /2023-11/pages/count | Nombre de pages. |
| GET | /2023-11/pages/{page} · /pages/handle/{handle} | Détail page. |
Articles — filtres : published, blog_id, blog_handle, created_at (plage), search
(titre, handle, auteur, description, contenu, blog, tags). Includes : tags, blog.
Tris : title, handle, published, vendor, created_at, updated_at (défaut title).
Champs ArticleResource) : id, title, handle, description, content, author,
published, blog_id, blog (objet), metadata, medias[], created_at, updated_at.
Blogs — filtre search (titre/handle + articles). Include articles. Champs BlogResource) :
id, title, handle, metadata, medias[], created_at, updated_at.
Pages — filtre search (titre/handle). Champs PageResource) : id, title, handle,
content, metadata, medias[], created_at, updated_at.
SEO / rendu : utilisez toujours les routes handle/{slug} pour des URLs propres
/blog/mon-article,/p/a-propos).contentest du HTML/markdown éditorial à rendre tel quel ;
medias[]fournit l’image de couverture (champsmicrothumblargewebp).
4.14 Réservation de services — disponibilités & créneaux
Pour les entreprises de services (prise de rendez-vous), un produit de type service est lié à des professionnelsUser) qui ont des disponibilités récurrentes et des indisponibilités
(jours bloqués). Le front affiche un calendrier puis des créneaux réservables.
| Méthode | Endpoint | Notes |
|---|---|---|
| GET | /2023-11/availabilities?user_id=<id> | Horaires récurrents du professionnel. |
| GET | /2023-11/availabilities/slots?user_id=<id>&service_id=<id>&day=<date> | Créneaux libres calculés pour une date. |
| GET | /2023-11/availabilities/{availability} | Détail d’une plage récurrente. |
| GET | /2023-11/unavailabilities?user_id=<id> | Jours d’indisponibilité (filtres start_date, end_date). |
| GET | /2023-11/unavailabilities/dates?user_id=<id> | Tableau de dates ["2026-05-23", …] pour griser un calendrier. |
| GET | /2023-11/unavailabilities/{unavailability} | Détail. |
availabilities* — champs : user_id, day_of_week (0=dimanche → 6), start_time,
end_time H:i:s), timezone, metadata.
availabilities/slots* — paramètres requis service_id et day (date), user_id optionnel
(défaut = utilisateur authentifié). Réponse calculée côté serveur :
Le moteur part des plages availabilities du jour, découpe en créneaux de
service.duration_minutesespacés de+ buffer_minutes, exclut les rendez-vous déjà pris
CalendarEvent), les créneaux passés et ceux qui débordent. Si le jour est marqué indisponible,
Parcours de réservation conseillé :unavailable = trueetslots = [].
unavailabilities/dates→ griser les jours bloqués du calendrier.- À la sélection d’un jour :
availabilities/slots→ afficher les créneauxavailable: true. - Le client choisit un créneau, puis vous créez la commande (§4.8) en plaçant l’heure du RDV
dans
shipping.scheduled_at(et/oumetadata).
4.15 Chat storefront (messagerie temps réel)
Genuka fournit un webchat embarquable : le visiteur discute avec l’entreprise depuis la boutique. Ces routes ne nécessitent pasX-Company (l’entreprise est déduite du **jeton de
conversation**), mais un middleware validate.chat.token et un throttle:chat.
| Méthode | Endpoint | Notes |
|---|---|---|
| GET | /2023-11/inbox/chat/{conversation}/messages | Messages (cursor-paginés, per_page défaut 50). Les messages internes ne sont jamais renvoyés. |
| POST | /2023-11/inbox/chat/{conversation}/messages | Envoyer un message. |
| POST | /2023-11/inbox/chat/{conversation}/typing | Indicateur « en train d’écrire ». |
| POST | /2023-11/inbox/chat/{conversation}/read | Marquer comme lu. |
| POST | /2023-11/inbox/chat/{conversation}/request-unlock | Demande de déblocage (OTP) — sans token. |
Envoi d’un message :
typerequis ;content.bodyrequis sitype=text(max 5000 caractères). Les types internes/système sont refusés403).- Réponse : la ressource
MessageResourcecréée. - La liste de messages renvoie aussi un
return_linkvers le canal d’origine (WhatsApp, Messenger, Instagram, Telegram) quand la conversation y est rattachée.
Ce module est optionnel pour une boutique : utile pour le support / la conversion. Le jeton de
conversation est émis par le widget de chat lors de l’ouverture d’une session.
4.16 Factures & documents publics
| Méthode | Endpoint | Notes | |---|---|---| |GET | /2023-11/invoices/{orderId} | Affiche la facture liée à une commande. |
| GET | /2023-11/invoices/{orderId}/invoice | Version imprimable (PDF/HTML). |
Pratique pour proposer un lien « Télécharger ma facture » sur la page de confirmation / suivi de
commande, sans authentification (lorderId fait office de jeton d’accès — ne l’exposez pas
publiquement au-delà du client concerné).
4.17 Plans (hors boutique)
| Méthode | Endpoint | Notes | |---|---|---| |GET | /2023-11/plans | Liste des plans d’abonnement Genuka. |
| GET | /2023-11/plans/{lookup_key} | Détail d’un plan. |
Ces routes concernent l’abonnement de l’entreprise à Genuka, pas le storefront. À ignorer
pour un site e-commerce classique ; documentées ici par exhaustivité (routes publiques).
5. Multi-devise
CompanyResourceexposecurrency(devise par défaut) etcurrencies[](additionnelles).- Chaque
Shoppeut porter sa proprecurrency_code/currency_name. - Devise d’une commande résolue :
currency_code(requête) → boutique → entreprise →XAF.
- Afficher la devise active (boutique avant entreprise).
- Figer les prix transactionnels dans la devise de la commande.
- Ne jamais reconvertir a posteriori une commande déjà créée.
6. Multi-boutique
- Liste des boutiques publiques via
/shops. - Scope via
X-ShopIdsur catalogue, stock, prix, checkout, suivi. - Relations boutiques ↔ entrepôts pour le stock.
- Imposer le choix d’une boutique au début du parcours.
- Propager
X-ShopIdpartout. - Isoler le panier par boutique (devise, stock et prix diffèrent).
7. Conventions & gestion des erreurs
| Code | Signification | Forme | |---|---|---| |200 | Succès | data ou objet métier |
| 401 | Non authentifié (token manquant/invalide) | { "message": "..." } |
| 404 | Ressource introuvable | { "message": "..." } |
| 422 | Validation / X-Company manquant | { "message": "...", "errors": { } } |
| 400 | Erreur métier (stock, doublon client, lien de paiement…) | { "message": "..." } |
- Pagination : enveloppe Laravel
{ data, links, meta }. Contrôle viaper_pageoupage[size]. - Filtres/tris/includes : convention Spatie QueryBuilder
filter[x],sort,include). - Idempotence : l’API détecte les doublons
CheckDuplicateOrderTask), mais protégez aussi le bouton de paiement côté front (désactivation + clé d’idempotence applicative).
8. Checklist e-commerce (à ne pas oublier)
- Panier persistant (invité + connecté) et fusion à la connexion — géré côté front.
- Idempotence du
POST /customers/orders(anti double-commande). - Revérification serveur des prix/stock : ne jamais faire confiance au front (déjà appliqué par le pipeline).
- Taxes complètes (produits, livraison, remises) — calculées côté serveur, à afficher fidèlement.
- E-mails transactionnels : confirmation, paiement, expédition, échec.
- Webhooks / polling pour synchroniser statut commande/paiement/livraison.
- Remboursements, retours, notes de crédit
returns,credit_notesdansOrderDetailResource). - SEO catalogue (slug
handle, métadonnées, schema.org). - Conformité RGPD/PII (rétention, droit à l’effacement).
- Observabilité (correlation id dans les logs, alertes paiement/webhook).
- Anti-abus du checkout (rate limit, captcha selon le risque).
- Tests E2E du parcours complet (catalogue → panier → paiement → suivi).
9. Roadmap d’implémentation conseillée
| Phase | Contenu | |---|---| | 1 | Contexte entreprise + boutique · liste/détail produits + variantes · panier local | | 2 | Auth client + adresses · checkout (livraison/retrait + remise) · création de commande | | 3 | Lien de paiement + callbacks providers · pages suivi commande/livraison · documents | | 4 | Multi-devise · multi-boutique · durcissement (idempotence, sécurité, monitoring) |⚠️ La sécurité, l’idempotence et le monitoring (phase 4) ne sont pas optionnels : intégrez-les
au plus tôt plutôt que de les repousser en fin de projet.
