> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oneclickdz.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Télécharger le Reçu

> Téléchargez la preuve de paiement d'une transaction réussie

## Vue d'ensemble

Diffuse le reçu du facturier pour une facture payée. C'est le seul endpoint Bill Payment qui ne retourne pas de JSON en cas de succès — le corps de la réponse est le fichier brut.

Les reçus n'existent que pour les transactions dont le `status` est `SUCCESS`. Tout le reste — une transaction encore en cours, une transaction échouée, une transaction remboursée, une transaction appartenant à un autre partenaire — répond avec l'enveloppe d'erreur JSON habituelle et un `404 NOT_FOUND`.

<Note>
  Le `receiptUrl` d'une transaction réussie correspond exactement à cet endpoint. Il requiert toujours votre en-tête `X-Access-Token` : ce n'est donc **pas** un lien que vous pouvez donner à un client ou intégrer dans un e-mail. Téléchargez les octets, stockez-les et servez-les depuis votre propre système.
</Note>

## Paramètres de chemin

<ParamField path="transactionId" type="string" required>
  L'identifiant de la transaction. Une chaîne hexadécimale en minuscules de 24 caractères, issue de [Obtenir une Transaction par ID](/fr/api-reference/bill-payment/check-by-id) ou de n'importe quelle liste.
</ParamField>

## Réponse

En cas de succès, le corps est le fichier lui-même. Lisez les en-têtes de réponse pour savoir ce que vous avez reçu.

<ResponseField name="Content-Type" type="header" required>
  Le type de média du fichier : `application/pdf`, `image/png` ou `image/jpeg`. Tout ce que nous ne pouvons pas identifier est servi en `application/octet-stream`.

  **Faites toujours votre branchement sur cet en-tête** plutôt que de supposer un PDF.
</ResponseField>

<ResponseField name="Content-Disposition" type="header" required>
  `attachment; filename="..."` — le nom de fichier suggéré, avec l'extension qui correspond à `Content-Type`.
</ResponseField>

<ResponseField name="Content-Length" type="header" required>
  La taille du fichier en octets.
</ResponseField>

<ResponseField name="Cache-Control" type="header" required>
  `private, max-age=86400`. Le reçu appartient à un seul partenaire ; ne le mettez jamais dans un cache partagé ou public.
</ResponseField>

<ResponseField name="X-Content-Type-Options" type="header" required>
  `nosniff`.
</ResponseField>

<ResponseField name="X-Request-Id" type="header" required>
  Identifiant de corrélation de cette requête. Il n'y a pas de corps JSON en cas de succès : cet en-tête est donc le seul endroit où il apparaît — journalisez-le.
</ResponseField>

## Exemples

Chaque exemple vérifie le statut avant d'écrire quoi que ce soit, et prend l'extension du fichier dans la réponse plutôt que de la supposer.

