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

# Intégration

Ce guide d'intégration décrit ce processus étape par étape:

1. Créer une intention de paiement `PaymentIntent`
2. Effectuer une tentative de paiement
3. (Optionnel) Côté client, authentifier la tentative de paiement
4. Côté HUB2: traitement du paiement auprès des fournisseurs
5. Récupération du statut de l'intention de paiement `PaymentIntent`

Voici en détail comment procéder à ces étapes.

## Créer une intention de paiement

L'objet `PaymentIntent` est utilisé pour représenter l'intention de collecter un paiement auprès d'un client. Il conserve une trace des frais et des différentes tentatives de paiement tout au long du traitement.

L'intention de paiement `PaymentIntent` doit être créée sur **le serveur du marchand** avec un `amount`, une `currency`, la référence du client final et la référence de l'achat.

[Créer une intention de paiement](/api-reference/payments/create-a-paymentintent-object)

<CodeGroup>
  ```bash Curl theme={null}
  curl --location --request POST 'https://api.hub2.io/payment-intents' \
  --header 'ApiKey: [REDACTED]' \
  --header 'MerchantId: [REDACTED]' \
  --header 'Environment: sandbox' \
  --header 'Content-Type: application/json' \
  --data-raw '{
      "customerReference": "Test_01",
      "purchaseReference": "Test_YYYY_MM_DD_01",
      "amount": 200,
      "currency": "XOF"
  }'
  ```

  ```typescript Typescript theme={null}
  import fetch from 'node-fetch';

  async function postPaymentIntents() {
      const response = await fetch('https://api.hub2.io/payment-intents', {
          method: 'POST',
          headers: {
              'ApiKey': '[REDACTED]',
              'MerchantId': '[REDACTED]',
              'Environment': 'sandbox',
              'Content-Type': 'application/json'
          },
          body: JSON.stringify({
              "customerReference": "Test_01",
              "purchaseReference": "Test_YYYY_MM_DD_01",
              "amount": 200,
              "currency": "XOF"
          })
      });

      if (response.ok) {
          const result = await response.json();
          console.log(result);
      } else {
          console.log(`HTTP Status: ${response.status}`);
      }
  }

  postPaymentIntents();
  ```

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

  url = 'https://api.hub2.io/payment-intents'

  headers = {
      'ApiKey': '[REDACTED]',
      'MerchantId': '[REDACTED]',
      'Environment': 'sandbox',
      'Content-Type': 'application/json',
  }

  data = {
      "customerReference": "Test_01",
      "purchaseReference": "Test_YYYY_MM_DD_01",
      "amount": 200,
      "currency": "XOF"
  }

  response = requests.post(url, headers=headers, data=json.dumps(data))

  if response.status_code == 200:
      print(response.json())
  else:
      print(f'HTTP Status: {response.status_code}')
  ```
</CodeGroup>

<Warning>
  **Cet ID et ce jeton quelque part dans l'infrastructure du marchand, ils seront nécessaires lors des étapes suivantes.**
</Warning>

* L'`id` est la référence unique de l'intention de paiement `PaymentIntent`, il sera utilisé lors des tentatives de paiement
* Le `token` est un *JSON Web Token* (JWT) qui sera utilisé pour authentifier les requêtes de paiement

```json theme={null}
{
    "id": "pi_tffiDaXyWQu203rIXhujW",
    "createdAt": "2022-07-18T06:35:25.932Z",
    "updatedAt": "2022-07-18T06:35:25.944Z",
    "merchantId": "[REDACTED]",
    "purchaseReference": "Test_YYYY_MM_DD_01",
    "customerReference": "Test_01",
    "amount": 200,
    "currency": "XOF",
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpbnRlbnRJZCI6InBpX3RmZmlEYVh5V1F1MjAzcklYaHVqVyIsIm1lcmNoYW50SWQiOiIzIiwibW9kZSI6InNhbmRib3giLCJpYXQiOjE2NTgxMjYxMjV9.1eB6ifC2ldfRw5UstnVa-bQqIdx9_IGLRdwWyXzZR4o",
    "status": "payment_required",
    "payments": [],
    "mode": "sandbox"
}
```

### Restrictions

Lors de la création d'une intention de paiement, des vérifications sont faites sur le champ reference : aucun caractère spécial n'est permis. L'utilisation de caractères spéciaux ici produira une erreur 400 Bad Request.

Les caractères autorisés sont les lettres, les chiffres, les tirets, les caractères de soulignement, les points ou les espaces. Voici la liste sous forme d'expression régulière : A-Za-z0-9-\_. .

## Tentative de paiement sur une intention de paiement

Une fois l'intention de paiement (`PaymentIntent`) créée, il sera possible de collecter les informations de paiement des client et de faire une tentative de `Payment`.

Pour ce faire `Payment`, vous devez spécifier une `paymentMethod` ainsi que tout les champs requis décris dans la référence de notre api.

[Tenter un paiement](/api-reference/payments/attempt-a-payment-on-a-paymentintent-object)

Puisque ce point de terminaison ne nécessite pas de clé API privée `API_KEY`, la requête de paiement peut être faite directement **côté client** en utilisant le JWT `token` enregistré au préalable pour authentifier la requête.

<CodeGroup>
  ```bash Curl theme={null}
  curl --location --request POST 'https://api.hub2.io/payment-intents/pi_tffiDaXyWQu203rIXhujW/payments' \
  --header 'Content-Type: application/json' \
  --data-raw '{
      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpbnRlbnRJZCI6InBpX3RmZmlEYVh5V1F1MjAzcklYaHVqVyIsIm1lcmNoYW50SWQiOiIzIiwibW9kZSI6InNhbmRib3giLCJpYXQiOjE2NTgxMjYxMjV9.1eB6ifC2ldfRw5UstnVa-bQqIdx9_IGLRdwWyXzZR4o",
      "paymentMethod": "mobile_money",
      "country": "CI",
      "provider": "orange",
      "mobileMoney": {
          "msisdn": "00000001"
      }
  }'
  ```

  ```typescript Typescript theme={null}
  import fetch from 'node-fetch';

  async function postPaymentIntents() {
      const response = await fetch('https://api.hub2.io/payment-intents/pi_tffiDaXyWQu203rIXhujW/payments', {
          method: 'POST',
          headers: {
              'Content-Type': 'application/json'
          },
          body: JSON.stringify({
              "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpbnRlbnRJZCI6InBpX3RmZmlEYVh5V1F1MjAzcklYaHVqVyIsIm1lcmNoYW50SWQiOiIzIiwibW9kZSI6InNhbmRib3giLCJpYXQiOjE2NTgxMjYxMjV9.1eB6ifC2ldfRw5UstnVa-bQqIdx9_IGLRdwWyXzZR4o",
              "paymentMethod": "mobile_money",
              "country": "CI",
              "provider": "orange",
              "mobileMoney": {
                  "msisdn": "00000001"
              }
          })
      });

      if (response.ok) {
          const result = await response.json();
          console.log(result);
      } else {
          console.log(`HTTP Status: ${response.status}`);
      }
  }

  postPaymentIntents();
  ```

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

  url = 'https://api.hub2.io/payment-intents/pi_tffiDaXyWQu203rIXhujW/payments'

  headers = {
      'Content-Type': 'application/json',
  }

  data = {
      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpbnRlbnRJZCI6InBpX3RmZmlEYVh5V1F1MjAzcklYaHVqVyIsIm1lcmNoYW50SWQiOiIzIiwibW9kZSI6InNhbmRib3giLCJpYXQiOjE2NTgxMjYxMjV9.1eB6ifC2ldfRw5UstnVa-bQqIdx9_IGLRdwWyXzZR4o",
      "paymentMethod": "mobile_money",
      "country": "CI",
      "provider": "orange",
      "mobileMoney": {
          "msisdn": "00000001"
      }
  }

  response = requests.post(url, headers=headers, data=json.dumps(data))

  if response.status_code == 200:
      print(response.json())
  else:
      print(f'HTTP Status: {response.status_code}')
  ```
