Support tickets
Reseller support: tickets and after-sales (SAV) claims with their conversation and resolution, support contacts and FAQ, and the regional WhatsApp number resolved from the account or the device location.
The « Assistance » area of the reseller app (Imad, MRA-08) and the support pages of the reseller web (Anis, WEB-05); built in SRV-24. The back-office side is support and communication.
- Tickets carry a type, a subject, a conversation and attachments, optionally linked to one of my orders (SUP-01). Only public messages reach the reseller; internal notes never do.
- After-sales claims are tickets of type
AFTER_SALES(V1 — SUP-04): the order, the claimed lines with quantities and the issue (AfterSalesIssue), photos, within the after-sales window. The back-office diagnoses and applies a resolution —REPLACEMENT(a free order),RETURN(a return request),CREDIT_NOTE,REPAIRorREJECTEDwith a reason — which is shown on the ticket (ticket lifecycle). - Contacts and WhatsApp.
support.contactsgives the phone, e-mail, hours and FAQ (SUP-03);support.whatsappgives the WhatsApp number of the caller's support region — from the account's wilaya when signed in, else from the device location or a chosen wilaya — for the fixed WhatsApp button of both reseller apps (SUP-05, ADM-08). It works for guests.
| Method | Path | Operation | Access | Apps | Release |
|---|---|---|---|---|---|
| GET | /tickets | tickets.listMy support tickets | self | RA · RW | V1 |
| POST | /tickets | tickets.createOpen a ticket (type, subject, message, related order, attachments) | selfIdempotency-Key | RA · RW | V1 |
| GET | /tickets/{ticketId} | tickets.getTicket with its public messages | self | RA · RW | V1 |
| POST | /tickets/{ticketId}/messages | tickets.replyReply to a ticket | selfIdempotency-Key | RA · RW | V1 |
| POST | /tickets/{ticketId}/close | tickets.closeClose my ticket (optionally rate the answer) | self | RA · RW | V1 |
| GET | /support/contacts | support.contactsSupport phone, hours and FAQ entries | session | RA · RW | V1 |
| GET | /support/whatsapp | support.whatsappWhatsApp support number of the caller's region: declared region when signed in, else from lat/lng or wilayaCode; returns number, wa.me link and hours | guest or session | RA · RW | V1 |
Every operation can also answer 426 UPGRADE_REQUIRED, 429 RATE_LIMITED, 500 INTERNAL_ERROR and 503 MAINTENANCE; session operations also 401 UNAUTHENTICATED, 403 ACCOUNT_SUSPENDED and 403 PERMISSION_DENIED (caller is not a reseller); Idempotency-Key required operations also 400 IDEMPOTENCY_KEY_REQUIRED, 409 IDEMPOTENCY_KEY_REUSED, 409 IDEMPOTENCY_IN_PROGRESS. See the error registry.
Ticket types and what they require
TicketType | Label | orderId | Extra requirements |
|---|---|---|---|
AFTER_SALES | Service après-vente (SAV) | required, order DELIVERED | items (lines, quantities, AfterSalesIssue), at least one photo, within the after-sales window (BR-81: afterSales.windowDays after delivery, or the product warranty — D-35) |
ORDER_ISSUE | Problème de commande | required | — |
DELIVERY_ISSUE | Problème de livraison | required | — |
BILLING | Facturation | optional | — |
QUESTION | Question | optional | — |
OTHER | Autre | optional | — |
Return or claim? A wrong, damaged or defective item noticed within returns.windowDays (7 days) of delivery can be returned directly (returns.create); afterwards — or when the reseller wants a replacement or a repair — it is an after-sales claim. The order detail tells the app which buttons to show (REQUEST_RETURN, OPEN_CLAIM in allowedActions).
Schemas
interface TicketSummary {
id: Uuid;
number: string; // TKT-2026-000046
type: TicketType;
subject: string;
status: TicketStatus; // OPEN, IN_PROGRESS, WAITING_RESELLER, RESOLVED, REJECTED, CLOSED
order: { id: Uuid; number: string } | null;
afterSalesResolution: AfterSalesResolution | null;
lastMessageAt: Iso;
lastMessagePreview: string; // first 120 characters
unreadCount: number; // support messages I have not read
rating: { score: number; comment: string | null; ratedAt: Iso } | null;
createdAt: Iso;
resolvedAt: Iso | null;
closedAt: Iso | null;
}
interface TicketDetail extends TicketSummary {
messages: TicketMessage[]; // public messages, oldest first
afterSales: AfterSalesClaim | null; // AFTER_SALES only
allowedActions: ("REPLY" | "CLOSE" | "RATE")[];
}
interface TicketMessage {
id: Uuid;
author: { type: "RESELLER" | "SUPPORT" | "SYSTEM"; displayName: string }; // support agents: first name + « Service client »
body: string;
attachments: FileRef[]; // TICKET_ATTACHMENT, signed URLs
createdAt: Iso;
}
interface AfterSalesClaim {
order: { id: Uuid; number: string; deliveredAt: Iso };
items: {
id: Uuid; // TicketItem id
orderItemId: Uuid;
product: { id: Uuid; name: string; imageUrl: string | null };
variant: { id: Uuid; sku: string; label: string };
qty: number;
issue: AfterSalesIssue; // DEFECTIVE, DAMAGED, WRONG_ITEM, MISSING_ITEM, NOT_AS_DESCRIBED, OTHER
}[];
claimableUntil: Iso;
resolution: {
type: AfterSalesResolution; // REPLACEMENT, RETURN, CREDIT_NOTE, REPAIR, REJECTED
note: string | null; // explanation written for the reseller
decidedAt: Iso;
replacementOrder: { id: Uuid; number: string; status: OrderStatus } | null;
return: { id: Uuid; number: string; status: ReturnStatus } | null;
creditNote: { id: Uuid; number: string; amountTtc: Money } | null;
repair: { expectedOn: string | null } | null; // YYYY-MM-DD
rejectionReason: string | null;
} | null;
}Operations
GET /tickets
tickets.list · Access session — reseller (self) · Apps RA, RW · Idempotency-Key — · Release V1 · Requirements SUP-01
Purpose. « Assistance »: my tickets and claims, the most recently active first.
Request
| Query | Type | Required | Notes |
|---|---|---|---|
status | TicketStatus, repeatable | no | Tab En cours = OPEN, IN_PROGRESS, WAITING_RESELLER |
type | TicketType, repeatable | no | |
orderId | Uuid | no | Tickets of one order |
cursor, limit | — | no | limit default 20, max 100 |
Response — 200 OK: { items: TicketSummary[], nextCursor }.
{
"items": [
{
"id": "01a1a12c-56c0-7989-951b-4022807290b6",
"number": "TKT-2026-000046",
"type": "AFTER_SALES",
"subject": "BOROFONE BW06 — boîtier de charge défectueux",
"status": "RESOLVED",
"order": { "id": "01a1065b-2900-748c-88c5-976d443033f4", "number": "CMD-2026-000131" },
"afterSalesResolution": "REPLACEMENT",
"lastMessageAt": "2026-11-05T09:40:00Z",
"lastMessagePreview": "Réclamation résolue : remplacement. La commande CMD-2026-000163 a été créée pour vous.",
"unreadCount": 1,
"rating": null,
"createdAt": "2026-11-03T11:30:00Z",
"resolvedAt": "2026-11-05T09:40:00Z",
"closedAt": null
},
{
"id": "01a1965f-7240-7a21-a8d4-11ef017fd953",
"number": "TKT-2026-000045",
"type": "DELIVERY_ISSUE",
"subject": "Commande CMD-2026-000155 pas encore reçue",
"status": "WAITING_RESELLER",
"order": { "id": "01a187cf-6b40-7c6f-894d-b1f256947da9", "number": "CMD-2026-000155" },
"afterSalesResolution": null,
"lastMessageAt": "2026-11-03T08:55:00Z",
"lastMessagePreview": "Le transporteur indique un passage hier à 16h. Pouvez-vous nous confirmer si le magasin était ouvert ?",
"unreadCount": 1,
"rating": null,
"createdAt": "2026-11-01T09:10:00Z",
"resolvedAt": null,
"closedAt": null
},
{
"id": "01a15ec2-ff00-764a-b2a4-ffdae7dba695",
"number": "TKT-2026-000041",
"type": "BILLING",
"subject": "Échéance de la facture FAC-2026-000079",
"status": "CLOSED",
"order": null,
"afterSalesResolution": null,
"lastMessageAt": "2026-10-22T09:15:00Z",
"lastMessagePreview": "L'échéance reste au 28 octobre ; vous pouvez régler en deux fois.",
"unreadCount": 0,
"rating": { "score": 5, "comment": "Réponse rapide, merci.", "ratedAt": "2026-10-22T10:02:00Z" },
"createdAt": "2026-10-21T14:00:00Z",
"resolvedAt": "2026-10-22T09:15:00Z",
"closedAt": "2026-10-22T10:02:00Z"
}
],
"nextCursor": null
}Errors — 400 VALIDATION_FAILED for unknown filter values.
Rules & side effects — read-only, sorted by lastMessageAt descending. The SUPPORT quick tile of the home shows the number of tickets in WAITING_RESELLER. Live: ticket.replied and ticket.resolved on events.stream update the list.
Used by — RA-32 (« Assistance »), RW-14, claims block of RA-22.
POST /tickets
tickets.create · Access session — reseller (self) · Apps RA, RW · Idempotency-Key required · Release V1 · Requirements SUP-01, SUP-04
Purpose. « Nouvelle demande »: opens a ticket — a question, an order or delivery problem, a billing question, or an after-sales claim on delivered items.
Request — attachments are uploaded first with purpose TICKET_ATTACHMENT (files).
interface CreateTicketRequest {
type: TicketType;
subject: string; // 5–120 characters
message: string; // 1–2 000 characters
orderId?: Uuid; // required for AFTER_SALES, ORDER_ISSUE, DELIVERY_ISSUE
attachmentFileIds?: Uuid[]; // 0–5; AFTER_SALES: at least 1 photo
items?: { // AFTER_SALES only, 1–50 entries
orderItemId: Uuid; // OrderDetail.lines[].id
qty: number; // ≤ delivered − already claimed − already returned
issue: AfterSalesIssue;
}[];
}{
"type": "AFTER_SALES",
"subject": "BOROFONE BW06 — boîtier de charge défectueux",
"message": "Trois paires BW06 de la commande CMD-2026-000131 : le boîtier ne charge plus les écouteurs après une semaine. Clients revenus au magasin.",
"orderId": "01a1065b-2900-748c-88c5-976d443033f4",
"attachmentFileIds": ["01a1a12a-8200-79a8-93c8-c5b89aac7ecf"],
"items": [
{ "orderItemId": "01a1065b-2900-7a11-9b22-3c4d5e6f7a81", "qty": 3, "issue": "DEFECTIVE" }
]
}Response — 201 Created, Location: /v1/tickets/01a1a12c-56c0-7989-951b-4022807290b6: the TicketDetail, status OPEN.
{
"id": "01a1a12c-56c0-7989-951b-4022807290b6",
"number": "TKT-2026-000046",
"type": "AFTER_SALES",
"subject": "BOROFONE BW06 — boîtier de charge défectueux",
"status": "OPEN",
"order": { "id": "01a1065b-2900-748c-88c5-976d443033f4", "number": "CMD-2026-000131" },
"afterSalesResolution": null,
"lastMessageAt": "2026-11-03T11:30:00Z",
"lastMessagePreview": "Trois paires BW06 de la commande CMD-2026-000131 : le boîtier ne charge plus les écouteurs après une semaine.",
"unreadCount": 0,
"rating": null,
"createdAt": "2026-11-03T11:30:00Z",
"resolvedAt": null,
"closedAt": null,
"messages": [
{
"id": "01a1a12c-56c0-7090-99d0-d51e9b32f39a",
"author": { "type": "RESELLER", "displayName": "Mohamed Benali" },
"body": "Trois paires BW06 de la commande CMD-2026-000131 : le boîtier ne charge plus les écouteurs après une semaine. Clients revenus au magasin.",
"attachments": [
{ "id": "01a1a12a-8200-79a8-93c8-c5b89aac7ecf", "url": "https://storage.hoco.dz/hoco-private/ticket_attachment/2026/11/01a1a12a-8200-79a8-93c8-c5b89aac7ecf.webp?X-Amz-Expires=300&X-Amz-Signature=2a9f…", "thumbnailUrl": "https://storage.hoco.dz/hoco-private/ticket_attachment/2026/11/01a1a12a-8200-79a8-93c8-c5b89aac7ecf-thumb.webp?X-Amz-Expires=300&X-Amz-Signature=7c3e…", "mimeType": "image/webp", "size": 236114, "purpose": "TICKET_ATTACHMENT" }
],
"createdAt": "2026-11-03T11:30:00Z"
}
],
"afterSales": {
"order": { "id": "01a1065b-2900-748c-88c5-976d443033f4", "number": "CMD-2026-000131", "deliveredAt": "2026-10-08T10:20:00Z" },
"items": [
{
"id": "01a1a12c-56c0-7b11-8c22-4d5e6f708192",
"orderItemId": "01a1065b-2900-7a11-9b22-3c4d5e6f7a81",
"product": { "id": "01a0901f-d380-7a88-9746-4ec8936c3d68", "name": "BOROFONE BW06 Écouteurs TWS", "imageUrl": "https://cdn.hoco.dz/products/bw06/bw06-white-1-240.webp" },
"variant": { "id": "01a0901f-d768-77a1-94f2-1bca2251bdcd", "sku": "BW06_WHITE", "label": "Blanc" },
"qty": 3,
"issue": "DEFECTIVE"
}
],
"claimableUntil": "2026-11-07T10:20:00Z",
"resolution": null
},
"allowedActions": ["REPLY", "CLOSE"]
}Errors
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_FAILED | Field codes: REQUIRED (orderId for the types that need it, items for AFTER_SALES), PHOTO_REQUIRED (claim without photo), TOO_MANY (more than 5 attachments), ITEMS_NOT_ALLOWED (items on another type), UNKNOWN_ORDER_ITEM, DUPLICATE_ITEM, FILE_NOT_FOUND, FILE_ALREADY_ATTACHED, lengths. |
| 404 | NOT_FOUND | orderId is not one of my orders. |
| 422 | FILE_NOT_READY | An attachment upload was not completed. |
| 422 | FILE_PURPOSE_MISMATCH | An attachment was not uploaded with purpose TICKET_ATTACHMENT. |
| 409 | ORDER_NOT_DELIVERED | AFTER_SALES on an order that is not DELIVERED. |
| 422 | AFTER_SALES_WINDOW_EXPIRED | Past the after-sales window (details.claimableUntil). |
| 422 | QUANTITY_NOT_ALLOWED | An item's qty above what is left to claim (details: { orderItemId, claimableQty }). |
| 429 | RATE_LIMITED | More than 10 tickets in 24 hours for the reseller (assumption, anti-spam). |
Rules & side effects
Ticket(+TicketItemrows for a claim, + the firstTicketMessage), numberTKT-YYYY-NNNNNN, statusOPEN; attachments linked to the message.- Claims are flagged « SAV » in the support queue (BO-55, BO-69); the claimed quantities count in the order line's
claimedQty, so the same units cannot be claimed or returned twice. - Outbox
ticket.created→ support queue in-app (and SSE forAFTER_SALES), acknowledgement in the reseller's inbox; CRM timeline entryCLAIMfor claims. - « Contacter le support » from an order pre-fills
orderIdandtype: ORDER_ISSUE.
Used by — RA-33 (« Nouvelle demande »), « Contacter le support » and « Signaler un problème » on RA-22, RW-14.
GET /tickets/{ticketId}
tickets.get · Access session — reseller (self) · Apps RA, RW · Idempotency-Key — · Release V1 · Requirements SUP-01, SUP-04
Purpose. « Conversation »: the ticket with its public messages and, for a claim, the claimed items and the resolution.
Request — path ticketId.
Response — 200 OK: a TicketDetail. Example: the claim above two days later — HOCO asked a question, the reseller answered, a replacement was decided.
{
"id": "01a1a12c-56c0-7989-951b-4022807290b6",
"number": "TKT-2026-000046",
"type": "AFTER_SALES",
"subject": "BOROFONE BW06 — boîtier de charge défectueux",
"status": "RESOLVED",
"order": { "id": "01a1065b-2900-748c-88c5-976d443033f4", "number": "CMD-2026-000131" },
"afterSalesResolution": "REPLACEMENT",
"lastMessageAt": "2026-11-05T09:40:00Z",
"lastMessagePreview": "Réclamation résolue : remplacement. La commande CMD-2026-000163 a été créée pour vous.",
"unreadCount": 0,
"rating": null,
"createdAt": "2026-11-03T11:30:00Z",
"resolvedAt": "2026-11-05T09:40:00Z",
"closedAt": null,
"messages": [
{
"id": "01a1a12c-56c0-7090-99d0-d51e9b32f39a",
"author": { "type": "RESELLER", "displayName": "Mohamed Benali" },
"body": "Trois paires BW06 de la commande CMD-2026-000131 : le boîtier ne charge plus les écouteurs après une semaine. Clients revenus au magasin.",
"attachments": [
{ "id": "01a1a12a-8200-79a8-93c8-c5b89aac7ecf", "url": "https://storage.hoco.dz/hoco-private/ticket_attachment/2026/11/01a1a12a-8200-79a8-93c8-c5b89aac7ecf.webp?X-Amz-Expires=300&X-Amz-Signature=5d0b…", "thumbnailUrl": "https://storage.hoco.dz/hoco-private/ticket_attachment/2026/11/01a1a12a-8200-79a8-93c8-c5b89aac7ecf-thumb.webp?X-Amz-Expires=300&X-Amz-Signature=8a41…", "mimeType": "image/webp", "size": 236114, "purpose": "TICKET_ATTACHMENT" }
],
"createdAt": "2026-11-03T11:30:00Z"
},
{
"id": "01a1a150-f5c0-7f42-97e3-64c86987a26c",
"author": { "type": "SUPPORT", "displayName": "Sarah — Service client" },
"body": "Merci. Les voyants du boîtier s'allument-ils quand il est branché ? Pouvez-vous nous envoyer une vidéo courte ou une photo du boîtier branché ?",
"attachments": [],
"createdAt": "2026-11-03T12:10:00Z"
},
{
"id": "01a1a2b0-4a00-7c13-8d24-5e6f708192a3",
"author": { "type": "RESELLER", "displayName": "Mohamed Benali" },
"body": "Aucun voyant ne s'allume, même avec un autre câble.",
"attachments": [],
"createdAt": "2026-11-04T08:05:00Z"
},
{
"id": "01a1aa0c-1e00-7d24-9e35-6f708192a3b4",
"author": { "type": "SYSTEM", "displayName": "HOCO" },
"body": "Réclamation résolue : remplacement. La commande CMD-2026-000163 a été créée pour vous.",
"attachments": [],
"createdAt": "2026-11-05T09:40:00Z"
}
],
"afterSales": {
"order": { "id": "01a1065b-2900-748c-88c5-976d443033f4", "number": "CMD-2026-000131", "deliveredAt": "2026-10-08T10:20:00Z" },
"items": [
{
"id": "01a1a12c-56c0-7b11-8c22-4d5e6f708192",
"orderItemId": "01a1065b-2900-7a11-9b22-3c4d5e6f7a81",
"product": { "id": "01a0901f-d380-7a88-9746-4ec8936c3d68", "name": "BOROFONE BW06 Écouteurs TWS", "imageUrl": "https://cdn.hoco.dz/products/bw06/bw06-white-1-240.webp" },
"variant": { "id": "01a0901f-d768-77a1-94f2-1bca2251bdcd", "sku": "BW06_WHITE", "label": "Blanc" },
"qty": 3,
"issue": "DEFECTIVE"
}
],
"claimableUntil": "2026-11-07T10:20:00Z",
"resolution": {
"type": "REPLACEMENT",
"note": "Défaut de batterie du boîtier confirmé. Trois paires neuves vous sont envoyées gratuitement ; votre délégué récupère les paires défectueuses.",
"decidedAt": "2026-11-05T09:40:00Z",
"replacementOrder": { "id": "01a1aa0c-1e00-7a35-8b46-708192a3b4c5", "number": "CMD-2026-000163", "status": "SUBMITTED" },
"return": null,
"creditNote": null,
"repair": null,
"rejectionReason": null
}
},
"allowedActions": ["REPLY", "CLOSE", "RATE"]
}| Field | Notes |
|---|---|
messages[].author | Support agents appear by first name and team; SYSTEM messages record status changes and resolutions. Internal notes are never returned. |
afterSales.resolution | REPLACEMENT: free order (price 0) that waits for the back-office confirmation like any order — link to RA-22. RETURN: a return request already APPROVED with its instructions — link to RA-40. CREDIT_NOTE: credit note on the order's invoice. REPAIR: expected date. REJECTED: status REJECTED and rejectionReason (out of warranty, misuse…); the reseller may contest once by replying. |
allowedActions | REPLY unless CLOSED; CLOSE from OPEN, IN_PROGRESS, RESOLVED, REJECTED; RATE when closing a resolved or rejected ticket. |
Errors
| Status | Code | When |
|---|---|---|
| 404 | NOT_FOUND | Not my ticket. |
Rules & side effects — reading the conversation marks the support messages as read (unreadCount → 0) — the one documented side effect of this read. Attachment URLs are signed for 5 minutes. Live: ticket.replied appends messages while the screen is open.
Used by — RA-34 (« Conversation »), RW-14, deep links of ticket notifications.
POST /tickets/{ticketId}/messages
tickets.reply · Access session — reseller (self) · Apps RA, RW · Idempotency-Key required · Release V1 · Requirements SUP-01
Purpose. Adds my reply to the conversation (text and up to 5 attachments).
Request
interface ReplyRequest {
message: string; // 1–2 000 characters
attachmentFileIds?: Uuid[]; // 0–5, TICKET_ATTACHMENT
}{ "message": "Non, le magasin était fermé hier après-midi. Le livreur peut passer demain matin entre 9h et 12h.", "attachmentFileIds": [] }Response — 201 Created: the new message and the ticket after the change.
{
"message": {
"id": "01a1a2c6-9b00-7e35-8f46-708192a3b4c5",
"author": { "type": "RESELLER", "displayName": "Mohamed Benali" },
"body": "Non, le magasin était fermé hier après-midi. Le livreur peut passer demain matin entre 9h et 12h.",
"attachments": [],
"createdAt": "2026-11-03T09:20:00Z"
},
"ticket": {
"id": "01a1965f-7240-7a21-a8d4-11ef017fd953",
"number": "TKT-2026-000045",
"type": "DELIVERY_ISSUE",
"subject": "Commande CMD-2026-000155 pas encore reçue",
"status": "IN_PROGRESS",
"order": { "id": "01a187cf-6b40-7c6f-894d-b1f256947da9", "number": "CMD-2026-000155" },
"afterSalesResolution": null,
"lastMessageAt": "2026-11-03T09:20:00Z",
"lastMessagePreview": "Non, le magasin était fermé hier après-midi. Le livreur peut passer demain matin entre 9h et 12h.",
"unreadCount": 0,
"rating": null,
"createdAt": "2026-11-01T09:10:00Z",
"resolvedAt": null,
"closedAt": null
}
}Errors
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_FAILED | Empty or too long message; attachment field codes as for tickets.create. |
| 404 | NOT_FOUND | Not my ticket. |
| 422 | FILE_NOT_READY | Attachment upload not completed. |
| 422 | FILE_PURPOSE_MISMATCH | Attachment not uploaded with purpose TICKET_ATTACHMENT. |
| 409 | TICKET_TRANSITION_NOT_ALLOWED | The ticket is CLOSED — open a new ticket referencing it (details.from: "CLOSED"). Contesting a REJECTED claim a second time gives the same answer. |
Rules & side effects — a reply moves WAITING_RESELLER, RESOLVED or REJECTED (first contest) back to IN_PROGRESS and notifies the assignee in-app; on OPEN and IN_PROGRESS the status is unchanged. The auto-close timer of resolved tickets restarts.
Used by — reply field of RA-34 and RW-14.
POST /tickets/{ticketId}/close
tickets.close · Access session — reseller (self) · Apps RA, RW · Idempotency-Key optional · Release V1 · Requirements SUP-01
Purpose. « Clôturer »: the reseller closes the ticket, optionally rating the answer from 1 to 5.
Request
interface CloseTicketRequest {
rating?: { score: 1 | 2 | 3 | 4 | 5; comment?: string }; // comment ≤ 300
}{ "rating": { "score": 5, "comment": "Remplacement rapide, merci." } }Response — 200 OK: the TicketSummary, status CLOSED.
{
"id": "01a1a12c-56c0-7989-951b-4022807290b6",
"number": "TKT-2026-000046",
"type": "AFTER_SALES",
"subject": "BOROFONE BW06 — boîtier de charge défectueux",
"status": "CLOSED",
"order": { "id": "01a1065b-2900-748c-88c5-976d443033f4", "number": "CMD-2026-000131" },
"afterSalesResolution": "REPLACEMENT",
"lastMessageAt": "2026-11-05T09:40:00Z",
"lastMessagePreview": "Réclamation résolue : remplacement. La commande CMD-2026-000163 a été créée pour vous.",
"unreadCount": 0,
"rating": { "score": 5, "comment": "Remplacement rapide, merci.", "ratedAt": "2026-11-05T12:15:00Z" },
"createdAt": "2026-11-03T11:30:00Z",
"resolvedAt": "2026-11-05T09:40:00Z",
"closedAt": "2026-11-05T12:15:00Z"
}Errors
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_FAILED | score outside 1–5, comment too long. |
| 404 | NOT_FOUND | Not my ticket. |
| 409 | TICKET_TRANSITION_NOT_ALLOWED | Already CLOSED, or WAITING_RESELLER (support is waiting for my answer: reply instead, or let the auto-close run) — details: { from, to: "CLOSED", allowed }. |
Rules & side effects — closedAt, rating stored on the ticket (support KPI on BO-55); closing does not cancel a replacement order or a return already created by a resolution. Resolved and rejected tickets are closed automatically after support.autoCloseDays (default 7) without reply.
GET /support/contacts
support.contacts · Access session — any signed-in reseller, PENDING_VALIDATION included · Apps RA, RW · Idempotency-Key — · Release V1 · Requirements SUP-03
Purpose. The support phone, e-mail and hours, and the FAQ shown at the top of « Assistance ». The WhatsApp button uses support.whatsapp.
Request — no parameters (Accept-Language selects the FAQ language).
Response — 200 OK, ETag, Cache-Control: private, max-age=3600.
{
"phone": "+213412345678",
"email": "support@hoco.dz",
"hours": [
{ "days": ["SUN", "MON", "TUE", "WED", "THU"], "opens": "08:30", "closes": "17:00" }
],
"timezone": "Africa/Algiers",
"isOpenNow": true,
"outsideHoursMessage": "Notre équipe vous répond du dimanche au jeudi, de 8h30 à 17h. Laissez-nous un message, nous vous rappelons.",
"faq": [
{ "id": "01a0f153-dc00-745c-9922-1f433ab10dd2", "category": "Commandes", "q": "Quel est le montant minimum de commande ?", "a": "10 000,00 DA TTC de produits, hors frais de livraison." },
{ "id": "01a0f153-dfe8-76ad-9d82-21d5c46a3ba9", "category": "Commandes", "q": "Pourquoi ma commande est-elle « En attente » ?", "a": "Chaque commande est vérifiée et confirmée par l'équipe HOCO avant sa préparation. Vous êtes notifié dès qu'elle est validée." },
{ "id": "01a0f153-e3d0-7614-9bf6-96b27f009aa5", "category": "Paiements", "q": "Comment déclarer un virement, un versement CCP ou un paiement BaridiMob ?", "a": "Compte › Paiements › Déclarer un paiement : indiquez le montant, la date et la référence, et joignez la photo du reçu." },
{ "id": "01a0f153-e7b8-77da-bc3b-124e41f4a169", "category": "Compte", "q": "Comment changer le nom du magasin ou le numéro principal ?", "a": "Ouvrez une demande d'assistance : ces informations sont modifiées par HOCO après vérification." }
]
}Errors — none specific.
Rules & side effects — contacts and hours come from the settings (support.phone, support.email, support.hours — the same values appConfig.get gives before sign-in); FAQ entries are managed in the back-office settings. isOpenNow is computed in Africa/Algiers and ignores public holidays (assumption — a holiday calendar can be added to the settings).
Used by — RA-32, RW-14, the contact block of RA-06.
GET /support/whatsapp
support.whatsapp · Access optional — guest or signed-in user · Apps RA, RW · Idempotency-Key — · Release V1 · Requirements SUP-05, ADM-08
Purpose. Resolves the WhatsApp number of the caller's support region for the fixed WhatsApp button, with a ready wa.me link, the hours and how the region was found.
Request
| Query | Type | Required | Notes |
|---|---|---|---|
lat, lng | number | no (together) | Device position (guests); WGS84 |
wilayaCode | string | no | Wilaya picked by a guest when the location is unavailable |
productId | Uuid | no | Pre-fills the message with the product (from a product page) |
orderId | Uuid | no | Signed-in only: pre-fills the message with my order number |
Signed-in user (no parameter needed):
GET /v1/support/whatsappResponse — 200 OK, Cache-Control: private, max-age=300.
interface SupportWhatsapp {
region: { id: Uuid; name: string } | null; // null with the DEFAULT number
wilaya: { code: string; name: string } | null; // the wilaya used for the resolution
whatsappNumber: string; // E.164
waLink: string; // https://wa.me/<digits>?text=<url-encoded>
prefillText: string;
hours: { days: ("SUN" | "MON" | "TUE" | "WED" | "THU" | "FRI" | "SAT")[]; opens: string; closes: string }[];
timezone: "Africa/Algiers";
isOpenNow: boolean;
outsideHoursMessage: string | null;
resolvedFrom: "ACCOUNT" | "LOCATION" | "WILAYA" | "DEFAULT";
}{
"region": { "id": "01a0c7e2-41d0-7a6b-9c3e-5f1a2b3c4d5e", "name": "Ouest" },
"wilaya": { "code": "31", "name": "Oran" },
"whatsappNumber": "+213770900310",
"waLink": "https://wa.me/213770900310?text=Bonjour%2C%20je%20suis%20Magasin%20El%20Wafa%20(REV-000123).%20",
"prefillText": "Bonjour, je suis Magasin El Wafa (REV-000123). ",
"hours": [ { "days": ["SUN", "MON", "TUE", "WED", "THU"], "opens": "08:30", "closes": "17:00" } ],
"timezone": "Africa/Algiers",
"isOpenNow": true,
"outsideHoursMessage": null,
"resolvedFrom": "ACCOUNT"
}A guest on a product page, location allowed, standing in Sidi Bel Abbès (wilaya 22, region Ouest):
GET /v1/support/whatsapp?lat=35.1899&lng=-0.6309&productId=01a08ac2-8900-7f6d-937c-c81b37e89a5b{
"region": { "id": "01a0c7e2-41d0-7a6b-9c3e-5f1a2b3c4d5e", "name": "Ouest" },
"wilaya": { "code": "22", "name": "Sidi Bel Abbès" },
"whatsappNumber": "+213770900310",
"waLink": "https://wa.me/213770900310?text=Bonjour%2C%20je%20souhaite%20des%20informations%20sur%20HOCO%20N32%20Chargeur%20rapide%2030W%20(r%C3%A9f.%20N32).",
"prefillText": "Bonjour, je souhaite des informations sur HOCO N32 Chargeur rapide 30W (réf. N32).",
"hours": [ { "days": ["SUN", "MON", "TUE", "WED", "THU"], "opens": "08:30", "closes": "17:00" } ],
"timezone": "Africa/Algiers",
"isOpenNow": true,
"outsideHoursMessage": null,
"resolvedFrom": "LOCATION"
}| Field | Notes |
|---|---|
resolvedFrom | ACCOUNT: signed in — the region of the wilaya declared in the account (Reseller.wilayaCode → SupportRegionWilaya), query parameters ignored. LOCATION: nearest commune centroid to lat/lng → its wilaya → region. WILAYA: the wilayaCode picked by a guest. DEFAULT: nothing usable, or no region covers the wilaya — the national number support.whatsapp from the settings, region: null. |
waLink | Opened as is (url_launcher on mobile, a new tab on the web); the digits are the E.164 number without +. No message is sent by the platform (WhatsApp Business API is V2). |
prefillText | Signed-in: store name and REV- number (plus the order number with orderId); guest: a neutral greeting, the product with productId. Never a price. |
outsideHoursMessage | Set when isOpenNow is false, shown under the button (« Réponse dès 8h30 »). |
Errors
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_FAILED | lat without lng (or the reverse), coordinates out of range, unknown wilayaCode, orderId sent by a guest. |
| 404 | NOT_FOUND | productId not visible to the caller, orderId not mine. |
Rules & side effects
- Resolution order: account → location → wilaya → default (D-36 decides the regions; with one national region every caller gets the same number and the feature works unchanged).
- Coordinates are used for the lookup only: never stored, logged rounded to 0.1°. A position outside Algeria resolves to
DEFAULT. - Signed-in apps can open the button straight from
Me.supportRegion(me) and call this operation only to refresh hours or build a contextual message; guests always call it (the app asks for the location permission only when the button is tapped, and falls back to a wilaya picker). - Public rate limit: 30 calls per minute per IP for guests.
Used by — the fixed WhatsApp button of every reseller app screen and of the reseller web layout (guest browsing RA-41 included), RA-32, RW-14; empty search results of the catalog.
Reseller profile, addresses & documents
The reseller's store profile with status, customer type, assigned delegate and support region, the fields a reseller may edit, store documents (RC, NIF, NIS, ID) and delivery addresses.
Invitations & attribution (public + reseller)
Public operations behind the delegates' invitation links and QR codes: resolving a code, attributing the app install to the inviting delegate, and the /i/ landing page that opens the right app or store.