<CodeGroup>
  ```bash cURL theme={null}
  # -J -O writes the file under the name the server suggests
  curl https://billapi.oneclickdz.com/v3/bills/transactions/68b2f4c1a7d3e9f204c81a55/receipt \
    -H "X-Access-Token: YOUR_API_KEY" \
    --fail \
    --remote-header-name --remote-name
  ```

  ```javascript Node.js theme={null}
  import { writeFile } from "node:fs/promises";

  const transactionId = "68b2f4c1a7d3e9f204c81a55";

  const response = await fetch(
    `https://billapi.oneclickdz.com/v3/bills/transactions/${transactionId}/receipt`,
    { headers: { "X-Access-Token": process.env.ONECLICKDZ_API_KEY } },
  );

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`${error.error.code}: ${error.error.message}`);
  }

  const contentType = response.headers.get("content-type");
  const extension = contentType.includes("pdf")
    ? "pdf"
    : contentType.includes("png")
      ? "png"
      : contentType.includes("jpeg")
        ? "jpg"
        : "bin";

  const bytes = Buffer.from(await response.arrayBuffer());
  await writeFile(`receipt-${transactionId}.${extension}`, bytes);

  console.log(response.headers.get("x-request-id"), bytes.length);
  ```

  ```python Python theme={null}
  import os
  import requests

  transaction_id = '68b2f4c1a7d3e9f204c81a55'

  response = requests.get(
      f'https://billapi.oneclickdz.com/v3/bills/transactions/{transaction_id}/receipt',
      headers={'X-Access-Token': os.getenv('ONECLICKDZ_API_KEY')}
  )

  if response.status_code != 200:
      error = response.json()
      raise RuntimeError(f"{error['error']['code']}: {error['error']['message']}")

  content_type = response.headers.get('Content-Type', '')
  extension = (
      'pdf' if 'pdf' in content_type
      else 'png' if 'png' in content_type
      else 'jpg' if 'jpeg' in content_type
      else 'bin'
  )

  with open(f'receipt-{transaction_id}.{extension}', 'wb') as handle:
      handle.write(response.content)

  print(response.headers.get('X-Request-Id'), len(response.content))
  ```

  ```php PHP theme={null}
  <?php
  $transactionId = '68b2f4c1a7d3e9f204c81a55';

  $ch = curl_init("https://billapi.oneclickdz.com/v3/bills/transactions/$transactionId/receipt");
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-Access-Token: ' . getenv('ONECLICKDZ_API_KEY')
  ]);

  $bytes  = curl_exec($ch);
  $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  $type   = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
  curl_close($ch);

  if ($status !== 200) {
      $error = json_decode($bytes, true);
      throw new Exception($error['error']['code'] . ': ' . $error['error']['message']);
  }

  $extension = str_contains($type, 'pdf') ? 'pdf'
      : (str_contains($type, 'png') ? 'png'
      : (str_contains($type, 'jpeg') ? 'jpg' : 'bin'));

  file_put_contents("receipt-$transactionId.$extension", $bytes);

  echo strlen($bytes);
  ?>
  ```
</CodeGroup>

### Réponse de succès

Le corps est binaire. Les en-têtes ressemblent à ceci :

```http theme={null}
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Length: 48211
Content-Disposition: attachment; filename="receipt-68b2f4c1a7d3e9f204c81a55.pdf"
Cache-Control: private, max-age=86400
X-Content-Type-Options: nosniff
X-Request-Id: req_9f3a1c72e0b84d51aB3xZq07
```

## Réponses d'erreur

Les erreurs sont retournées en JSON, dans la même enveloppe que pour tous les autres endpoints.

<AccordionGroup>
  <Accordion title="401 — Token d'Accès Manquant ou Invalide">
    **La clé était absente ou a été rejetée.**

    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "INVALID_ACCESS_TOKEN",
        "message": "The provided access token is invalid."
      },
      "requestId": "req_9f3a1c72e0b84d51aB3xZq07"
    }
    ```

    **Que faire :** vérifiez la clé avec [Valider la Clé API](/fr/api-reference/bill-payment/validate-key).
  </Accordion>

  <Accordion title="404 — Aucun Reçu Disponible">
    **Soit la transaction ne vous appartient pas dans cet environnement, soit elle n'a pas de reçu.**

    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "NOT_FOUND",
        "message": "Receipt not available for this transaction."
      },
      "requestId": "req_9f3a1c72e0b84d51aB3xZq07"
    }
    ```

    Un reçu n'existe qu'une fois qu'un paiement a atteint `SUCCESS`. Télécharger pendant que la transaction est en `PROCESSING` retourne cette erreur, tout comme une transaction `FAILED` ou `REFUNDED` — ni l'une ni l'autre n'a de reçu, car aucun paiement n'a été mené à terme.

    **Que faire :** lisez d'abord la transaction, et ne téléchargez que lorsque `status` vaut `SUCCESS` et que `receiptUrl` est présent.
  </Accordion>

  <Accordion title="503 — Authentification ou Service Indisponible">
    **Nous n'avons pas pu vérifier votre clé à temps, ou l'API est en maintenance planifiée.**

    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "AUTH_UNAVAILABLE",
        "message": "Authentication is temporarily unavailable. Please retry shortly."
      },
      "requestId": "req_9f3a1c72e0b84d51aB3xZq07"
    }
    ```

    **Que faire :** respectez l'en-tête `Retry-After` (5 secondes) et réessayez. Le téléchargement peut être répété sans risque.
  </Accordion>

  <Accordion title="500 — Erreur Interne">
    **Le reçu n'a pas pu être lu.**

    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "INTERNAL_ERROR",
        "message": "An unexpected error occurred."
      },
      "requestId": "req_9f3a1c72e0b84d51aB3xZq07"
    }
    ```

    **Que faire :** réessayez une fois, puis contactez le support avec le `requestId`. Le paiement lui-même n'est pas affecté — la transaction reste en `SUCCESS`.
  </Accordion>
</AccordionGroup>

## Télécharger au bon moment

Récupérez le reçu dès qu'un paiement atteint `SUCCESS`, dans la même étape que celle qui enregistre le succès de votre côté.

```javascript theme={null}
async function settle(transactionId) {
  const response = await fetch(
    `https://billapi.oneclickdz.com/v3/bills/transactions/${transactionId}`,
    { headers: { "X-Access-Token": process.env.ONECLICKDZ_API_KEY } },
  );

  const body = await response.json();
  if (!body.success) throw new Error(body.error.code);

  const transaction = body.data;
  if (transaction.status !== "SUCCESS") return transaction.status;

  // operationId is the biller's proof; the receipt is the document for it.
  await saveOrder({
    transactionId: transaction.transactionId,
    operationId: transaction.operationId,
    total: transaction.total,
  });

  await downloadReceipt(transactionId);

  return "SUCCESS";
}
```

<Warning>
  Stockez les octets, pas l'URL. `receiptUrl` requiert votre clé API : un lien stocké est donc inutile pour votre client et dangereux à partager.
</Warning>

## Bonnes pratiques

<CardGroup cols={2}>
  <Card title="Télécharger une Fois, Stocker pour Toujours" icon="box-archive">
    Conservez le reçu avec votre propre enregistrement de commande. C'est le document avec lequel se règle un litige client.
  </Card>

  <Card title="Lire Content-Type" icon="file-lines">
    Le reçu est un PDF ou une image selon le facturier. Prenez l'extension dans l'en-tête.
  </Card>

  <Card title="Ne Jamais Exposer l'URL" icon="shield-halved">
    Servez les reçus depuis votre propre système, derrière votre propre authentification.
  </Card>

  <Card title="L'Associer à operationId" icon="receipt">
    Le reçu est le document ; `operationId` est la référence. Stockez les deux.
  </Card>
</CardGroup>

## Endpoints associés

<CardGroup cols={2}>
  <Card title="Obtenir une Transaction par ID" icon="id-card" href="/fr/api-reference/bill-payment/check-by-id">
    Où apparaissent `receiptUrl` et `operationId`
  </Card>

  <Card title="Lister les Transactions" icon="list" href="/fr/api-reference/bill-payment/list-transactions">
    Retrouvez tout ce qui a été payé sur une période
  </Card>

  <Card title="Payer une Facture" icon="money-bill-transfer" href="/fr/api-reference/bill-payment/pay-bill">
    Le paiement qui l'a produit
  </Card>

  <Card title="Reçus et Rapprochement" icon="scale-balanced" href="/fr/bill-payment-guides/5-receipts-and-reconciliation">
    Stocker et rapprocher les reçus
  </Card>
</CardGroup>