</CodeGroup>

Si la requête est en succès, un code `HTTP 201` sera retourné avec le `PaymentIntent` dans la réponse de la requête.

Constatez que le statut est maintenant `processing` et que le tableau `payments` contient la nouvelle tentative de paiement `Payment`.

L'ID du paiement peut maintenant être enregistré pour identifier cette tentative de paiement dans la liste, puisque *plus d'une tentative de paiement* peut être effectuée pour **une seule** intention de paiement `PaymentIntent`.

```
{
    "id": "pi_tffiDaXyWQu203rIXhujW",
    "createdAt": "2022-07-18T06:35:25.932Z",
    "updatedAt": "2022-07-18T06:39:21.471Z",
    "merchantId": "XXXXX",
    "purchaseReference": "Test_YYYY_MM_DD_01",
    "customerReference": "Test_01",
    "amount": 200,
    "currency": "XOF",
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpbnRlbnRJZCI6InBpX3RmZmlEYVh5V1F1MjAzcklYaHVqVyIsIm1lcmNoYW50SWQiOiIzIiwibW9kZSI6InNhbmRib3giLCJpYXQiOjE2NTgxMjYxMjV9.1eB6ifC2ldfRw5UstnVa-bQqIdx9_IGLRdwWyXzZR4o",
    "status": "processing",
    "payments": [
        {
            "id": "pay_GBc53h6dHvuuq4vlcP6dY",
            "intentId": "pi_tffiDaXyWQu203rIXhujW",
            "createdAt": "2022-07-18T06:39:20.721Z",
            "updatedAt": "2022-07-18T06:39:21.471Z",
            "amount": 207,
            "currency": "XOF",
            "status": "created",
            "method": "mobile_money",
            "country": "CI",
            "provider": "orange",
            "number": "00000001",
            "fees": [
                {
                    "currency": "XOF",
                    "id": "fee_drJQNmGYKQAqT7oulY519",
                    "label": "payments.customer_fee",
                    "rate": 3,
                    "rateType": "percent",
                    "amount": 7
                }
            ]
        }
    ],
    "mode": "sandbox"
}
```

