# Hosted Checkout (/fr/online/integration/hosted_checkout)





Envoyez le client vers une page de paiement hébergée par Tapaya pour les cartes, Apple Pay et Google Pay, puis vérifiez le résultat sur
votre serveur. Votre page ne manipule jamais les données de carte, et vous n’ajoutez aucun champ de carte à votre boutique.

<CheckoutFlowDiagram />

## Avant de commencer [#before-you-start]

Choisissez la clé qui correspond à l’entité qui exploite votre backend. Quelle que soit la clé, conservez-la sur votre serveur ; la page [Sécurité du
Checkout](/online/security/checkout_security) liste les règles côté serveur.

<Cards>
  <VisualCard title="Une seule boutique" visual="<Store />" href="/online/integration/checkout_settings#create-merchant-checkout-keys" description="Utilisez une clé checkout de commerçant. Elle sélectionne son commerçant, et les exemples de cette page l’utilisent." />

  <VisualCard title="Plusieurs commerçants" visual="<Building2 />" href="/online/integration/api_integration#authentication" description="Utilisez la Secret key de l’organisation et envoyez un jeton de commerçant avec chaque requête." />
</Cards>

Ensuite, dans **Checkout** de la [Tapaya Platform](https://platform.tapaya.com/checkout-settings) :

1. **Ajoutez votre commerçant** et vérifiez qu’il affiche **Ready**. Voir [Paramètres du Checkout](/online/integration/checkout_settings).
2. **Enregistrez votre boutique** comme site autorisé, avec des URL de succès et d’annulation par défaut, pour la clé que vous avez choisie. Les
   exemples utilisent `https://shop.example`.

<Callout title="Les URL de retour suivent la clé" type="info">
  Les sessions créées avec une clé de commerçant utilisent les URL de retour du commerçant. Les sessions créées avec la Secret key de l’organisation utilisent
  plutôt les [destinations de retour](/online/integration/checkout_settings#configure-return-destinations) de l’organisation.
  Les deux ne sont jamais combinées.
</Callout>

## Accepter un paiement [#accept-a-payment]

Les trois options créent les mêmes sessions Checkout avec les mêmes identifiants. Choisissez-en une ; chaque étape ci-dessous suit
votre choix.

<div className="fd-steps [&_h3]:fd-step">
  ### Configurez votre serveur [#set-up-your-server]

  <Tabs defaultValue="fetch" groupId="checkout-server" className="border-0 bg-transparent">
    <TabsList>
      <TabsTrigger value="fetch">
        HTTP (fetch)
      </TabsTrigger>

      <TabsTrigger value="node">
        Node.js SDK
      </TabsTrigger>

      <TabsTrigger value="curl">
        cURL
      </TabsTrigger>
    </TabsList>

    <TabsContent value="fetch">
      Définissez `TAPAYA_CHECKOUT_API_URL` sur `https://api.sandbox.tapaya.com` et stockez votre clé dans `TAPAYA_CHECKOUT_API_KEY`.
      Appelez l’API avec le `fetch` de votre environnement d’exécution serveur, jamais depuis le navigateur du client. Les exemples lisent ces valeurs
      via `process.env` ; adaptez cette lecture si votre environnement d’exécution utilise une autre interface pour les secrets.

      Créez une fonction utilitaire qui vérifie les erreurs HTTP avant de renvoyer le corps de la réponse :

      ```js title="server/checkout.js"
      const apiUrl = process.env.TAPAYA_CHECKOUT_API_URL;
      const apiKey = process.env.TAPAYA_CHECKOUT_API_KEY;

      async function checkoutRequest(path, options = {}) {
      	const response = await fetch(new URL(path, apiUrl), {
      		...options,
      		headers: {
      			...options.headers,
      			Authorization: `Bearer ${apiKey}`,
      		},
      		redirect: 'error',
      		cache: 'no-store',
      		signal: AbortSignal.timeout(8000),
      	});

      	if (!response.ok) {
      		throw new Error(`Checkout request failed (${response.status})`);
      	}

      	return response.json();
      }
      ```
    </TabsContent>

    <TabsContent value="node">
      Utilisez Node.js 22.13.0 ou version ultérieure, et installez le paquet ESM sur votre serveur :

      <CodeBlockTabs defaultValue="npm" groupId="package-manager">
        <CodeBlockTabsList>
          <CodeBlockTabsTrigger value="npm">
            npm
          </CodeBlockTabsTrigger>

          <CodeBlockTabsTrigger value="pnpm">
            pnpm
          </CodeBlockTabsTrigger>

          <CodeBlockTabsTrigger value="bun">
            Bun
          </CodeBlockTabsTrigger>
        </CodeBlockTabsList>

        <CodeBlockTab value="npm">
          ```sh
          npm install @tapayadot/checkout
          ```
        </CodeBlockTab>

        <CodeBlockTab value="pnpm">
          ```sh
          pnpm add @tapayadot/checkout
          ```
        </CodeBlockTab>

        <CodeBlockTab value="bun">
          ```sh
          bun add @tapayadot/checkout
          ```
        </CodeBlockTab>
      </CodeBlockTabs>

      Stockez votre clé dans `TAPAYA_SECRET_KEY` et sélectionnez l’environnement sandbox. Le SDK ne charge pas lui-même les fichiers `.env` :
      chargez donc ces valeurs dans l’environnement de votre serveur avant de construire le client.

      ```sh title=".env"
      TAPAYA_SECRET_KEY=tapaya_test_...
      TAPAYA_ENVIRONMENT=sandbox
      ```

      ```ts title="server/checkout.ts"
      import { Tapaya } from '@tapayadot/checkout';

      const tapaya = new Tapaya(); // reads TAPAYA_SECRET_KEY
      ```

      Sans `TAPAYA_ENVIRONMENT`, une clé `tapaya_live_` sélectionne la production et les autres clés utilisent la sandbox par défaut. En
      mode plateforme, transmettez le jeton du commerçant : `new Tapaya(key, { merchantToken })`, ou `merchantToken` à chaque appel. Si
      votre clé est stockée sous un autre nom, transmettez-la comme premier argument. La [référence du SDK
      Node.js](/online/integration/node_sdk) couvre les URL d’API personnalisées, les délais d’attente, les nouvelles tentatives et les erreurs.
    </TabsContent>

    <TabsContent value="curl">
      Exécutez ces commandes depuis votre serveur ou un terminal de confiance. Chargez votre clé dans `TAPAYA_CHECKOUT_API_KEY`, tenez-la à l’écart
      du code du navigateur et du gestionnaire de versions, et faites pointer l’URL de l’API vers la sandbox :

      ```bash
      export TAPAYA_CHECKOUT_API_URL='https://api.sandbox.tapaya.com'
      ```
    </TabsContent>
  </Tabs>

  ### Créez une session [#create-a-session]

  Calculez le total de la commande sur votre serveur, taxes et livraison comprises. Enregistrez la commande et une
  `checkoutAttemptKey` unique **avant** d’envoyer la requête, et chargez `order` depuis cet instantané enregistré. `merchantOrderId`
  est obligatoire.

  <Tabs defaultValue="fetch" groupId="checkout-server" className="border-0 bg-transparent">
    <TabsList>
      <TabsTrigger value="fetch">
        HTTP (fetch)
      </TabsTrigger>

      <TabsTrigger value="node">
        Node.js SDK
      </TabsTrigger>

      <TabsTrigger value="curl">
        cURL
      </TabsTrigger>
    </TabsList>

    <TabsContent value="fetch">
      ```js
      const session = await checkoutRequest('/merchant/checkout-sessions', {
      	method: 'POST',
      	headers: {
      		'Content-Type': 'application/json',
      		'Idempotency-Key': order.checkoutAttemptKey,
      	},
      	body: JSON.stringify({
      		merchantOrderId: order.id,
      		amount: order.totalInMinorUnits,
      		currency: order.currency,
      		successUrl: 'https://shop.example/checkout/return',
      		cancelUrl: 'https://shop.example/checkout/cancel',
      	}),
      });
      ```
    </TabsContent>

    <TabsContent value="node">
      ```ts
      const session = await tapaya.checkout.sessions.create(
      	{
      		merchantOrderId: order.id,
      		amount: order.totalInMinorUnits,
      		currency: order.currency,
      		successUrl: 'https://shop.example/checkout/return',
      		cancelUrl: 'https://shop.example/checkout/cancel',
      	},
      	{ idempotencyKey: order.checkoutAttemptKey },
      );
      ```
    </TabsContent>

    <TabsContent value="curl">
      L’exemple débite 121,00 EUR pour `order-1001`, dont 21,00 EUR de TVA et 6,05 EUR de livraison, avec
      l’idempotency key enregistrée `order-1001-checkout-1`. Remplacez ces valeurs par les informations de votre commande enregistrée.

      ```bash
      curl --request POST \
      	"$TAPAYA_CHECKOUT_API_URL/merchant/checkout-sessions" \
      	--fail-with-body --silent --show-error --max-time 8 \
      	--header "Authorization: Bearer $TAPAYA_CHECKOUT_API_KEY" \
      	--header 'Content-Type: application/json' \
      	--header 'Idempotency-Key: order-1001-checkout-1' \
      	--data '{
      		"merchantOrderId": "order-1001",
      		"amount": 12100,
      		"currency": "EUR",
      		"tax": 2100,
      		"shipping": 605,
      		"taxRate": 21,
      		"successUrl": "https://shop.example/checkout/return",
      		"cancelUrl": "https://shop.example/checkout/cancel"
      	}'
      ```
    </TabsContent>
  </Tabs>

  La réponse contient l’`id` de la session et l’`url` hébergée. Pour les récapitulatifs d’articles, la ventilation des taxes, les informations client
  et tous les champs de la réponse, voir [Créer une session Checkout](/online/integration/api_integration#create-a-checkout-session).

  **Si la requête échoue ou expire,** relancez-la avec la même clé et un corps inchangé. Ne générez jamais de nouvelle clé pour
  un résultat incertain : un corps modifié avec la même clé renvoie HTTP 409.

  | Option             | Nouvelles tentatives automatiques                                                                                                                                                                                                                                                                                                                                                                                  |
  | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
  | HTTP (fetch), cURL | Non. Répétez la requête vous-même. Voir [Idempotence et erreurs](/online/integration/api_integration#idempotency-and-errors).                                                                                                                                                                                                                                                                                      |
  | Node.js SDK        | Jusqu’à deux fois après une erreur de connexion, un dépassement de délai ou une réponse HTTP 429, 502, 503 ou 504, avec la même clé et le même corps. Voir [Idempotence et nouvelles tentatives](/online/integration/node_sdk#idempotency-and-retries). D’un appel distinct à l’autre ou après un redémarrage, transmettez votre clé enregistrée : si vous l’omettez, une nouvelle clé est générée à chaque appel. |

  ### Redirigez le client [#redirect-the-customer]

  Enregistrez `session.id` avec votre commande en tant que `checkoutSessionId`, puis envoyez le navigateur vers `session.url` avec HTTP
  303\. Utilisez l’URL sans la modifier plutôt que de la construire vous-même.

  ```ts
  // First persist session.id as the order's checkoutSessionId.
  return Response.redirect(session.url, 303);
  ```

  Si votre framework utilise une autre API de réponse, envoyez HTTP 303 avec `session.url` dans l’en-tête `Location`. Avec
  cURL, ouvrez l’`url` renvoyée dans votre navigateur pour tester le paiement manuellement.

  ### Vérifiez le paiement [#verify-the-payment]

  Dans votre gestionnaire de retour, identifiez la commande à partir du contexte de checkout authentifié de votre application et récupérez sa
  session enregistrée depuis votre serveur. Faites-le aussi sur l’URL d’annulation : une visite de l’une ou l’autre URL ne prouve rien.

  <Tabs defaultValue="fetch" groupId="checkout-server" className="border-0 bg-transparent">
    <TabsList>
      <TabsTrigger value="fetch">
        HTTP (fetch)
      </TabsTrigger>

      <TabsTrigger value="node">
        Node.js SDK
      </TabsTrigger>

      <TabsTrigger value="curl">
        cURL
      </TabsTrigger>
    </TabsList>

    <TabsContent value="fetch">
      ```js
      const current = await checkoutRequest(
      	`/merchant/checkout-sessions/${encodeURIComponent(order.checkoutSessionId)}`,
      );

      if (
      	current.id !== order.checkoutSessionId ||
      	current.merchantOrderId !== order.id ||
      	current.amount !== order.totalInMinorUnits ||
      	current.currency !== order.currency
      ) {
      	throw new Error('Checkout session does not match the order');
      }

      const paid = current.paymentStatus === 'successful';
      ```
    </TabsContent>

    <TabsContent value="node">
      ```ts
      const current = await tapaya.checkout.sessions.get(order.checkoutSessionId);

      if (
      	current.id !== order.checkoutSessionId ||
      	current.merchantOrderId !== order.id ||
      	current.amount !== order.totalInMinorUnits ||
      	current.currency !== order.currency
      ) {
      	throw new Error('Checkout session does not match the order');
      }

      const paid = current.paymentStatus === 'successful';
      ```
    </TabsContent>

    <TabsContent value="curl">
      Chargez l’ID de session enregistré avec votre commande dans `CHECKOUT_SESSION_ID` :

      ```bash
      curl "$TAPAYA_CHECKOUT_API_URL/merchant/checkout-sessions/$CHECKOUT_SESSION_ID" \
      	--fail-with-body --silent --show-error --max-time 8 \
      	--header "Authorization: Bearer $TAPAYA_CHECKOUT_API_KEY"
      ```

      Pour la commande d’exemple, vérifiez que `id` correspond à l’ID de session enregistré, que `merchantOrderId` vaut `order-1001`, que `amount`
      vaut `12100`, que `currency` vaut `EUR` et que `paymentStatus` vaut `successful`. Une réponse HTTP 200 seule ne prouve pas le paiement.
    </TabsContent>
  </Tabs>

  <Callout title="Traitez la commande une seule fois, et uniquement si elle est payée" type="warn">
    Enregistrez le paiement et traitez la commande uniquement lorsque la session correspond à votre commande enregistrée et que `paymentStatus` vaut
    `successful`, et enregistrez-le de manière atomique pour que cela ne se produise qu’une fois. Si la récupération échoue ou si le paiement n’est pas résolu, conservez
    l’ID de session et vérifiez à nouveau. Voir [Statut du paiement](/online/integration/api_integration#payment-status).
  </Callout>
</div>

## Gérer les résultats de paiement [#handle-payment-outcomes]

La page [Sécurité du Checkout](/online/security/checkout_security#verify-before-you-fulfill) explique ce qu’il faut vérifier et comment
relancer une requête au résultat inconnu sans créer de deuxième session.

| Résultat                                   | Ce qu’indique la session                                          | Que faire                                                                                                                                                                                         |
| ------------------------------------------ | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Refusé**                                 | `open`, avec `paymentStatus: failed`                              | Rien. Le client peut essayer une autre carte ou un autre portefeuille sur la même page.                                                                                                           |
| **Pas encore connu**                       | `open` et `unpaid` pendant que Tapaya confirme le résultat        | Récupérez à nouveau la session plus tard.                                                                                                                                                         |
| **Nouvelle session pour la même commande** | La session impayée précédente indique `expired`                   | Utilisez la nouvelle session. Une commande payée, ou dont un paiement est en cours, renvoie HTTP 409. Voir [Un paiement par commande](/online/integration/api_integration#one-payment-per-order). |
| **Expirée**                                | N’accepte plus de nouveaux paiements 30 minutes après sa création | Créez une nouvelle session si la commande est toujours impayée. Un paiement soumis avant l’expiration aboutit tout de même.                                                                       |

<Callout title="Pas encore de webhooks" type="info">
  Il n’existe pas encore de webhooks pour le Checkout. Exécutez côté serveur une tâche de rapprochement qui récupère les sessions des commandes dont
  les clients ne reviennent jamais.
</Callout>

## Vérifier l’intégration [#check-the-integration]

<Cards>
  <Card title="Cartes de test" href="/online/getting-started/testing" description="Testez chaque résultat de paiement dans la sandbox." />

  <Card title="Liste de contrôle QA" href="/online/getting-started/checklist" description="Couvrez les nouvelles tentatives, les paiements interrompus et l’expiration avant de passer en production." />
</Cards>
