> ## 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.

# Integration

This guide will describe the process, step by step:

1. Create the payment intent `PaymentIntent`
2. Attempt a payment
3. (Optional) On client's side, authenticate the payment attempt
4. HUB2's side: Processing the payment with the providers/payment platforms
5. Fetching the payment intent `PaymentIntent`'s status

Here are the detailed steps on how to proceed with these actions.

## Create a payment intent

The `PaymentIntent` object is used to represent the intent to collect a payment from a customer. It keeps track of fees and various payment attempts throughout the processing.

The `PaymentIntent` must be created on **the merchant server** with an `amount`, a `currency`, your customer reference and your purchase reference.

[Create a payment intent](/api-reference/payments/create-a-paymentintent-object)

Here is a sample of request to that endpoint:

<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>
  **This ID and this token should be saved somewhere in the merchant infrastructure, it will be required for the next steps.**
</Warning>

* The `id` is the unique reference to the `PaymentIntent`, it will be used to attempt payments
* The `token` is a *JSON Web Token* (JWT) that will be used to authenticate the payment request

```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

When creating a payment intent, checks are performed on the field `reference` : no special characters are allowed. Trying to use special characters here will result in a `400 Bad Request` error.

Allowed characters are letters, numbers, hyphen, underscore, dot and space. Here's the list in the regular expression format : `A-Za-z0-9\-_. `.

## Attempt a payment on the Payment Intent

Once the `PaymentIntent` is successfully created, it will be possible to collect payment information with the final customer and attempt a `Payment`.

To attempt a `Payment`,  the `paymentMethod` and all required field that are described in the API reference must be specified .

[Attempt a payment](/api-reference/payments/attempt-a-payment-on-a-paymentintent-object)

As this route does not require your private `API_KEY`, the payment request could be made directly on the **client side** (from the client himself), using the previously retrieved JWT `token` to authenticate the request.

<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>

If the request is successful, a `HTTP 201` with the full `PaymentIntent` object in the response body will be returned.

Notice that the status is now `processing` and the `payments` array contains the newly created `Payment` attempt.

The payment `id` can now be saved to identify your payment attempt in the list as *more than one payment* may be attempted for **one** `PaymentIntent`.

```json theme={null}
{
    "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"
}
```

## Retrieve Payment status

After attempting the payment, HUB2 will contact the payment provider and update the payment object according to its response.

From this point, the payment status can be retrieved in two different ways.

### 1. Register a Webhook for payment events (recommended method)

Instead of polling `PaymentIntent` for status updates, we recommend to use webhooks.

Please take a look at [Webhooks Integration](/integration/en/webhooks) to see how to implement them.

Register to `payment` or `payment_intents` related events.

By using this method, the merchant server will be notified once the `Payment` or the `PaymentIntent` is updated.

This way, implementing mechanisms to poll the status is not necessary, rate limiting won't be applied and potential latency issues will be avoided.

Also in case of high payment traffic, it will reduce the load on the merchant server and consequently on HUB2 servers.

### 2. Status Polling

Start polling the payment status using the dedicated API endpoint.

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

<Warning>
  To prevent DoS attack, this route is rate limited, a `HTTP 429 - Too Many Requests` will be returned in this case. Please take this in consideration while implementing the check status polling.
</Warning>

Here is a sample of request on that 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>

This will fetch a payment status, but there's another endpoint dedicated on fetching a payment intent status if need be.

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

<Card title="Important">
  Polling should be used only if `PaymentIntent`.`status` is `processing` OR `action_required`. Continuously polling `payment_required`, `successful` or `failed` `PaymentIntent` will result in source IP address being throttled or temporarily **banned**.
</Card>

In most cases, the next `PaymentIntent` status will be `action_required`. This status mean that the payment attempt requires a manual action from the user to *authenticate* or *validate* the payment.

## Handle user action

The `Payment` object will contain a `nextAction` object that will describe the type of the action that will be required from the user.

```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">
  This is the part we are interested in : `"nextAction": { "type": "otp", "message": "Mode Sandbox. Entrez un numéro à 4 chiffres pour authentifier le paiement." }`.
</Card>

The `nextAction.message` should now be displayed to the final customer to let him know what he should do. The action indicates the customer to validate the Payment. There are different types for the action :

* A `redirection` action type provides all the information to redirect the customer to an external page.
* A `ussd` action type indicates to the user the USSD procedure to follow to validate the payment.
* A `otp` action type indicates to the final customer how to generate an One Time Password (OTP). This type of action requires the client to enter the OTP code into a field that will be sent to the [dedicated API Route](/api-reference/payments/authenticate-the-current-payment). Please note that some providers allow to pass the OTP code directly in the payment attempt request (see `otp` field `mobileMoney` object).

## Bank Collection

The "Bank Collection" feature allows a merchant to offer their customers a bank transfer payment option. The customer receives a unique reference to make the transfer.

### Prerequisites

To use this payment method, it is necessary to have completed a KYB (Know Your Business) registration beforehand.

[Create a KYB registration](/api-reference/identity/create-a-kyb-object)

### Payment Process

1. The merchant creates a payment intent (`PaymentIntent`) as described previously
2. For the payment attempt, the merchant specifies `bank_transfer` as the payment method
3. The system generates a unique reference for the transfer
4. The customer receives this reference and can make the transfer
5. The payment status is updated once the transfer is received (automatic or manual update)

[Attempt a payment](/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>

### API Response

Upon success, the API will return a `PaymentIntent` object containing the bank transfer payment details, including the unique reference generated for the bank transfer.

<Warning>
  **Important**: The reference must be communicated to the end customer so they can make the bank transfer. The payment status will be updated once it is received and processed.
</Warning>