## Récupérer le statut du paiement

Après une tentative de paiement, HUB2 le fournisseur et met à jour l'objet de paiement selon la réponse.

À partir de là, le statut du paiement peut être récupéré de plusieurs façons.

### 1. Enregistrer un webhook pour les événements de paiement (méthode recommandée)

Au lieu de faire du polling pour détecter la mise à jour du statut du `PaymentIntent`, il est recommandé d'utiliser les webhooks.

Veuillez regarder à la page [Intégration webhooks](/integration/fr/webhooks) pour voir comment les implémenter.

Souscrivez aux événements `payment` ou `payment_intents`.

En utilisant cette méthode, le serveur du marchand sera notifié dès que le `Payment` ou le `PaymentIntent` sera mis à jour.

De cette manière, l'implémentation de mécanismes de polling est inutile, les limites de flux ne sont pas subies, et une charge potentielle due à des problèmes de temps de réponse est ainsi évitée.

Aussi, en cas de trafic élevé, la charge sur les serveurs sera réduite en conséquence, tant du côté du marchand que de celui d'HUB2.

### 2. Polling de statut

Effectuer un polling sur le statut du paiement en utilisant le point de terminaison dédié dans l'api.

[Retrieve the status payment object](/api-reference/payments/retrieve-the-status-payment-object)

<Warning>
  Afin de prévenir toute attaque par déni de service, ce point de terminaison est limité. Des retours `HTTP 429 - Too Many Requests` seront effectués dans ce cas. Il faut prendre cela en considération lors de l'implémentation du polling.
</Warning>

Voici un exemple de requête vers ce endpoint :

<CodeGroup>
  ```bash Curl theme={null}
  curl --location --request GET 'https://api.hub2.io/payments/pay_GBc53h6dHvuuq4vlcP6dY/status' \
  --header 'ApiKey: [REDACTED]' \
  --header 'MerchantId: [REDACTED]' \
  --header 'Environment: sandbox'
  ```

  ```typescript Typescript theme={null}
  import fetch from 'node-fetch';

  async function getPaymentStatus() {
      const response = await fetch('https://api.hub2.io/payments/pay_GBc53h6dHvuuq4vlcP6dY/status', {
          method: 'GET',
          headers: {
              'ApiKey': '[REDACTED]',
              'MerchantId': '[REDACTED]',
              'Environment': 'sandbox'
          }
      });

      if (response.ok) {
          const result = await response.json();
          console.log(result);
      } else {
          console.log(`HTTP Status: ${response.status}`);
      }
  }

  getPaymentStatus();
  ```

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

  url = 'https://api.hub2.io/payments/pay_GBc53h6dHvuuq4vlcP6dY/status'

  headers = {
      'ApiKey': '[REDACTED]',
      'MerchantId': '[REDACTED]',
      'Environment': 'sandbox',
  }

  response = requests.get(url, headers=headers)

  if response.status_code == 200:
      print(response.json())
  else:
      print(f'HTTP Status: {response.status_code}')
  ```
</CodeGroup>

Cela va récupérer le statut d'un paiement, mais il existe aussi un point de terminaison pour récupérer le statut d'une intention de paiement si cela est nécessaire.

[Retrieve a payment intent status](/api-reference/payments/retrieve-a-paymentintent-status)

<Card title="Important">
  Le polling devrait être utilisé seulement si le `PaymentIntent`.`status` est `processing` ou `action_required`. Continuer le polling lors de statut `payment_required`, `successful` ou `failed` peut conclure par l'adresse IP du marchand limitée ou temporairement **bannie**.
</Card>

Dans la plupart des cas, le statut du `PaymentIntent` sera `action_required`. Ce statut indique que la tentative de paiement est en attente d'une action manuelle du client final, qui doit *authentifier* ou *valider* le paiement.

## Gérer l'action utilisateur

L'objet `Payment` contiendra un objet `nextAction` qui décrit le type d'action que le client final doit effectuer.

```json theme={null}
{
    "id": "pi_tffiDaXyWQu203rIXhujW",
    "...": "...",
    "amount": 200,
    "currency": "XOF",
    "status": "action_required",
    "payments": [
        {
            "id": "pay_GBc53h6dHvuuq4vlcP6dY",
            "intentId": "pi_tffiDaXyWQu203rIXhujW",
            "amount": 207,
            "currency": "XOF",
            "status": "pending",
            "method": "mobile_money",
            "country": "CI",
            "provider": "orange",
            "number": "00000001",
            "fees": ["..."],
            "nextAction": {
                "type": "otp",
                "message": "Mode Sandbox. Entrez un numéro à 4 chiffres pour authentifier le paiement."
            }
        }
    ],
    "nextAction": {
        "type": "otp",
        "message": "Mode Sandbox. Entrez un numéro à 4 chiffres pour authentifier le paiement."
    }
}
```

<Card title="Important">
  Voici la partie qui nous intéresse : `"nextAction": { "type": "otp", "message": "Mode Sandbox. Entrez un numéro à 4 chiffres pour authentifier le paiement." }`.
</Card>

Le message `nextAction.message` devrait maintenant être affiché au client final pour lui indiquer les actions à entreprendre afin de valider le paiement. L'action spécifie les étapes que le client doit suivre:

* L'action `redirection` fournit les informations de redirection à une page externe.
* L'action `ussd` indique la procédure USSD à suivre pour valider le paiement.
* L'action `otp` indique à l'utilisateur comment générer un OTP. Ce type d'action nécessite que le client saisisse le code OTP dans un champ qui sera envoyé au [point de terminaison api](/api-reference/payments/authenticate-the-current-payment). Veuillez noter que certains fournisseurs permettent de passer le code OTP directement dans la requête de paiement (voir champ `otp` dans l'objet `mobileMoney`).

## Paiement par virement (Bank Collection)

La fonctionnalité "Bank Collection" permet à un marchand de proposer à ses clients une option de paiement par virement. Le client reçoit une référence unique pour effectuer le virement.

### Prérequis

Pour utiliser ce circuit de paiement, il est nécessaire d'avoir effectué au préalable un enregistrement KYB (Know Your Business).
[Effectuer une déclaration KYB](/api-reference/identity/create-a-kyb-object)

### Processus de paiement

1. Le marchand crée une intention de paiement (`PaymentIntent`) comme décrit précédemment
2. Pour la tentative de paiement, le marchand spécifie `bank_transfer` comme méthode de paiement
3. Le système génère une référence unique pour le virement
4. Le client reçoit cette référence et peut effectuer le virement
5. Le statut du paiement est mis à jour une fois le virement reçu (mise à jour automatique ou manuelle)

[Tenter un paiement](/api-reference/payments/attempt-a-payment-on-a-paymentintent-object)

<CodeGroup>
  ```bash Curl theme={null}
  curl --location --request POST 'https://api.hub2.io/payment-intents/pi_tffiDaXyWQu203rIXhujW/payments' \
  --header 'Content-Type: application/json' \
  --data-raw '{
      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpbnRlbnRJZCI6InBpX3RmZmlEYVh5V1F1MjAzcklYaHVqVyIsIm1lcmNoYW50SWQiOiIzIiwibW9kZSI6InNhbmRib3giLCJpYXQiOjE2NTgxMjYxMjV9.1eB6ifC2ldfRw5UstnVa-bQqIdx9_IGLRdwWyXzZR4o",
      "paymentMethod": "bank_transfer",
      "country": "CI",
      "provider": "Bank",
      "bankTransfer": {
          "kybName": "Name declared in KYB",
          "expirationDelay": 3600
      }
  }'
  ```

  ```typescript Typescript theme={null}
  import fetch from 'node-fetch';

  async function postPaymentIntents() {
      const response = await fetch('https://api.hub2.io/payment-intents/pi_tffiDaXyWQu203rIXhujW/payments', {
          method: 'POST',
          headers: {
              'Content-Type': 'application/json'
          },
          body: JSON.stringify({
              "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpbnRlbnRJZCI6InBpX3RmZmlEYVh5V1F1MjAzcklYaHVqVyIsIm1lcmNoYW50SWQiOiIzIiwibW9kZSI6InNhbmRib3giLCJpYXQiOjE2NTgxMjYxMjV9.1eB6ifC2ldfRw5UstnVa-bQqIdx9_IGLRdwWyXzZR4o",
              "paymentMethod": "bank_transfer",
              "country": "CI",
              "provider": "Bank",
              "bankTransfer": {
                  "kybName": "Name declared in KYB",
                  "expirationDelay": 3600
              }
          })
      });

      if (response.ok) {
          const result = await response.json();
          console.log(result);
      } else {
          console.log(`HTTP Status: ${response.status}`);
      }
  }

  postPaymentIntents();
  ```

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

  url = 'https://api.hub2.io/payment-intents/pi_tffiDaXyWQu203rIXhujW/payments'

  headers = {
      'Content-Type': 'application/json',
  }

  data = {
      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpbnRlbnRJZCI6InBpX3RmZmlEYVh5V1F1MjAzcklYaHVqVyIsIm1lcmNoYW50SWQiOiIzIiwibW9kZSI6InNhbmRib3giLCJpYXQiOjE2NTgxMjYxMjV9.1eB6ifC2ldfRw5UstnVa-bQqIdx9_IGLRdwWyXzZR4o",
      "paymentMethod": "bank_transfer",
      "country": "CI",
      "provider": "Bank",
      "bankTransfer": {
          "kybName": "Name declared in KYB",
          "expirationDelay": 3600
      }
  }

  response = requests.post(url, headers=headers, data=json.dumps(data))

  if response.status_code == 200:
      print(response.json())
  else:
      print(f'HTTP Status: {response.status_code}')
  ```
</CodeGroup>

### Réponse de l'API

En cas de succès, l'API retournera un objet `PaymentIntent` contenant les détails du paiement par virement, y compris la référence unique générée pour le virement.

<Warning>
  **Important**: La référence de virement doit être communiquée au client final pour qu'il puisse effectuer le virement. Le statut du paiement sera mis à jour une fois le virement reçu et traité.
</Warning>
