# Introduction

The DusuPay platform enables merchants (businesses and online platforms) to process payments across multiple payment options. The document describes the current version of the API.

Welcome to the DusuPay API. This API enables merchants (businesses and other online platforms) to integrate online payments into their business processes and support payments for goods and services. Our developer friendly API gives you access to mobile money, bank, card and crypto integrations across a number of countries. More payment options to be enabled in due course and the document will be updated accordingly. With this platform, merchants are able to request payments from valid mobile money subscribers, card holders, bank accounts and crypto wallets. Merchants can equally send funds via mobile money, bank accounts and soon to crypto wallets

## Platform Features

### Sandbox Environment

This is a utility platform built to allow merchants simulate the workflows of the production/live environment. The sandbox environment offers an API that the merchant integrates first to test the platform and perform UATs. It offers test accounts that the merchant can use to try out the different operations of the API without affecting customer funds in the real world. When all tests are done to the satisfaction of the merchant and the DusuPay support staff, the merchant can then be approved to go live.

{% hint style="info" %}
We intend to make your migration from sandbox to production very smooth. You'll strictly need to change the API base URL and obtain a new set of credentials (security keys). Everything else will work as expected.
{% endhint %}

### Production Environment

Under this environment, real funds can be requested from real customers and real funds sent to real customers. In order for the merchant to be accepted to use the production environment, they will have completed the compliance/KYC phase and also integrated the sandbox API for all the required tests.

### Async Processing

The DusuPay API is asynchronous; meaning the merchant doesn't need to wait on the same request thread to obtain the new status of the transaction. The confirmation of the transaction status will be via a callback/webhook/IPN POST request. We will therefore require that you share the **https** URLs for your callback endpoints and these will be configured on the merchant account accordingly. Different callback URLs are configured for collections and payouts.

### Transparency

DusuPay provides a management portal to all merchants. This is the central point to manage your business. It allows for creation of multiple merchant accounts and provides reports for all the activity on the different accounts. It also offers accounts statements to reflect how the merchant balances have changed over time. For every merchant account, API settings can be configured to support the integration.

### API Endpoints

The DusuPay API can be accessed using the following base URLs. Both URLs will only be accessible when the merchant account is created and approved on either environment.

<table><thead><tr><th width="151.77069091796875">Environment</th><th>API Base URL</th></tr></thead><tbody><tr><td>Sandbox/Test</td><td>https://sandboxapi.dusupay.com/</td></tr><tr><td>Production/Live</td><td>https://payments.dusupay.com/</td></tr></tbody></table>

### Postman Collection

The Postman Collection is a great way to quickly explore the DusuPay APIs without writing any code. The collection contains pre-formatted requests for the different transaction methods/options. Download the collection and add your API keys in order to start interacting with the sandbox APIs.

{% embed url="<https://postman.dusupay.com>" %}


# Registration

Any entity interested in the DusuPay  API needs to register and fulfil the compliance/KYC requirements before they are granted access to the platform.

## Required Documents

Please organize and share copies of the following items for account approval.

1. Registered & Certified Certificate of Incorporation (for registered companies) OR Certificate of Registration (for registered business names).
2. TIN Certificate (for registered companies)
3. Statement of Particulars of the members.
4. Valid Trading License.
5. Identity Documents of Primary and related parties (National ID (for nationals) OR Passport (for Foreigners) OR Refugee ID and Refugee attestations (for refugees).
6. Identity documents of all UBOs (Ultimate Beneficial Owners) of the company.
7. Proof of company address (tenancy agreement or utility bill etc)
8. Brief company profile including the website details

{% hint style="info" %}
Please write an email to us at **<legal@dusupay.com>** to request for the API connection and share all the requirements as described in this section. Our compliance department will do due-diligence and once approved, the API account will be created, and details shared accordingly.
{% endhint %}


# Error Handling

The DusuPay API uses HTTP status codes to indicate the success or failure of the API requests. The section describes the summary of these codes and what they mean in our context.

## Summary

* Codes in the 2XX range mean that the API request was processed successfully.
* Codes in the 4XX range mean that something was wrong with the data that you sent. For example, you might have missed some required parameters/headers, or you might be using the wrong API credentials.
* Codes in the 5XX range indicate an error in processing on our end

## Common HTTP Codes

<table><thead><tr><th width="132.57142857142856">Code</th><th>Description</th></tr></thead><tbody><tr><td>200</td><td>OK - Request was successful</td></tr><tr><td>202</td><td>Accepted - Request has been accepted and acknowledged. We will now go ahead to process the request and notify you of the status afterwards.</td></tr><tr><td>400</td><td>Bad Request - Malformed request or missing required parameters</td></tr><tr><td>401</td><td>Unauthorized - Missing required headers, wrong Public or Secret Key etc</td></tr><tr><td>403</td><td>Forbidden - You are trying to access a resource for which you don't have proper access rights.</td></tr><tr><td>404</td><td>Not Found - You are trying to access a resource that does not exist</td></tr><tr><td>422</td><td>Unprocessable Entity - You provided all the required parameters, but they are not proper for the request</td></tr><tr><td>500</td><td>Internal Server Error - We had a glitch in our servers. Retry the request in a little while or contact support</td></tr><tr><td>503</td><td>Service Unavailable – We are temporarily offline for maintenance. Please try again later.</td></tr></tbody></table>


# Authentication

The DusuPay API uses a key pair (Public and Secret Keys) for authentication. To make an authenticated request to our API, you need to pass custom headers as described in this section.

## Obtain the Keys

The Public and Secret keys can be copied from the merchant dashboard under the specific merchant account details as described [here](/getting-started/merchant-account-credentials). These keys should be sent as headers with the header names; **public-key** and **secret-key**

{% hint style="info" %}
Please ensure that your keys are stored safely and not shared with the public. In the event your keys are compromised, please contact us immediately for assistance. You an alternatively regenerate the secret key from within your merchant account dashboard
{% endhint %}

## Request Headers

For most of the API requests, only the public-key header is required. Scenarios that require the secret-key header will be described accordingly.

<table><thead><tr><th width="154.33333333333331">Header Name</th><th width="487">Value</th><th>Required</th></tr></thead><tbody><tr><td>Content-Type</td><td>application/json</td><td>YES</td></tr><tr><td>x-api-version</td><td>API version (currently 1)</td><td>YES</td></tr><tr><td>public-key</td><td>The Public Key as obtained from the merchant account settings</td><td>YES</td></tr><tr><td>secret-key</td><td>The Secret Key as obtained from the merchant account settings. Required for specific situations as the document will guide</td><td>NO</td></tr></tbody></table>


# Merchant Account Credentials

For every approved merchant account, a set of credentials is automatically generated and stored on the record. This section describes how the merchant can manage credentials after the account creation

As described [here](/getting-started/authentication), the API requires that certain headers are sent for the various requests. To be specific, the `public-key`header is mandatory for all the API requests and the `secret-key` is required only in a few situations as the documentation will advise along the way. The other critical variable is the **signing key**, a randomly generated string stored on the merchant account record, whose sole purpose is to support the generation of the HMAC Hash signature sent alongside the merchant callbacks.

When the merchant account is created and approved by the admins, all the above 3 mentioned values are generated and securely stored. The public-key (as its name suggests) is displayed among the merchant account details in plain text. The signing key too is displayed in plain text for the merchant to view it. The secret key on the other hand is stored as a strongly hashed value and the gateway team never gets to see the plain text version of this secret key. The merchant should therefore generate a new secret key from their dashboard in order to temporarily see the secret key in plain text and get a chance to store is somewhere safe.

The merchant is at liberty to re-generate just the secret key OR all the keys mentioned above that way in case of compromise, the merchant has full control of the keys change without needing the gateway support team. The table below advises further.

<table><thead><tr><th width="537">Scenario/Use case</th><th>Resource</th></tr></thead><tbody><tr><td>If you wish to generate only the secret key and the other keys remain the same</td><td><a href="/pages/Wa2N8q7PKYtcGz2XNHc3">Generate Secret Key</a></td></tr><tr><td>If you wish to generate a fresh set of credentials and replace all the existing ones</td><td><a href="/pages/OXheYU893mMDDLzs3jyQ">Regenerate Security Keys</a></td></tr></tbody></table>

{% hint style="info" %}
We strongly recommend routine change of the credentials as a security measure especially in situations where integration work is outsourced to parties external to the merchant/organization.
{% endhint %}


# Generate Secret Key

The section below describes (with screenshots) how to generate and store a new secret key. The process assumes that you're already logged in to the merchant dashboard.

#### Step 1: Click "**Merchant Profile**" Menu.

<figure><img src="/files/Xympy59PZzKheN7tivMo" alt=""><figcaption></figcaption></figure>

#### Step 2: Edit the Merchant Profile

<figure><img src="/files/FMIR23QUIzdstru4Ulxs" alt=""><figcaption></figcaption></figure>

#### Step 3: Click the "**Edit**" button on the Merchant Account of your choice

<figure><img src="/files/Pnt1DklINXHddZPBrSp8" alt=""><figcaption></figcaption></figure>

#### Step 4: Click "**Generate New Secret Key**" button.

<figure><img src="/files/yksaq9KptG4rqOvl4nJk" alt=""><figcaption></figcaption></figure>

#### Step 5: From the popup dialog, click "**Request New Secret Key**" Button.

<figure><img src="/files/9PfJwqq1z9PRgjbQSwCR" alt=""><figcaption></figcaption></figure>

#### Step 6: Close the dialog after copying the secret key

<figure><img src="/files/dLkbhRgO5vLlWNXNAr22" alt=""><figcaption></figcaption></figure>


# Regenerate Security Keys

The section below describes (with screenshots) how to generate a new set of security keys. The process assumes that you're already logged in to the merchant dashboard.

#### Step 1: Click "**Merchant Profile**" Menu.

<figure><img src="/files/Xympy59PZzKheN7tivMo" alt=""><figcaption></figcaption></figure>

#### Step 2: Edit the Merchant Profile

<figure><img src="/files/FMIR23QUIzdstru4Ulxs" alt=""><figcaption></figcaption></figure>

#### Step 3: Click the "**Edit**" button on the Merchant Account of your choice

<figure><img src="/files/Pnt1DklINXHddZPBrSp8" alt=""><figcaption></figcaption></figure>

#### Step 4: Click "**Generate New Keys**" button.

<figure><img src="/files/nDwHj3QMHcUS0KmEQLDo" alt=""><figcaption><p>Generate New Keys</p></figcaption></figure>

#### Step 5: From the popup dialog, click "**Generate a set of New Keys**" Button.

<figure><img src="/files/9znT82JlZ8yIjxJldfCh" alt=""><figcaption><p>Generate a set of New Keys</p></figcaption></figure>

<figure><img src="/files/eIaXEK5SCL2KHsyiGnsw" alt=""><figcaption><p>Confirmation Dialog window on Generate a set of New Keys</p></figcaption></figure>

#### Step 6: Copy the keys and click "**Close, I have Saved my keys**" button.

<figure><img src="/files/LO5rn7VajZRjqZbxz4Tf" alt=""><figcaption></figcaption></figure>


# Supported Countries/Regions

The DusuPay platform supports a wide variety of payment methods, transaction types and currencies across a number of countries or regions. Merchant accounts will be enabled for only what they request.

Below is a list of the supported countries/regions, the acceptable transaction methods and types plus the official transaction currencies that apply to the respective regions.

<table><thead><tr><th width="82.6446533203125">Code</th><th width="132.658203125">Name</th><th width="321.0758056640625">Transaction Method(s)</th><th>Transaction Currency</th></tr></thead><tbody><tr><td>UG</td><td>Uganda</td><td>MOBILE_MONEY (Collection, Payout)</td><td>UGX</td></tr><tr><td>KE</td><td>Kenya</td><td>MOBILE_MONEY (Collection, Payout)</td><td>KES</td></tr><tr><td>TZ</td><td>Tanzania</td><td>MOBILE_MONEY (Collection, Payout)</td><td>TZS</td></tr><tr><td>RW</td><td>Rwanda</td><td>MOBILE_MONEY (Collection, Payout)</td><td>RWF</td></tr><tr><td>GH</td><td>Ghana</td><td>MOBILE_MONEY (Collection, Payout)</td><td>GHS</td></tr><tr><td>CM</td><td>Cameroon</td><td>MOBILE_MONEY (Collection, Payout)</td><td>XAF</td></tr><tr><td>ZA</td><td>South Africa</td><td>BANK (Collection, Payout)</td><td>ZAR</td></tr><tr><td>NG</td><td>Nigeria</td><td>BANK (Collection, Payout)</td><td>NGN</td></tr><tr><td>CI</td><td>Ivory Coast</td><td>MOBILE_MONEY (Collection, Payout)</td><td>XOF</td></tr><tr><td>BJ</td><td>Benin</td><td>MOBILE_MONEY (Collection, Payout)</td><td>XOF</td></tr><tr><td>ML</td><td>Mali</td><td>MOBILE_MONEY (Collection, Payout)</td><td>XOF</td></tr><tr><td>TG</td><td>Togo</td><td>MOBILE_MONEY (Collection, Payout)</td><td>XOF</td></tr><tr><td>SN</td><td>Senegal</td><td>MOBILE_MONEY (Collection, Payout)</td><td>XOF</td></tr><tr><td>BF</td><td>Burkina Faso</td><td>MOBILE_MONEY (Collection, Payout)</td><td>XOF</td></tr><tr><td>GN</td><td>Guinea Conakry</td><td>MOBILE_MONEY (Collection, Payout)</td><td>GNF</td></tr></tbody></table>


# Transaction Limits

For some of the transaction currencies that DusuPay supports, there are limits that control what a single transaction account is able to do per transaction and in a 24hr period.

The tables below describe the accepted amount limits for the respective currencies for both collections and payouts.

### Collection Limits

<details>

<summary>XAF</summary>

| Country/Region | Payment Option   | Min. Trans. Amount | Max. Trans. Amount | Max Day Limit |
| -------------- | ---------------- | -----------------: | -----------------: | ------------: |
| CM             | MTN Mobile Money |                540 |          1,000,000 |     2,000,000 |
| CM             | Orange Money     |                540 |          1,000,000 |     2,000,000 |

</details>

<details>

<summary>XOF</summary>

| Country/Region | Payment Option          | Min. Trans. Amount | Max. Trans. Amount | Max Day Limit |
| -------------- | ----------------------- | -----------------: | -----------------: | ------------: |
| CI             | MTN, Moov & Orange      |                600 |            500,000 |     1,000,000 |
| BJ             | MTN & Moov              |                600 |            500,000 |     1,000,000 |
| ML             | Orange & Moov           |                600 |            500,000 |     1,000,000 |
| TG             | T-Money & Moov          |                600 |            500,000 |     1,000,000 |
| SN             | Free Senegal & Expresso |                600 |            500,000 |     1,000,000 |
| BF             | Orange & Moov           |                600 |            500,000 |     1,000,000 |

</details>

<details>

<summary>GHS</summary>

| Country/Region | Payment Option   | Min. Trans. Amount | Max. Trans. Amount | Max Day Limit |
| -------------- | ---------------- | -----------------: | -----------------: | ------------: |
| GH             | MTN Mobile Money |                 20 |              7,000 |        24,000 |
| GH             | AirtelTigo       |                 20 |              7,000 |        24,000 |
| GH             | Vodafone         |                 20 |              7,000 |        24,000 |

</details>

<details>

<summary>KES</summary>

| Country/Region | Payment Option  | Min. Trans. Amount | Max. Trans. Amount | Max Day Limit |
| -------------- | --------------- | -----------------: | -----------------: | ------------: |
| KE             | Safaricom MPESA |                100 |            100,000 |       140,000 |
| KE             | Airtel          |                100 |            100,000 |       140,000 |

</details>

<details>

<summary>NGN</summary>

| Country/Region | Payment Option    | Min. Trans. Amount | Max. Trans. Amount |
| -------------- | ----------------- | -----------------: | -----------------: |
| NG             | Bank Transfer     |                380 |         10,000,000 |
| NG             | Local Verve Cards |                380 |          1,000,000 |
| NG             | Card              |                380 |          1,000,000 |

</details>

<details>

<summary>RWF</summary>

| Country/Region | Payment Option   | Min. Trans. Amount | Max. Trans. Amount | Max Day Limit |
| -------------- | ---------------- | -----------------: | -----------------: | ------------: |
| RW             | MTN Mobile Money |                980 |          2,000,000 |     4,000,000 |
| RW             | Airtel Money     |                980 |          2,000,000 |     4,000,000 |

</details>

<details>

<summary>TZS</summary>

| Country/Region | Payment Option | Min. Trans. Amount | Max. Trans. Amount | Max Day Limit |
| -------------- | -------------- | -----------------: | -----------------: | ------------: |
| TZ             | Airtel Money   |              2,000 |          5,000,000 |     7,000,000 |
| TZ             | TigoPesa       |              2,000 |          5,000,000 |     7,000,000 |
| TZ             | Vodacom Mula   |              2,000 |          5,000,000 |     7,000,000 |

</details>

<details>

<summary>UGX</summary>

| Country/Region | Payment Option   | Min. Trans. Amount | Max. Trans. Amount | Max Day Limit |
| -------------- | ---------------- | -----------------: | -----------------: | ------------: |
| UG             | MTN Mobile Money |              3,000 |          5,000,000 |     7,000,000 |
| UG             | Airtel Money     |              3,000 |          5,000,000 |     7,000,000 |

</details>

<details>

<summary>ZAR</summary>

| Country/Region | Payment Option | Min. Trans. Amount | Max. Trans. Amount |
| -------------- | -------------- | -----------------: | -----------------: |
| ZA             | Bank EFT       |                 20 |            200,000 |

</details>

<details>

<summary>GNF</summary>

| Country/Region | Payment Option   | Min. Trans. Amount | Max. Trans. Amount | Max Day Limit |
| -------------- | ---------------- | -----------------: | -----------------: | ------------: |
| GN             | Orange Money     |              8,000 |          4,000,000 |     8,000,000 |
| GN             | MTN Mobile Money |              8,000 |          4,000,000 |     8,000,000 |

</details>

### Payout/Disbursement Limits

| Country/Region | Payment Method | Currency | Min. Trans. Amount | Max. Trans. Amount |
| -------------- | -------------- | -------- | -----------------: | -----------------: |
| CM             | Mobile Money   | XAF      |                540 |          1,000,000 |
| GH             | Mobile Money   | GHS      |                 20 |              7,000 |
| KE             | Mobile Money   | KES      |                100 |            150,000 |
| NG             | Bank Transfer  | NGN      |              2,000 |            500,000 |
| RW             | Mobile Money   | RWF      |                980 |          2,000,000 |
| TZ             | Mobile Money   | TZS      |              2,000 |          3,000,000 |
| UG             | Mobile Money   | UGX      |              3,000 |          2,000,000 |
| ZA             | Bank Transfer  | ZAR      |                 20 |             30,000 |
| GN             | Mobile Money   | GNF      |              8,000 |          4,000,000 |
| CI             | Mobile Money   | XOF      |                600 |            500,000 |
| BJ             | Mobile Money   | XOF      |                600 |            500,000 |
| ML             | Mobile Money   | XOF      |                600 |            500,000 |
| TG             | Mobile Money   | XOF      |                600 |            500,000 |
| SN             | Mobile Money   | XOF      |                600 |            500,000 |
| BF             | Mobile Money   | XOF      |                600 |            500,000 |


# Sandbox Test Accounts

The table below describes the test account numbers that can be used for tests on the sandbox to simulate transactions. Two scenarios (success and failure) can be simulated based on the account number.

### Mobile Money

<table><thead><tr><th width="132">Country</th><th>Payment Option</th><th width="262">Success</th><th>Failure</th></tr></thead><tbody><tr><td>Cameroon</td><td>MTN Mobile Money</td><td>237675776123</td><td>237675771456</td></tr><tr><td>Cameroon</td><td>Orange Money</td><td>237694566123</td><td>237694566456</td></tr><tr><td>Ghana</td><td>AirtelTigo Ghana</td><td>233263035123</td><td>233263035456</td></tr><tr><td>Ghana</td><td>Vodafone Cash</td><td>233501075123</td><td>233501075456</td></tr><tr><td>Ghana</td><td>MTN Mobile Money</td><td>233545503123</td><td>233545503456</td></tr><tr><td>Kenya</td><td>Safaricom M-Pesa</td><td>254741000123</td><td>254741000456</td></tr><tr><td>Kenya</td><td>Airtel Money KE</td><td>254731000123</td><td>254731000456</td></tr><tr><td>Rwanda</td><td>MTN Mobile Money</td><td>250785494123</td><td>250785491456</td></tr><tr><td>Rwanda</td><td>Airtel Money</td><td>250725799123</td><td>250725799456</td></tr><tr><td>Tanzania</td><td>Airtel Money TZ</td><td>255681000123</td><td>255681000456</td></tr><tr><td>Tanzania</td><td>TigoPesa TZ</td><td>255651000123</td><td>255651000456</td></tr><tr><td>Tanzania</td><td>Vodacom Mula TZ</td><td>255741000123</td><td>255741000456</td></tr><tr><td>Uganda</td><td>MTN Mobile Money</td><td>256777000001</td><td>256777000002</td></tr><tr><td>Uganda</td><td>Airtel Money</td><td>256752000001</td><td>256752000002</td></tr><tr><td>Zambia</td><td>MTN Mobile Money</td><td>260963626123</td><td>260963626456</td></tr><tr><td>Zambia</td><td>Airtel Money</td><td>260773626123</td><td>260773626456</td></tr><tr><td>Zambia</td><td>Zamtel Zambia</td><td>260953626123</td><td>260953626456</td></tr><tr><td>Ivory Coast</td><td>Moov Mobile Money</td><td>2250701234123</td><td>2250701234567</td></tr><tr><td>Ivory Coast</td><td>MTN Mobile Money</td><td>2250501234123</td><td>2250501234567</td></tr><tr><td>Ivory Coast</td><td>Orange Money</td><td>2250101234123</td><td>2250101234567</td></tr><tr><td>Benin</td><td>Moov Mobile Money</td><td>22997012345</td><td>22997012678</td></tr><tr><td>Benin</td><td>MTN Mobile Money</td><td>22961012345</td><td>22961012678</td></tr><tr><td>Mali</td><td>Moov Mobile Money</td><td>22367012345</td><td>22367012678</td></tr><tr><td>Mali</td><td>Orange Money</td><td>22374012345</td><td>22374012678</td></tr><tr><td>Togo</td><td>T-Money Mobile Money</td><td>22890012345</td><td>22890012678</td></tr><tr><td>Togo</td><td>Moov Mobile Money</td><td>22892012345</td><td>22892012678</td></tr><tr><td>Senegal</td><td>Orange Money</td><td>221770123456</td><td>221770123789</td></tr><tr><td>Senegal</td><td>Free Senegal Mobile Money</td><td>221780123456</td><td>221780123789</td></tr><tr><td>Senegal</td><td>Expresso Mobile Money</td><td>221760123456</td><td>221760123789</td></tr><tr><td>Burkina Faso</td><td>Moov Mobile Money</td><td>22666012345</td><td>22666012678</td></tr><tr><td>Burkina Faso</td><td>Orange Money</td><td>22670012345</td><td>22670012678</td></tr><tr><td>Guinea Conakry</td><td>Orange Money</td><td>23261112345</td><td>23261112678</td></tr><tr><td>Guinea Conakry</td><td>MTN Mobile Money</td><td>23266112345</td><td>23266112678</td></tr></tbody></table>

### Bank (Supported Payout Banks)

| Country/Region | Success    | Failure    |
| -------------- | ---------- | ---------- |
| Nigeria        | 2121562123 | 2121562456 |
| South Africa   | 2121562123 | 2121562456 |

### Card

<table><thead><tr><th width="154">Auth Method</th><th>Supported Currencies</th><th>Success</th><th>Failure</th></tr></thead><tbody><tr><td>3DS</td><td>USD, EUR</td><td>4821000000001234</td><td>4821000000005678</td></tr><tr><td>OTP</td><td>NGN</td><td>4622000000005678</td><td>4622000000004420</td></tr><tr><td>AVS</td><td>NGN</td><td>4622000000003747</td><td>4622000000003705</td></tr></tbody></table>


# DusuPay Public Keys

This section shares the two public keys used by DusuPay for the two environments (Sandbox and Production). The keys can be copied and stored as files to later be used in transaction processing.

The DusuPay public key serves two critical purposes as briefly described below.

<table><thead><tr><th width="247">Purpose</th><th>Description</th></tr></thead><tbody><tr><td>RSA Signature Verification</td><td>Callbacks from DusuPay carry a header named <code>rsa-signature</code> and with the public key, the signature can be verified as described <a href="/pages/ql6UH9G9LNRDzv0wXgcJ">here</a> to confirm authenticity.</td></tr><tr><td>Card details encryption</td><td>When the merchant opts for the direct card payment option, the public key is used to encrypt the card details as described in this <a href="/pages/W3dZsy7UL1A8bEB7Ukql">section</a></td></tr></tbody></table>

### Sandbox Public Key

```powershell
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzg8OSczAV7IYVsb5q7Ke
1ipnVkbL0fQdb8tS+0+JsZFhgZwwQopvuAgtA3XQh8Mlq1ul7iIl99qII8WHD96a
HRg1w8JxHkMP5glCFAIrRp/rRn72W3MC2O0utKiKyihEaW6ObB1eQKzuYfXAP1HO
dTnal+SzBjB2LyMFf4M7eE7Yksa9rdjFvIpfUkWSBlG028AxRrUI5fvem07TMK5i
I3GOv7xH/a3siaT2rQ+dZ83G+k7nCL/jNlekjD7QBPhRCExHGp6CKici74nWPZB+
cbCCObnBYfoJI+5AOwRjvimd8lBc/zBEL4Q5ElUG5UhPuiVMmjiZ4oEzbnIIswFx
ujFNuvqd8W3nv86OuTwUTpDszan1EHjoKY4wJ/wKJg38+3Gtfy0jzuaE0F71kvAy
j6yhM05hm+oybQeWkQFr/G1CiefG3uGHgE07lkU3PegdkRUHCDBhhkjgoaguHzna
Q8ReJDnRPlezncsW9VQyN10vuWIwLaYJvUqEBZdsp7/rIH4P81m6lI0vH2DG9ns0
acFA6hK+uIjzwiEY/n1NfdjRHpvYBSA493NcVbKGZ3yVHsvPvVSxkE/jX+P/0zVZ
DGu2LLv3IQilVElvjhT5qNeFBHG+2E9hS7Cqd+KsxNYpuRlow7NCiAflKx7/AeO2
P2DOy3sG/3E+u3OngnED5rUCAwEAAQ==
-----END PUBLIC KEY-----

```

### Production Public Key

```powershell
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0hPd57qbKYvZLwyqfZ3H
teqJ6VGniXjw/QWB4Ja4SRul7h7ShMwdDAyOZ1nZoFEJnIs95agoJdjh7sQfBOrB
YznHAK6tR/a8HP0z4VjN3V1d0wNDTLnx942XB+SnKWzQ+8atqKqBIsiD8ATWhryV
/AmLC3e8w38JP30qzV3xLVE469hY+Qh0rpbHNX1X/kpX2647G94Yj9rlPi7vggWz
qd3icwzjiEqTUr6LytUYLbgUEHfm9DDOEe96DUzZGCP2St0OhrBlAEMvD0md4X6Z
6GbXKrTz0Z8RF3dKm3R6k4VBBd390/qbd5Qt0BsAThK5qaFohhAtUF/DZCoA2bfl
K0pwQ43C3vfXcmew0+CzOAtowNbJ0ELqkIqoT1TD5OaVz6rMhLtBuSsfg233Bzup
iDqzltjWCqNfbNOZ8JKQQj9RQTwQpT/+izEdHjkM+HmMH4j//5/nuqzMYFcFkS00
mRDFhdSH1kORqs/Jj5pmBUbxeMnBXeKMh37maQBub5QygSkFDRmBz0pEqLynM/Yk
6YbgDqYDI7GWVFxh3c7TyLaixQbISZToTDEtnaQwFQOhNn6cVxFaITWJyftMFwSX
1oqrLjwnhi13k2eposPQibZgeY/s1xmWK7yYsC/GHjoZkNxXNvGafsIFuYD+793M
CNLBHhKqgIfU3YUiOKA1ne8CAwEAAQ==
-----END PUBLIC KEY-----

```

{% hint style="info" %}
We recommend that for each environment you're working with, the key content is copied and stored in your workflow based on how you see fit. In this documentation however, we will assume the content is stored in a file named `dusupay.public.key.pem` and referenced from there for all described usage.
{% endhint %}


# Balance Inquiry

The API allows the merchant to check the balance(s) for the different currencies they might hold.

<mark style="color:blue;">`GET`</mark> `https://sandboxapi.dusupay.com/data/wallet-balances`

The endpoint retrieves the balance(s) by currency. It returns currency wallets belonging to the merchant account

#### Query Parameters

<table><thead><tr><th width="121">Name</th><th width="88">Type</th><th>Description</th></tr></thead><tbody><tr><td>currency</td><td>String</td><td>The currency whose balances should be retrieved (Optional)</td></tr></tbody></table>

#### Headers

<table><thead><tr><th width="149">Name</th><th width="112">Type</th><th>Description</th></tr></thead><tbody><tr><td>public-key<mark style="color:red;">*</mark></td><td>String</td><td>The merchant account Public Key</td></tr><tr><td>secret-key<mark style="color:red;">*</mark></td><td>String</td><td>The merchant account Secret Key</td></tr></tbody></table>

### Sample Balance Inquiry Request

```powershell
curl https://sandboxapi.dusupay.com/data/wallet-balances \
   -H "Accept: application/json" \
   -H "x-api-version: 1" \
   -H "public-key: your-public-key" \
   -H "secret-key: your-secret-key"
```

{% tabs %}
{% tab title="200: OK Returns array of wallet balances based on the currency" %}

```json
{
    "code": 200,
    "status": "success",
    "message": "Request completed successfully.",
    "data": {
        "merchant_wallets": [
            {
                "available_balance": 40000,
                "actual_balance": 40000,
                "currency": "UGX",
                "uncleared_balance": 40000,
                "reserve_balance": 0,
                "wallet_type": "MERCHANT_COLLECTION"
            },
            {
                "available_balance": 0,
                "actual_balance": 0,
                "currency": "UGX",
                "uncleared_balance": 0,
                "reserve_balance": 0,
                "wallet_type": "MERCHANT_PAYOUT"
            }
        ]
    }
}

```

{% endtab %}

{% tab title="400: Bad Request When the request isn" %}

```javascript
{
  "code": 400,
  "status": "error",
  "message": "secret-key is required",
  "data": {}
}
```

{% endtab %}

{% tab title="401: Unauthorized When one of the keys is invalid" %}

```javascript
{
  "code": 401,
  "status": "error",
  "message": "Unauthorized API access. Invalid Merchant Secret Key",
  "data": {}
}
```

{% endtab %}
{% endtabs %}

### Wallet Types

<table><thead><tr><th width="253">Wallet Type</th><th>Description</th></tr></thead><tbody><tr><td>MERCHANT_COLLECTION</td><td>This wallet holds funds accumulated from funds collection of a specific currency. Funds directly deposited into the merchant account for a given currency would also live in a wallet of this type.</td></tr><tr><td>MERCHANT_PAYOUT</td><td>All the funds the merchant account wishes to designate for disbursements will be stored in a special wallet of this type. The merchant will be allowed to provision funds here by transferring from the collection account to this one.</td></tr></tbody></table>

### Balance Descriptions

<table><thead><tr><th width="190">Balance Name</th><th>Description</th></tr></thead><tbody><tr><td>available_balance</td><td>The amount/balance available for use. This the amount the merchant account has access to for any other platform operations</td></tr><tr><td>actual_balance</td><td>This is the total balance of a given currency for a certain merchant account wallet. It's the summation of all the balances in the given wallet.</td></tr><tr><td>uncleared_balance</td><td>This is the amount yet to be made available to the merchant account for use.</td></tr><tr><td>reserve_balance</td><td>In the event transactions are done using payment options for which a rolling reserve is implemented (e.g. card options), the accumulated rolling reserve amounts would be deposited under this balance</td></tr></tbody></table>


# Payment Options

Before any transaction request is performed, it's recommended that the payment options available to the merchant account are known. This endpoint returns that list.

The API description below shows how the list of payment options can be obtained. The request requires that the merchant filters by currency and transaction type to get the appropriate list of the payment options.

## Get Payment Options List

<mark style="color:blue;">`GET`</mark> `https://sandboxapi.dusupay.com/data/payment-providers`

Returns the list of payment options available to the merchant account based on currency and transaction type

#### Query Parameters

<table><thead><tr><th width="183">Name</th><th width="89">Type</th><th>Description</th></tr></thead><tbody><tr><td>currency<mark style="color:red;">*</mark></td><td>String</td><td>The 3-character ISO currency code e.g. UGX</td></tr><tr><td>transaction_type<mark style="color:red;">*</mark></td><td>String</td><td>The transaction type. Allowed types at the moment are <strong>COLLECTION</strong> and <strong>PAYOUT</strong></td></tr><tr><td>country_code</td><td>String</td><td>The 2-character country code e.g. SN (Optional). Useful for situations where multiple countries have the same currency</td></tr></tbody></table>

#### Headers

<table><thead><tr><th width="136">Name</th><th width="88">Type</th><th>Description</th></tr></thead><tbody><tr><td>public-key<mark style="color:red;">*</mark></td><td>String</td><td>The merchant account Public Key</td></tr></tbody></table>

### Sample Payment Options Request

```powershell
curl https://sandboxapi.dusupay.com/data/payment-providers?currency=UGX&transaction_type=COLLECTION \
   -H "Accept: application/json" \
   -H "x-api-version: 1" \
   -H "public-key: your-public-key"
```

{% tabs %}
{% tab title="200: OK Returns the list of payment options matching the query" %}

```json
{
    "code": 200,
    "status": "success",
    "message": "Request completed successfully.",
    "data": {
        "payment_providers": [
            {
                "provider_name": "MTN Mobile Money Uganda",
                "provider_code": "mtn_ug",
                "transaction_method": "MOBILE_MONEY",
                "transaction_method_name": "Mobile Money",
                "transaction_type": "Collection",
                "transaction_currency": "UGX",
                "min_amount": 500,
                "max_amount": 3000000,
                "max_daily_amount": 7000000,
                "is_active": true,
                "option_prefixes": [
                    "77",
                    "78",
                    "76",
                    "39"
                ]
            },
            {
                "provider_name": "Airtel Money Uganda",
                "provider_code": "airtel_ug",
                "transaction_method": "MOBILE_MONEY",
                "transaction_method_name": "Mobile Money",
                "transaction_type": "Collection",
                "transaction_currency": "UGX",
                "min_amount": 500,
                "max_amount": 3000000,
                "max_daily_amount": 7000000,
                "is_active": true,
                "option_prefixes": [
                    "74",
                    "75",
                    "70"
                ]
            }
        ]
    }
}

```

{% tabs %}
{% tab title="Response Description" %}

<table><thead><tr><th width="209">Parameter</th><th width="83">Type</th><th>Description</th></tr></thead><tbody><tr><td>provider_code</td><td>string</td><td>The unique code for the payment option. It is required for the transaction requests</td></tr><tr><td>provider_name</td><td>string</td><td>The name of the option</td></tr><tr><td>transaction_type</td><td>string</td><td>The transaction type</td></tr><tr><td>transaction_method</td><td>string</td><td>The transaction method supported by this option</td></tr><tr><td>transaction_currency</td><td>string</td><td>The currency in which the option transacts</td></tr><tr><td>min_amount</td><td>float</td><td>The minimum amount allowed per transaction when using this option</td></tr><tr><td>max_amount</td><td>float</td><td>The maximum amount allowed per transaction when using this option</td></tr><tr><td>max_daily_amount</td><td>float</td><td>The maximum amount a single account number is allowed to transact in 24hrs</td></tr><tr><td>option_prefixes</td><td>array</td><td>String prefixes that validate the phone numbers. Applies to mobile money options</td></tr></tbody></table>
{% endtab %}
{% endtabs %}
{% endtab %}

{% tab title="400: Bad Request When the request is not formed as expected" %}

```javascript
{
  "code": 400,
  "status": "error",
  "message": "transaction_type is required",
  "data": {}
}
```

{% endtab %}
{% endtabs %}


# Payout Bank Codes

For all countries/currencies where DusuPay supports bank payouts, there's need to specify the unique code for the bank to which the funds are to be sent. These codes are available via the API

The bank codes are attached to the respective payment providers designated for paying out funds to the supported banks. Therefore, a provider code, obtained from [here](/utility-functions/payment-options) can be used to query the list of supported banks. Any provider that supports BANK payouts will have the list of banks and respective codes.

### Get Payout Banks List

<mark style="color:blue;">`GET`</mark> `https://sandboxapi.dusupay.com/data/payout-bank-codes`

Returns the list of payout banks based on the provider code.

#### Query Parameters

<table><thead><tr><th width="162">Name</th><th width="80">Type</th><th>Description</th></tr></thead><tbody><tr><td>provider_code<mark style="color:red;">*</mark></td><td>String</td><td>The payment provider code for the provider whose bank codes are required</td></tr></tbody></table>

#### Headers

<table><thead><tr><th width="150">Name</th><th width="107">Type</th><th>Description</th></tr></thead><tbody><tr><td>public-key<mark style="color:red;">*</mark></td><td>String</td><td>The merchant account Public Key</td></tr></tbody></table>

### Sample Payment Options Request

```powershell
curl https://sandboxapi.dusupay.com/data/payout-bank-codes?provider_code=bank_ng \
   -H "Accept: application/json" \
   -H "x-api-version: 1" \
   -H "public-key: your-public-key"
```

{% tabs %}
{% tab title="200: OK Returns the list of payout banks" %}

```json
{
    "code": 200,
    "status": "success",
    "message": "Request completed successfully.",
    "data": {
        "payout_banks": [
            {
                "bank_name": "Access Bank Nigeria",
                "bank_code": "access_bank_ng",
                "is_active": true
            },
            {
                "bank_name": "Guaranty Trust Bank",
                "bank_code": "gtbank_ng",
                "is_active": true
            }
        ]
    }
}
```

{% tabs %}
{% tab title="Response Description" %}

<table><thead><tr><th width="137">Parameter</th><th width="96">Type</th><th>Description</th></tr></thead><tbody><tr><td>bank_code</td><td>String</td><td>The unique code for the payout bank. It is required for the bank payout transaction requests</td></tr><tr><td>bank_name</td><td>String</td><td>The name of the bank</td></tr><tr><td>is_active</td><td>Boolean</td><td>Whether or not the bank is active for usage</td></tr></tbody></table>
{% endtab %}
{% endtabs %}
{% endtab %}

{% tab title="400: Bad Request When the request is not formed as expected" %}

```json
{
  "code": 400,
  "status": "error",
  "message": "provider_code is required",
  "data": {}
}
```

{% endtab %}
{% endtabs %}


# Mobile Money Operator Prefixes

All the telecoms that provide the mobile money options to DusuPay have different prefixes that identify the mobile money numbers. These prefixes usually come right after the international country code

The table below shows the phone prefixes for the different networks/payment options that offer mobile money. These prefixes will also be returned as the part of the response when mobile money payment options are queried as described in this [section](/utility-functions/payment-options). Merchants are free to use this information and handle phone number validations.

<table><thead><tr><th width="133">Country</th><th width="137">Operator</th><th width="135">Country Code</th><th>Prefixes</th></tr></thead><tbody><tr><td>Cameroon</td><td>MTN Mobile Money</td><td>237</td><td>67, 68, 650, 651, 652, 653, 654</td></tr><tr><td>Cameroon</td><td>Orange Money</td><td>237</td><td>69, 655, 656, 657, 658, 659</td></tr><tr><td>Ghana</td><td>MTN Mobile Money</td><td>233</td><td>24, 54, 55, 591, 592, 593, 594, 595, 596, 3080, 3081, 3082, 3180, 3280, 33800, 34800, 35800, 36800, 37800, 38800, 39800</td></tr><tr><td>Ghana</td><td>Vodafone Cash</td><td>233</td><td>20, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 50</td></tr><tr><td>Ghana</td><td>AirtelTigo Ghana</td><td>233</td><td>26, 56, 307, 317, 327, 337, 347, 357, 367, 377, 387, 397, 27, 29, 57</td></tr><tr><td>Kenya</td><td>Safaricom M-Pesa</td><td>254</td><td>110,111,112, 113,114,115,70,71,72,740,741,742,743,745, 746, 748, 757,758,759,768, 769,79</td></tr><tr><td>Kenya</td><td>Airtel Money KE</td><td>254</td><td>73, 750, 751, 752, 753, 754, 755, 756, 762, 767, 78, 100, 101, 102</td></tr><tr><td>Rwanda</td><td>MTN Mobile Money</td><td>250</td><td>78, 79</td></tr><tr><td>Rwanda</td><td>AirtelTigo Rwanda</td><td>250</td><td>72, 73</td></tr><tr><td>Tanzania</td><td>Airtel Money TZ</td><td>255</td><td>68, 69, 78</td></tr><tr><td>Tanzania</td><td>TigoPesa TZ</td><td>255</td><td>65, 67, 71</td></tr><tr><td>Tanzania</td><td>Vodacom Mula TZ</td><td>255</td><td>74, 75, 76</td></tr><tr><td>Uganda</td><td>MTN</td><td>256</td><td>77, 78, 760, 761, 39</td></tr><tr><td>Uganda</td><td>Airtel</td><td>256</td><td>70, 75, 74</td></tr><tr><td>Zambia</td><td>MTN Zambia</td><td>260</td><td>76, 96</td></tr><tr><td>Zambia</td><td>Airtel Zambia</td><td>260</td><td>77, 97</td></tr><tr><td>Zambia</td><td>Zamtel Zambia</td><td>260</td><td>95, 211, 212, 213, 214, 215, 216, 217, 218</td></tr></tbody></table>


# Handling Notifications/Callbacks

Whenever the status of the transaction changes (to processing, failure or success), we notify your service via callbacks. Below are the hints to take note of as regards these payment notifications.

* All callbacks are sent to the callback URL you provide during registration.
* A secure callback URL is required and notifications will ONLY be sent to **https** URLs. If you don't receive these notifications, ensure to check this.
* The callback request is a POST with JSON serialized data.
* We expect that your service will acknowledge the callback request by responding with HTTP code **200** otherwise we will retry a few more times (if the response code is below the 500 range) and then stop.
* The callback request includes two extra headers; **rsa-signature** (an RSA generated signature) and **hmac-signature**, and these can optionally be used to verify that the callback request originated from our servers. Verification can be done for any of these two signatures, NOT necessarily both of them. If the signature is valid, you can proceed with your business logic.
* The HMAC signature can be verified using [this](/callbacks/hmac-signature-verification) procedure while the RSA (DusuPay) signature can be verified using [this](/callbacks/rsa-signature-verification) procedure. The merchant can choose which procedure to work with.

{% hint style="info" %}
All **production** notifications/callbacks will originate from this IP address below. Whenever more IPs are enabled, you'll be notified, and the document updated accordingly. We recommend that the IP (s) are whitelisted on your platform and only accept callback requests originating from them.

**161.35.164.139**
{% endhint %}

{% hint style="warning" %}
If you would like to equally whitelist IPs for the **sandbox** notifications/callbacks, consider the IP **165.227.128.244** for that as all sandbox callback requests will originate from that IP
{% endhint %}


# Callback Events

Every callback payload has a parameter named event and its purpose is to communicate the reason for the callback. The section below describes the various values the parameter can take.

Depending on the workflow (collection, invoice generation/payment, disbursement etc) being used, the gateway will send appropriate events to the appropriate callback URLs. For every workflow type, the applicable events are described below;

### Collections

<table><thead><tr><th width="218">Event</th><th width="176">Transaction Status</th><th>Description</th></tr></thead><tbody><tr><td>request.failed</td><td>FAILED</td><td>Request failed and could not be processed by the gateway. Failure reason is part of the callback payload</td></tr><tr><td>transaction.charges</td><td>PENDING</td><td>Communicates success of the first collection leg (initialization) and the charges to be levied. The collection confirmation request should be sent after this event is received</td></tr><tr><td>transaction.processing</td><td>PROCESSING</td><td>Notifies the merchant that transaction processing is ongoing. Returns mobile money instructions (when payment method is MOBILE_MONEY) or the card payment URL (when payment method is CARD)</td></tr><tr><td>transaction.failed</td><td>FAILED</td><td>The gateway tried to process the transaction but failed e.g. insufficient balance on the account. Failure reason is part of the callback payload</td></tr><tr><td>transaction.completed</td><td>COMPLETED</td><td>The gateway processed the collection successfully.</td></tr></tbody></table>

### Invoices (Generation and Payment)

<table><thead><tr><th width="180">Event</th><th>Description</th></tr></thead><tbody><tr><td>request.failed</td><td>Request failed and could not be processed by the gateway. Failure reason is part of the callback payload</td></tr><tr><td>invoice.generated</td><td>Event sent at successful generation of the invoice. Details of the invoice would be shared as part of the payload</td></tr><tr><td>invoice.paid</td><td>Event sent when the invoice is paid for. Part of the payload is the reference for the collection that fulfilled the invoice</td></tr></tbody></table>

### Payouts/Disbursements

<table><thead><tr><th width="218">Event</th><th width="175">Transaction Status</th><th>Description</th></tr></thead><tbody><tr><td>request.failed</td><td>FAILED</td><td>Request failed and could not be processed by the gateway. Failure reason is part of the callback payload</td></tr><tr><td>transaction.failed</td><td>FAILED</td><td>The gateway tried to process the transaction but failed. Failure reason is part of the callback payload</td></tr><tr><td>transaction.completed</td><td>COMPLETED</td><td>The gateway processed the payout successfully.</td></tr></tbody></table>


# Transaction Status Verification

In some situations, the merchant account might to confirm the status of the transaction. This document resource describes the process of retrieving the transaction details (status inclusive)

Whenever we get the final transaction status from the processor (telecom/bank/card processor), we'll send the notifications to your designated callback URLs and these requests will be retried using the exponential back-off strategy. This guarantees that even when your URL is unreachable OR the http request fails, there will be automated retries at different intervals. Our belief is that; if we don't have the final status yet, checking status from the merchant side doesn't help. However, if status check is crucial for your workflow, please note the following.

1. Status check requests will be denied if they're done less than **2 minutes** from the time the transaction was initiated.
2. We recommend that an interval of **5 minutes** is implemented between status check requests for the same transaction.

### Get Transaction Status

<mark style="color:blue;">`GET`</mark> `https://sandboxapi.dusupay.com/data/transaction/verify/{reference}`

Returns the transaction details and the parameter `data.transaction_status` holds the current status of the transaction. Replace `{reference}` with the merchant reference that was used prior when initiating the transaction.

#### Headers

<table><thead><tr><th width="150">Name</th><th width="107">Type</th><th>Description</th></tr></thead><tbody><tr><td>public-key<mark style="color:red;">*</mark></td><td>String</td><td>The merchant account Public Key</td></tr></tbody></table>

### Sample Transaction Status Request

```powershell
curl https://sandboxapi.dusupay.com/data/transaction/verify/MCTREFT2WMNWZ23SBN6Y \
   -H "Accept: application/json" \
   -H "x-api-version: 1" \
   -H "public-key: your-public-key"
```

{% tabs %}
{% tab title="200: Request successful" %}

```json
{
    "code": 200,
    "status": "success",
    "message": "Request completed successfully.",
    "data": {
        "id": 20760,
        "merchant_reference": "MCTREFT2WMNWZ23SBN6Y",
        "internal_reference": "DUSUPAYRMGRXNNYBWATKJ",
        "transaction_type": "COLLECTION",
        "request_currency": "UGX",
        "transaction_amount": 2000000,
        "transaction_currency": "UGX",
        "transaction_charge": 60000,
        "transaction_account": "256787008803",
        "charge_customer": false,
        "total_credit": 1940000,
        "provider_code": "mtn_ug",
        "request_amount": 2000000,
        "customer_name": "JOHN DOE",
        "transaction_status": "COMPLETED",
        "status_message": "Transaction Completed Successfully"
    }
}
```

{% endtab %}

{% tab title="404: Transaction Not Found" %}

```json
{
    "code": 404,
    "status": "error",
    "message": "Transaction with reference: MCTREF2BUBWGEVDZ47DQFH1 Not Found. Contact Support",
    "data": {}
}
```

{% endtab %}

{% tab title="403: Request before 2mins grace period" %}

```json
{
    "code": 403,
    "status": "error",
    "message": "Status check denied. Try again after 2 minutes.",
    "data": {}
}
```

{% endtab %}

{% tab title="429: Violation of recommended interval" %}

```json
{
    "code": 429,
    "status": "error",
    "message": "Request Failed. Too many requests in a short time.",
    "data": {}
}
```

{% endtab %}
{% endtabs %}

### Transaction Status Descriptions

<table><thead><tr><th width="190">Status</th><th>Description</th></tr></thead><tbody><tr><td>PENDING</td><td>The transaction has been logged on the DusuPay platform, awaiting escalation to the last mile processor (telecom, bank, card processor etc)</td></tr><tr><td>PROCESSING</td><td>The transaction is currently being processed and the DusuPay platform is waiting for the final status from the last mile processor</td></tr><tr><td>FAILED</td><td>The transaction is failed. Reason for failure will always be in the <code>data.status_message</code> parameter.</td></tr><tr><td>COMPLETED</td><td>The transaction was processed successfully.</td></tr><tr><td>CANCELLED</td><td>The transaction was cancelled. This is usually done by the customer.</td></tr></tbody></table>


# Getting Started

Collections are payments that the merchant receives into their account. The sections here will highlight how the DusuPay platform supports these.

The DusuPay API supports funds collection via a number of methods e.g. Mobile Money, Bank Transfer, Card, Crypto and many other methods will be supported along the way. This is to allow for the flexibility of customer payments, such that customers can make payments in the ways that are most convenient. The funds collection section will describe the process flows for every available payment method. However, every collection will follow the steps described below, in the order of occurrence.

### The collection API request

The merchant will form the collection request payload (based on the selected payment method) and send it to the API for processing. If there are any errors with the parameters, these would be communicated immediately in the response otherwise the API would respond with an acknowledgement. Take the following the mobile money request and a sample acknowledgement.

```json
{
    "merchant_reference": "MCTREFT2WMNWZ23SBN6Y",
    "transaction_method": "MOBILE_MONEY",
    "currency": "UGX",
    "amount": 1000,
    "provider_code": "mtn_ug",
    "msisdn": "256777000001",
    "customer_name": "JOHN DOE",
    "description": "Test Collection",
    "allow_final_status_change": true
}

/*sample acknowledgement would be something like the following*/
{
    "code": 202,
    "status": "accepted",
    "message": "Request Accepted",
    "data": {
        "internal_reference": "DUSUPAYRMGRXNNYBWATKJ",
        "merchant_reference": "MCTREFT2WMNWZ23SBN6Y"
    }
}
```

However, should this process fail in any way even after the acknowledgement e.g. if the merchant is making too many requests to the same phone number, the following failure callback will be sent to the collection callback URL.

```json
{
    "event": "request.failed",
    "payload": {
        "id": 20760,
        "merchant_reference": "MCTREFT2WMNWZ23SBN6Y",
        "internal_reference": "DUSUPAYRMGRXNNYBWATKJ",
        "transaction_type": "COLLECTION",
        "request_currency": "UGX",
        "request_amount": 1000,
        "transaction_status": "FAILED",
        "status_message": "Failed to initialize the transaction. System exception!"
    }
} 
```

### Redirect to Payment URL - Optional

In special circumstances e.g. Mobile Money via the hosted page, bank, card and crypto collections, the initial response would have the payment URL to which the customer needs to be redirected to complete the transaction. A sample of this response payload is shared below.

```json
{
    "code": 202,
    "status": "accepted",
    "message": "Request Accepted",
    "data": {
        "internal_reference": "DUSUPAYZAERK2SBE6WJAG",
        "merchant_reference": "MCTREFQSSBHMHU3RCTLA",
        "payment_url": "https://devpay.dusupay.com/pay/collection/DUSUPAYZAERK2SBE6WJAG"
    }
}
```

{% hint style="info" %}
When the `payment_url` is part of the response, be sure to redirect the customer to that URL as that will be the only way for the customer to complete the transaction.
{% endhint %}

### Payment instructions notification - Optional

In special circumstances e.g. Mobile Money (direct charge) and Card (S2S), this notification is sent when the transaction is still in progress BUT the merchant needs to act on the instructions shared. Sometimes, the merchant will be required to submit some extra parameters from the customer or simply display the instructions to the user. These scenarios will be described in the specific document sections. Below is a sample payload

```json
{
    "event": "transaction.processing",
    "payload": {
        "id": 27594,
        "merchant_reference": "MCTREF9WVCPGFRHYCWSK",
        "internal_reference": "DUSUPAYSTPDKZ6MLY5WPQ",
        "transaction_type": "COLLECTION",
        "request_currency": "UGX",
        "transaction_amount": 5000,
        "transaction_currency": "UGX",
        "transaction_charge": 150,
        "transaction_account": "256777000005",
        "charge_customer": false,
        "total_credit": 4850,
        "provider_code": "mtn_ug",
        "request_amount": 5000,
        "customer_name": "JOHN DOE",
        "transaction_status": "PROCESSING",
        "status_message": "Transaction Initiated successfully. Approve Payment by entering the PIN",
        "authorization": {
            "mode": "NONE",
            "required_parameters": []
        },
        "payment_instructions": "<p>You will receive a prompt on the mobile number <b>256777000005</b>.<br/> Enter your PIN to authorize your payment of <b>UGX 5,000</b></p>"
    }
}
```

### Final status notification

This is the notification that communicates the very final status of the transaction, and this would mark the close of the transaction process. Sample completed and failed transaction callbacks/notifications shared below.

```json
/*sample completed transaction callback*/
{
    "event": "transaction.completed",
    "payload": {
        "id": 20760,
        "merchant_reference": "MCTREFT2WMNWZ23SBN6Y",
        "internal_reference": "DUSUPAYRMGRXNNYBWATKJ",
        "transaction_type": "COLLECTION",
        "request_currency": "UGX",
        "transaction_amount": 2000000,
        "transaction_currency": "UGX",
        "transaction_charge": 60000,
        "transaction_account": "256787008803",
        "charge_customer": false,
        "total_credit": 1940000,
        "provider_code": "mtn_ug",
        "request_amount": 2000000,
        "customer_name": "JOHN DOE",
        "transaction_status": "COMPLETED",
        "status_message": "Transaction Completed Successfully"
    }
}

/*sample failed transaction callback*/
{
      "event": "transaction.failed",
      "payload": {
        "id": 26609,
        "merchant_reference": "MCTREFNRFRTQA6SCWT5X",
        "internal_reference": "DUSUPAYWYUF8CR3ZRCGYU",
        "transaction_type": "COLLECTION",
        "request_currency": "UGX",
        "transaction_amount": 2000000,
        "transaction_currency": "UGX",
        "transaction_charge": 0,
        "transaction_account": "256777000002",
        "charge_customer": false,
        "total_credit": 0,
        "provider_code": "mtn_ug",
        "request_amount": 2000000,
        "customer_name": "JOHN DOE",
        "transaction_status": "FAILED",
        "status_message": "Balance Insufficient for the transaction"
      }
    }
```

### Transaction final status change

In rare conditions, it's possible for the final status of the transaction to change especially from **FAILED** to **COMPLETED**. In such situations, it's usually because the DusuPay automation could fail some transactions that take over 1hr without a final status from the last mile processor (telecom/bank/card processor etc) and then later, it is discovered that the customer actually made the payment. The default action from DusuPay is that the status is updated and the merchant updated accordingly with a new callback. However, some merchants do not like this behaviour and prefer that the final status never changes. Such merchants are advised to make use of the collection Boolean parameter `allow_final_status_change` and set the value to `false`. By doing this, DusuPay is instructed not to alter the final transaction status.

{% hint style="info" %}
**Cross-currency Collections**

If the request currency is different e.g. USD from the payment provider currency (transaction currency) e.g. KES, the API will convert the USD to KES and at the end of the transaction, your KES balance will be credited with the converted amount. More details [here](/appendix/cross-currency-transactions).
{% endhint %}


# Mobile Money Collection

The DusuPay API supports mobile money as described in the Supported Countries section. The API allows for direct charge or making use of the hosted page that's powered by DusuPay (recommended).

### Pre-requisites

1. The merchant needs to have an approved merchant account on the live platform. This is only achieved after the go-live requirements are met (being on sandbox and clearing the UAT tests).
2. The mobile money channels required by the merchant need to be configured on the designated merchant account. The support teams will be available to assist with this.

### Collection Workflow Selection

The API allows the merchant to either use the hosted payment page (recommended) or build their own user experience based on the API workflow. With the hosted payment page, DusuPay handles all the different variations of authorizing mobile money payments e.g. PIN entry, verifying phone numbers with OTP before completing transactions (e.g. for GHS Mobile Money collections.) and this seamless workflow creates a good user experience. Some merchants however wish to create custom experiences using their brand guidelines and, in this situation, the direct charge can be used.

The table below briefly describes the two Mobile Money workflows to select from.

<table><thead><tr><th width="529">Scenario</th><th>Resource</th></tr></thead><tbody><tr><td>You wish to use a managed user experience that handles all Mobile Money workflow variations without any input from you, even when telecom policies change from time to time to require other parameters. <em><strong>(RECOMMENDED)</strong></em></td><td><a href="/pages/UkLTa97XzLtAa45EUPFg">Hosted Page Workflow</a></td></tr><tr><td>You wish to have control of the user experience, page branding and directly call all the API endpoints yourself. (this requires a thorough understanding of the Mobile Money API workflow and the variations in authorization modes)</td><td><a href="/pages/fYOpn6LuhxLuIyJ4WUSM">Direct Charge Workflow</a></td></tr></tbody></table>

{% hint style="info" %}
We strongly recommend that when the merchant chooses to use the direct charge workflow, they should first study the hosted page workflow before building their custom experiences. Merchants outside the mobile money processing countries are especially encouraged for try the hosted page first.
{% endhint %}


# Mobile Money - Direct Charge

This is one of the ways to process payments using Mobile Money. The section below will guide you through the process of accepting Mobile Money using the direct API method.

## Overview

The currently supported mobile money channels are listed [here](/getting-started/supported-countries-regions) (to be updated from time to time). Test mobile money phone numbers are also described in [this](/getting-started/sandbox-test-accounts) section. We equally recommend that you go through the [Getting Started](/funds-collection/getting-started) section to have a high-level understanding of the funds collection process.

## Workflow Overview

Study this diagram below to understand the customer journey workflow and how the API request/response happens

```mermaid
sequenceDiagram
    actor Customer
    participant Merchant Portal
    participant DusuPay
    participant Telecom

    Customer->>Merchant Portal: Initiates collection request
    Merchant Portal->>DusuPay: POST /collections/initialize
    DusuPay-->>Merchant Portal: 202 Accepted
    DusuPay->>Telecom: Collection request for amount
    Telecom->>Telecom: Confirm customer eligibility + balance
    Telecom->>Customer: Send approval PIN prompt
    Customer-->>Telecom: PIN approval
    Telecom->>DusuPay: Notification of transaction completion
    DusuPay->>Merchant Portal: Merchant callback notification
    Merchant Portal->>Customer: Display success message
```

## Step 1: Obtain the required data for the payment request

The table below describes the request parameters that are used for the collection/charge request. Most/all will be collected from the paying customer.

<table><thead><tr><th width="197">Parameter</th><th width="93">Type</th><th width="98">Required</th><th>Description</th></tr></thead><tbody><tr><td>merchant_reference</td><td>String</td><td>true</td><td>The unique reference for this request. It must be at least 8 characters long. Alternatively, the value <strong>auto</strong> can be passed, and a unique reference will be created for you by the API</td></tr><tr><td>transaction_method</td><td>String</td><td>true</td><td>The transaction method to be used. This will be <em><strong>MOBILE_MONEY</strong></em> for this request</td></tr><tr><td>currency</td><td>String</td><td>true</td><td>The 3-character ISO currency code for the request currency</td></tr><tr><td>amount</td><td>Number</td><td>true</td><td>The amount being requested</td></tr><tr><td>provider_code</td><td>String</td><td>true</td><td>The provider code as obtained from the payment options <a href="/pages/E8qh5wAQIPuCQxEHFHxg#get-payment-options-list">list</a></td></tr><tr><td>msisdn</td><td>String</td><td>true</td><td>The phone number from which the payment is being requested. This should be sent in international format e.g. 256777000001 for Ugandan numbers</td></tr><tr><td>customer_name</td><td>String</td><td>false</td><td>The name of the customer</td></tr><tr><td>customer_email</td><td>String</td><td>false</td><td>The email of the customer</td></tr><tr><td>description</td><td>String</td><td>true</td><td>The description/narration for the transaction. Between 10-30 characters</td></tr><tr><td>charge_customer</td><td>Boolean</td><td>false</td><td>Whether or not the customer should bear the charge for the transaction. By default, this is <strong>false</strong> to mean that the merchant bears the charge</td></tr><tr><td>allow_final_status_change</td><td>Boolean</td><td>false</td><td>Whether or not the final transaction status can be altered as described <a href="/pages/oIwPtHN5YxEe4a0U5aCk#transaction-final-status-change">here</a>. By default, this is <strong>true</strong> to mean DusuPay will alter the final transaction status under the circumstances described.</td></tr></tbody></table>

After collecting the necessary mobile money payment information from your customer, prepare your request payload as demonstrated below.

```json
{
  "merchant_reference": "auto",
  "transaction_method": "MOBILE_MONEY",
  "currency": "UGX",
  "amount": 4000,
  "provider_code": "mtn_ug",
  "msisdn": "256777000001",
  "customer_email": "johndoe@gmail.com",
  "customer_name": "JOHN DOE",
  "description": "Test Collection",
  "charge_customer": false,
  "allow_final_status_change": true
}
```

<mark style="color:green;">`POST`</mark> `https://sandboxapi.dusupay.com/collections/initialize`

The request is sent as a JSON body as demonstrated by the sample request below. Sample responses (acknowledgement and failure) are also shared.

```powershell
curl -X POST "https://sandboxapi.dusupay.com/collections/initialize" \
   -H 'Content-Type: application/json' \
   -H "x-api-version: 1" \
   -H "public-key: your-public-key" \
   -d '{
        "merchant_reference": "auto",
        "transaction_method": "MOBILE_MONEY",
        "currency": "UGX",
        "amount": 5000,
        "provider_code": "mtn_ug",
        "msisdn": "256777000001",
        "customer_email": "johndoe@gmail.com",
        "customer_name": "JOHN DOE",
        "description": "Test Collection",
        "charge_customer": false,
        "allow_final_status_change": true
    }'
```

{% tabs %}
{% tab title="202: Accepted - Request acknowledged for processing" %}

```json
{
  "code": 202,
  "status": "accepted",
  "message": "Request Accepted",
  "data": {
    "internal_reference": "DUSUPAYSTPDKZ6MLY5WPQ",
    "merchant_reference": "MCTREF9WVCPGFRHYCWSK"
  }
}
```

{% endtab %}

{% tab title="400: Bad Request - Request is not formed as expected" %}

```json
{
  "code": 400,
  "status": "error",
  "message": "256752000001 is not a valid MTN Mobile Money Uganda (mtn_ug) phone number",
  "data": {}
}
```

{% endtab %}
{% endtabs %}

## Step 2: Handle the payment instructions webhook

If the request in step 1 is successful and responds with an acknowledgement (HTTP code 202), you should listen and handle the payment instructions webhook/callback. This will be sent to the collection callback URL that's configured on your merchant account. The callback is sent as a JSON POST request and the payload will help you determine the next course of action. The tabs below show the two possible payload structures.

{% tabs %}
{% tab title="No Auth required - Plain Instructions " %}

```json
{
  "event": "transaction.processing",
  "payload": {
    "id": 27594,
    "merchant_reference": "MCTREF9WVCPGFRHYCWSK",
    "internal_reference": "DUSUPAYSTPDKZ6MLY5WPQ",
    "transaction_type": "COLLECTION",
    "request_currency": "UGX",
    "transaction_amount": 5000,
    "transaction_currency": "UGX",
    "transaction_charge": 150,
    "transaction_account": "256777000001",
    "charge_customer": false,
    "total_credit": 4850,
    "provider_code": "mtn_ug",
    "request_amount": 5000,
    "customer_name": "JOHN DOE",
    "transaction_status": "PROCESSING",
    "status_message": "Transaction Initiated successfully. Approve Payment by entering the PIN",
    "authorization": {
      "mode": "NONE",
      "required_parameters": []
    },
    "payment_instructions": "<p>You will receive a prompt on the mobile number <b>256777000001</b>.<br/> Enter your PIN to authorize your payment of <b>UGX 5,000</b></p>"
  }
}
```

{% endtab %}

{% tab title="OTP authorization required" %}

```json
{
  "event": "transaction.processing",
  "payload": {
    "id": 27594,
    "merchant_reference": "MCTREF9WVCPGFRHYCWSK",
    "internal_reference": "DUSUPAYSTPDKZ6MLY5WPQ",
    "transaction_type": "COLLECTION",
    "request_currency": "GHS",
    "transaction_amount": 500,
    "transaction_currency": "GHS",
    "transaction_charge": 150,
    "transaction_account": "233545503456",
    "charge_customer": false,
    "total_credit": 350,
    "provider_code": "mtn_gh",
    "request_amount": 500,
    "customer_name": "JOHN DOE",
    "transaction_status": "PROCESSING",
    "status_message": "Transaction Initiated successfully. OTP sent",
    "authorization": {
      "mode": "OTP",
      "required_parameters": [
        {
          "parameter_name": "otp"
        }
      ]
    },
    "payment_instructions": "<p>Authorize Payment using the OTP sent to <b>233545503456</b>.</p>"
  }
}
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
Pay close attention to the `authorization.mode` parameter. When the value is **NONE**, the merchant should simply display the instructions in the `payment_instructions` parameter. In this scenario, the customer should expect a PIN approval prompt from the telecom. If the mode is **OTP**, it means the OTP has been sent to the phone number and the merchant needs to call another endpoint to submit the OTP and authorize the transaction to proceed as described in step 3 below.
{% endhint %}

## Step 3: Authorize the transaction (Optional)

If the payment instructions webhook in step 2 above is sent, the transaction status being PROCESSING and the authorization mode is OTP, this means the OTP has been sent to the customer's phone number. The merchant therefore needs to collect the OTP from the customer and submit it in order to verify the phone number and authorize the transaction to proceed.

The parameter `authorization.required_parameters` holds an object array of the parameters the merchant should obtain and submit. In every object, `parameter_name` is the name of the parameter that's going to carry the required value (OTP in this case). Form the payload as demonstrated in the sample request below.

{% hint style="success" %}
For testing/sandbox purposes, use the OTP **123456** to simulate success and **378961** to simulate failure
{% endhint %}

<mark style="color:green;">`POST`</mark> `https://sandboxapi.dusupay.com/collections/authorize`

```powershell
curl -X POST "https://sandboxapi.dusupay.com/collections/authorize" \
   -H 'Content-Type: application/json' \
   -H "x-api-version: 1" \
   -H "public-key: your-public-key" \
   -d '{
        "internal_reference": "DUSUPAYSTPDKZ6MLY5WPQ",
        "otp": "123456"
    }'
```

If OTP verification is successful, a PIN prompt will be sent to the mobile money phone number in the request for the customer to approve the transaction. If the customer has sufficient balance for the transaction and is eligible to transact, the request will be successful otherwise the API will send an appropriate error message as shared by the telecom.

## Step 4: Handle the final status webhook

Every merchant account is expected to have configured a callback/webhook URL for collections. For all collections that transition to the final state (COMPLETED, FAILED or CANCELLED), a JSON POST request will be made to the callback URL. Sample callback payloads (request bodies) are shared below. Be sure to check out [Handling Notifications](/utility-functions/handling-notifications-callbacks) to see how you should verify the signature(s) in the request headers and how to respond.

{% tabs %}
{% tab title="Successful Mobile Money Collection" %}

```json
{
  "event": "transaction.completed",
  "payload": {
    "id": 20760,
    "merchant_reference": "MCTREFT2WMNWZ23SBN6Y",
    "internal_reference": "DUSUPAYRMGRXNNYBWATKJ",
    "transaction_type": "COLLECTION",
    "request_currency": "UGX",
    "transaction_amount": 2000000,
    "transaction_currency": "UGX",
    "transaction_charge": 60000,
    "transaction_account": "256787008803",
    "charge_customer": false,
    "total_credit": 1940000,
    "provider_code": "mtn_ug",
    "request_amount": 2000000,
    "customer_name": "JOHN DOE",
    "transaction_status": "COMPLETED",
    "status_message": "Transaction Completed Successfully"
  }
}
```

{% endtab %}

{% tab title="Failed Mobile Money Collection" %}

```json
{
  "event": "transaction.failed",
  "payload": {
    "id": 26609,
    "merchant_reference": "MCTREFNRFRTQA6SCWT5X",
    "internal_reference": "DUSUPAYWYUF8CR3ZRCGYU",
    "transaction_type": "COLLECTION",
    "request_currency": "UGX",
    "transaction_amount": 2000000,
    "transaction_currency": "UGX",
    "transaction_charge": 0,
    "transaction_account": "256777000002",
    "charge_customer": false,
    "total_credit": 0,
    "provider_code": "mtn_ug",
    "request_amount": 2000000,
    "customer_name": "JOHN DOE",
    "transaction_status": "FAILED",
    "status_message": "Balance Insufficient for the transaction"
  }
}
```

{% endtab %}
{% endtabs %}


# Mobile Money - Hosted Page

The hosted page workflow is the other way of accepting Mobile Money payments. The merchant initiates a request and in response is given a URL to which the user is redirected to complete the payment

## Overview

The currently supported mobile money channels are listed [here](/getting-started/supported-countries-regions) (to be updated from time to time). Test mobile money phone numbers are also described in [this](/getting-started/sandbox-test-accounts) section. We equally recommend that you go through the [Getting Started](/funds-collection/getting-started) section to have a high-level understanding of the funds collection process.

## Workflow Overview

Study this diagram below to understand the customer journey workflow and how the API request/response happens

```mermaid
sequenceDiagram
    actor Customer
    participant Merchant Portal
    participant DusuPay
    participant Telecom

    Customer->>Merchant Portal: Initiates collection request
    Merchant Portal->>DusuPay: POST /collections/initialize
    DusuPay-->>Merchant Portal: 202 Accepted with payment URL
    Merchant Portal-->>Customer: Redirect customer to HPP
    Customer->>DusuPay: Submit payment details form
    DusuPay->>Telecom: Collection request for amount
    Telecom->>Telecom: Confirm customer eligibility + balance
    Telecom->>Customer: Send approval PIN prompt
    Customer-->>Telecom: PIN approval
    Telecom->>DusuPay: Notification of transaction completion
    DusuPay->>Merchant Portal: Merchant callback notification
    DusuPay-->>Merchant Portal: Redirect to designated redirect_url
    Merchant Portal-->>Customer: Display success message
```

## Step 1: Obtain the required data for the payment request

The table below describes the request parameters that are used for the collection/charge request. Most/all will be collected from the paying customer.

<table><thead><tr><th width="197">Parameter</th><th width="93">Type</th><th width="98">Required</th><th>Description</th></tr></thead><tbody><tr><td>merchant_reference</td><td>String</td><td>true</td><td>The unique reference for this request. It must be at least 8 characters long. Alternatively, the value <strong>auto</strong> can be passed, and a unique reference will be created for you by the API</td></tr><tr><td>transaction_method</td><td>String</td><td>true</td><td>The transaction method to be used. This will be <em><strong>MOBILE_MONEY</strong></em> for this request</td></tr><tr><td>currency</td><td>String</td><td>true</td><td>The 3-character ISO currency code for the request currency</td></tr><tr><td>amount</td><td>Number</td><td>true</td><td>The amount being requested</td></tr><tr><td>provider_code</td><td>String</td><td>true</td><td>The provider code as obtained from the payment options <a href="/pages/E8qh5wAQIPuCQxEHFHxg#get-payment-options-list">list</a></td></tr><tr><td>msisdn</td><td>String</td><td>true</td><td>The phone number from which the payment is being requested. This should be sent in international format e.g. 256777000001 for Ugandan numbers</td></tr><tr><td>customer_name</td><td>String</td><td>false</td><td>The name of the customer</td></tr><tr><td>customer_email</td><td>String</td><td>false</td><td>The email of the customer</td></tr><tr><td>description</td><td>String</td><td>true</td><td>The description/narration for the transaction. Between 10-30 characters</td></tr><tr><td>charge_customer</td><td>Boolean</td><td>false</td><td>Whether or not the customer should bear the charge for the transaction. By default, this is <strong>false</strong> to mean that the merchant bears the charge</td></tr><tr><td>allow_final_status_change</td><td>Boolean</td><td>false</td><td>Whether or not the final transaction status can be altered as described <a href="/pages/oIwPtHN5YxEe4a0U5aCk#transaction-final-status-change">here</a>. By default, this is <strong>true</strong> to mean DusuPay will alter the final transaction status under the circumstances described.</td></tr><tr><td><mark style="color:orange;">mobile_money_hpp</mark></td><td><mark style="color:orange;">Boolean</mark></td><td><mark style="color:orange;">true</mark></td><td><mark style="color:orange;">Should be sent with the value <strong>true</strong> in order for the API to handle the payment via hosted page</mark></td></tr><tr><td><mark style="color:orange;">redirect_url</mark></td><td><mark style="color:orange;">String</mark></td><td><mark style="color:orange;">true</mark></td><td><mark style="color:orange;">The HTTPs redirect URL to which the API will redirect when the payment is successful/failed</mark></td></tr></tbody></table>

After collecting the necessary mobile money payment information from your customer, prepare your request payload as demonstrated below.

```json
{
  "merchant_reference": "auto",
  "transaction_method": "MOBILE_MONEY",
  "currency": "UGX",
  "amount": 4000,
  "provider_code": "mtn_ug",
  "msisdn": "256777000001",
  "customer_email": "johndoe@gmail.com",
  "customer_name": "JOHN DOE",
  "description": "Test Collection",
  "charge_customer": false,
  "allow_final_status_change": true,
  "mobile_money_hpp": true,
  "redirect_url": "https://your-redirect-url"
}
```

<mark style="color:green;">`POST`</mark> `https://sandboxapi.dusupay.com/collections/initialize`

The request is sent as a JSON body as demonstrated by the sample request below. Sample responses (acknowledgement and failure) are also shared.

```powershell
curl -X POST "https://sandboxapi.dusupay.com/collections/initialize" \
   -H 'Content-Type: application/json' \
   -H "x-api-version: 1" \
   -H "public-key: your-public-key" \
   -d '{
        "merchant_reference": "auto",
        "transaction_method": "MOBILE_MONEY",
        "currency": "UGX",
        "amount": 5000,
        "provider_code": "mtn_ug",
        "msisdn": "256777000001",
        "customer_email": "johndoe@gmail.com",
        "customer_name": "JOHN DOE",
        "description": "Test Collection",
        "charge_customer": false,
        "allow_final_status_change": true,
        "mobile_money_hpp": true,
        "redirect_url": "https://your-redirect-url"
    }'
```

{% tabs %}
{% tab title="202: Accepted - Request acknowledged for processing" %}

```json
{
  "code": 202,
  "status": "accepted",
  "message": "Request Accepted",
  "data": {
    "internal_reference": "DUSUPAYZAERK2SBE6WJAG",
    "merchant_reference": "MCTREFQSSBHMHU3RCTLA",
    "payment_url": "https://devpay.dusupay.com/pay/collection/DUSUPAYZAERK2SBE6WJAG"
  }
}
```

{% endtab %}

{% tab title="400: Bad Request - Request is not formed as expected" %}

```json
{
  "code": 400,
  "status": "error",
  "message": "256752000001 is not a valid MTN Mobile Money Uganda (mtn_ug) phone number",
  "data": {}
}
```

{% endtab %}
{% endtabs %}

## Step 2: Redirect to Payment URL

Notice the `payment_url` parameter in the response body. The URL should be loaded in the browser so that the customer can proceed with the transaction. When the page loads, the customer will be guided through the payment process and on success/failure, the customer will be redirected to the `redirect_url` sent by the merchant in the request. Additionally, a callback/webhook will be sent to the configured collection callback URL.

{% hint style="success" %}
For testing/sandbox purposes, use the OTP **123456** to simulate success and **378961** to simulate failure. This is in situations where the transaction requires OTP authorization.
{% endhint %}

## Step 3: Handle the redirect and/or callback

### Redirect

On success/failure of the transaction, the customer will be redirected to the URL that was passed in the `redirect_url` request parameter. The sample requests below demonstrated the success and failure scenarios. You can copy the URL and paste it on [this](https://semalt.tools/en/url-parser) online resource in order to view the query parameters therein.

{% tabs %}
{% tab title="Redirect on success" %}
{% code overflow="wrap" %}

```powershell
https://webhook.site/cc0393dc-fe49-401b-ba56-da9e099a3648?id=33911&event=transaction.completed&merchant_reference=MCTREFZQTUDRC38TLYUV&internal_reference=DUSUPAYHYJQ3ZUJMPGFG5&transaction_type=COLLECTION&transaction_status=COMPLETED&status_message=Transaction%20Completed%20Successfully&rsa_signature=Y23aNOaOs6NfOnrhpZrWGAOI4bMR5kNobFnrOBlqF%2FY7f03aBpHZx2rcmU3q2P9zS2w61xs3FsT%2FipRI9F21dNJ0UxDQci7yQ%2B7CZbk6kJQbfX9Ht7OIhH2%2F7cv%2FNUXDmXzZQYST3Qwe9QIhea1SIImWRzR8HKAeEeoVRPLDqxohLX4A0S91hjrZVO%2BjohZJLazIeXFrOaJyIu3xPQ7bzJi%2BfVhw6ry3R7NknIj5y752WpW0CqvxXKr3wPUIXy99tnN%2FCWLG65AmVL9GkBZj0j12%2B8ztBpknPBuGKWxndJB7zkkPTCmA%2BA1m21f3RENYTNH7nHet5zp5Iyq%2BkmtVVUmEcfvfAwAxs0sP0fYTFU8iSX6wvB6UEvk3AWgrprlb1XGd3sRgFgCb3HjzA7%2B8RBi%2Fw59qIXqK4LlbOtZw2tPc7MjLeXq%2BmqzrO8SRxyuvu6lX0HPhaHSXAGq%2BvljA1Kk4ZNLdSWxYCC2IgI7%2BPomrfWW1I6pNIF%2BudPTIYTPN6EEdMUwFBltyAoh%2ByHl6K%2F%2Fcz4FfE6OgZwJkbQ8661sHqiB2ND%2FhSQh%2B0Qre1tQTLx7BGQTSGdD34CLn%2FQICIlqNCvKbMF4ZO208xYaBct01T6Xa23zykkrmlTviEMJgSIo3zj2KpN%2BTF1X5jZOXLszBL20o1SeQlY9%2BhRzshzY%3D&hmac_signature=t%3D1721639784089%2Cs%3D1ec4e0f7749397faea80958bca9ab2c6d8e7448c3ee02f958881a392ce299128
```

{% endcode %}
{% endtab %}

{% tab title="Redirect on failure/cancellation" %}
{% code overflow="wrap" %}

```powershell
https://mysite.com?id=34639&event=transaction.failed&merchant_reference=MCTREF8DQRHYTNCQTACH&internal_reference=DUSUPAY8A54ZUJFGSMENE&transaction_type=COLLECTION&transaction_status=FAILED&status_message=Balance%20Insufficient%20for%20the%20transaction&rsa_signature=yvODC%2BViCiZmlEEEbQX1B%2FfQ0kNzrYqRN0c3Y8MjzZ%2FiMNVzf%2BUS36Ec8P1a0rOKgyc0l7KVbzEJwMZu0nasAHFuCeSOebs5NOHvSNJkAbQE2nZXgxvo1uQtJOMnbYZZKIvswemRTRGEyAKl7bRGc2AXzYRzJBVOYBVDOM0o36FUpfNNFrucVjvrmJJD8YtRtIXk3RjBzXkhV%2B3KAjWx0KU8bh424OyZUwsRTaN0MeuQMFY%2FkeuFvNKPzxKtN%2BaJqcx4aDZes6thevgfCljgne0ibC9Zy1eDCYP8A1T%2BNTS%2FHUZBuy1Y%2BzAmQ%2BMjnoiI%2FVk2I%2BuoRkHU01%2BMV%2Bo4CFGAoheoQ9N%2B7KFN0f4oAdT3G%2BJZtcwitZ0Q3Jo%2FW3aYNyvrPQHt88hjOtVHKhSNEwUJ8QNfy42WiZJOix4p5I5oyePdYGW5ia09MuaY6BkV8o9mdotcWGpn6O1PsxnxfduI6gbGLWtcxjrat75LeMhGjPLYcJA9CQECK7NvBFHOBUA%2F9rR5gReBVEkdVf5N8q551DvJfR4nENxZukaVMzjhq2IIndOHzRXTyXIxVKGhWAAcGXrVKcXvUvh8Np%2FlJg2bz4xWarvbk%2FFSrXYJj5IwNrOB02N5IzoVCGSyE%2BGWjofyUhgrq%2Ftlralnyp0GpXong2Sl8xB0%2Foucm4xtC60%3D&hmac_signature=t%3D1721967214552%2Cs%3D0a3505923c0541166952726c9c32c9dfdec41d51e4b1016093a6f53f0b9a76e9
```

{% endcode %}
{% endtab %}
{% endtabs %}

### Callback/Webhook

Every merchant account is expected to have configured a callback/webhook URL for collections. For all collections that transition to the final state (COMPLETED, FAILED or CANCELLED), a JSON POST request will be made to the callback URL. Sample callback payloads (request bodies) are shared below. Be sure to check out [Handling Notifications](/utility-functions/handling-notifications-callbacks) to see how you should verify the signature(s) in the request headers and how to respond.

{% tabs %}
{% tab title="Successful Mobile Money Collection" %}

```json
{
  "event": "transaction.completed",
  "payload": {
    "id": 20760,
    "merchant_reference": "MCTREFT2WMNWZ23SBN6Y",
    "internal_reference": "DUSUPAYRMGRXNNYBWATKJ",
    "transaction_type": "COLLECTION",
    "request_currency": "UGX",
    "transaction_amount": 2000000,
    "transaction_currency": "UGX",
    "transaction_charge": 60000,
    "transaction_account": "256787008803",
    "charge_customer": false,
    "total_credit": 1940000,
    "provider_code": "mtn_ug",
    "request_amount": 2000000,
    "customer_name": "JOHN DOE",
    "transaction_status": "COMPLETED",
    "status_message": "Transaction Completed Successfully"
  }
}
```

{% endtab %}

{% tab title="Failed Mobile Money Collection" %}

```json
{
  "event": "transaction.failed",
  "payload": {
    "id": 26609,
    "merchant_reference": "MCTREFNRFRTQA6SCWT5X",
    "internal_reference": "DUSUPAYWYUF8CR3ZRCGYU",
    "transaction_type": "COLLECTION",
    "request_currency": "UGX",
    "transaction_amount": 2000000,
    "transaction_currency": "UGX",
    "transaction_charge": 0,
    "transaction_account": "256777000002",
    "charge_customer": false,
    "total_credit": 0,
    "provider_code": "mtn_ug",
    "request_amount": 2000000,
    "customer_name": "JOHN DOE",
    "transaction_status": "FAILED",
    "status_message": "Balance Insufficient for the transaction"
  }
}
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
Either of the two (redirect/callback) can be used to confirm the final status of the transaction. We recommend that in either situation, the redirect/callback request is verified (by verifying the signatures)
{% endhint %}


# NGN Bank Transfers

The DusuPay API supports for funds collections from NGN bank accounts. Customers complete payments by transferring funds to a specified bank account. The guide describes the two supported modes

## Overview

Before starting, your merchant account needs to have been approved enabled to do funds collections from Nigerian banks. We equally recommend that you go through the [Getting Started](/funds-collection/getting-started) section to have a high-level understanding of the funds collection process.

## Collection Workflow Selection

The API allows the merchant to either use the hosted payment page (recommended) or build their own user experience based on the API workflow. With the hosted payment page, DusuPay handles the end to end customer journey and this seamless workflow creates a good user experience. Some merchants however wish to create custom experiences using their brand guidelines and, in this situation, the direct charge can be used.

The table below briefly describes the two collection workflows to select from.

<table><thead><tr><th width="532.6217041015625">Scenario</th><th>Resource</th></tr></thead><tbody><tr><td>You wish to use a managed user experience that handles the customer experience without any input from you, and you don't mind generic brand display <em><strong>(RECOMMENDED)</strong></em></td><td><a href="/pages/H8s6vW7NWSXPieu0nXf6">Hosted Page Workflow</a></td></tr><tr><td>You wish to have control of the user experience, page branding and directly call all the API endpoints yourself. (this requires a thorough understanding of the NGN Bank Transfer collection workflow as described on the page)</td><td><a href="/pages/tM7m4EmrKXO97W0JthOo">Direct Charge Workflow</a></td></tr></tbody></table>

{% hint style="info" %}
We strongly recommend that when the merchant chooses to use the direct charge workflow, they should first study the hosted page workflow before building their custom experiences. This ensures the customers have a smooth payment process
{% endhint %}


# NGN Bank Transfers - Direct Charge

This is one of the ways to process NGN payments using bank transfers. The section below will guide you through the process of accepting NGN payments using the direct API method.

## Workflow Overview

Study this diagram below to understand the customer journey workflow

```mermaid
sequenceDiagram
    actor Customer
    participant Merchant Portal
    participant DusuPay
    participant Bank

    Customer->>Merchant Portal: Initiates collection request
    Merchant Portal->>DusuPay: POST collections/initialize
    DusuPay-->>Merchant Portal: 202 Acknowledgement
    DusuPay->>Bank: Request virtual account details
    Bank-->>DusuPay: Account Details
    DusuPay->>Merchant Portal: transaction.processing callback request
    Merchant Portal->>Customer: Display charges + account details to the customer
    Customer->>Bank: Execute funds transfer
    Bank->>DusuPay: Acknowledgement of receipt
    DusuPay->>Merchant Portal: transaction.completed callback request
    Merchant Portal->>Customer: Display success page + give value
```

1. When your customer is ready to make an NGN payment, you can make the initial collection API request to the gateway, for which an acknowledgement will be done with 202 HTTP response code (you can optionally store the references). Show the customer a loading screen.
2. If there are no issues with the request, the DusuPay platform will proceed to obtain virtual bank details and trigger the [**transaction.processing**](/utility-functions/handling-notifications-callbacks/callback-events) event callback/webhook as described in the steps below. Details (charge information + account details) from this payload can be displayed to the customer.
3. As a precaution, listen to the [***request.failed***](/utility-functions/handling-notifications-callbacks/callback-events) and [***transaction.failed***](/utility-functions/handling-notifications-callbacks/callback-events) event callbacks just in case the request fails before obtaining the bank details. The reason for failure would be part of the callback payload and it's recommended that it's displayed to the customer.
4. If there was no error, the customer can follow the instructions to transfer funds to the account.
5. On success, the bank would notify the DusuPay platform, which would in turn notify you the merchant via your configured callback URL. The customer can be show the success screen.

{% hint style="info" %}
We recommend that this activity is tracked in your local/platform databases and poll for transaction status from your own data store and update the user interface accordingly. The DusuPay platform automatically retries callbacks if your platform doesn't respond as expected.
{% endhint %}

## Step 1: Obtain the required data for the payment request

The table below describes the request parameters that are used for the collection/charge request. Most/all will be collected from the paying customer.

<table><thead><tr><th width="219.5238037109375">Parameter</th><th width="93">Type</th><th width="106.485107421875">Required</th><th>Description</th></tr></thead><tbody><tr><td>merchant_reference</td><td>String</td><td>true</td><td>The unique reference for this request. It must be at least 8 characters long. Alternatively, the value <strong>auto</strong> can be passed, and a unique reference will be created for you by the API</td></tr><tr><td>transaction_method</td><td>String</td><td>true</td><td>The transaction method to be used. This will be <em><strong>BANK</strong></em> for this request</td></tr><tr><td>currency</td><td>String</td><td>true</td><td>The 3-character ISO currency code for the request currency</td></tr><tr><td>amount</td><td>Number</td><td>true</td><td>The amount being requested</td></tr><tr><td>provider_code</td><td>String</td><td>true</td><td>The provider code as obtained from the payment options <a href="/pages/E8qh5wAQIPuCQxEHFHxg#get-payment-options-list">list</a></td></tr><tr><td>customer_name</td><td>String</td><td>false</td><td>The name of the customer</td></tr><tr><td>customer_email</td><td>String</td><td>false</td><td>The email of the customer</td></tr><tr><td>description</td><td>String</td><td>true</td><td>The description/narration for the transaction. Between 10-30 characters</td></tr><tr><td>charge_customer</td><td>Boolean</td><td>false</td><td>Whether or not the customer should bear the charge for the transaction. By default, this is <strong>false</strong> to mean that the merchant bears the charge</td></tr><tr><td>allow_final_status_change</td><td>Boolean</td><td>false</td><td>Whether or not the final transaction status can be altered as described <a href="/pages/oIwPtHN5YxEe4a0U5aCk#transaction-final-status-change">here</a>. By default, this is <strong>true</strong> to mean DusuPay will alter the final transaction status under the circumstances described.</td></tr></tbody></table>

After collecting the necessary bank payment information from your customer, prepare your request payload as demonstrated below.

```json
{
  "merchant_reference": "auto",
  "transaction_method": "BANK",
  "currency": "NGN",
  "amount": 4000,
  "provider_code": "bank_ng",
  "customer_email": "johndoe@gmail.com",
  "customer_name": "JOHN DOE",
  "description": "Test Collection",
  "charge_customer": false,
  "allow_final_status_change": true
}
```

<mark style="color:green;">`POST`</mark> `https://sandboxapi.dusupay.com/collections/initialize`

The request is sent as a JSON body as demonstrated by the sample request below. Sample responses (acknowledgement and failure) are also shared.

```powershell
curl -X POST "https://sandboxapi.dusupay.com/collections/initialize" \
   -H 'Content-Type: application/json' \
   -H "x-api-version: 1" \
   -H "public-key: your-public-key" \
   -d '{
        "merchant_reference": "auto",
        "transaction_method": "BANK",
        "currency": "NGN",
        "amount": 4000,
        "provider_code": "bank_ng",
        "customer_email": "johndoe@gmail.com",
        "customer_name": "JOHN DOE",
        "description": "Test Collection",
        "charge_customer": false,
        "allow_final_status_change": true
    }'
```

{% tabs %}
{% tab title="202: Accepted - Request acknowledged for processing" %}

```json
{
  "code": 202,
  "status": "accepted",
  "message": "Request Accepted",
  "data": {
    "internal_reference": "DUSUPAYZAERK2SBE6WJAG",
    "merchant_reference": "MCTREFQSSBHMHU3RCTLA"
  }
}
```

{% endtab %}

{% tab title="400: Bad Request - Request is not formed as expected" %}

```json
{
  "code": 400,
  "status": "error",
  "message": "Invalid provider code (bank_ug)",
  "data": {}
}
```

{% endtab %}
{% endtabs %}

## Step 2: Handle the payment instructions webhook

If the request in step 1 is successful and responds with an acknowledgement (HTTP code 202), you should listen and handle the payment instructions webhook/callback. This will be sent to the collection callback URL that's configured on your merchant account. The callback is sent as a JSON POST request and the payload will help you determine the next course of action. Sample payloads below.

{% tabs %}
{% tab title="Instructions Payload" %}
{% code overflow="wrap" %}

```json
{
  "event": "transaction.processing",
  "payload": {
    "id": 14848,
    "merchant_reference": "MCTREFQSSBHMHU3RCTLA",
    "internal_reference": "DUSUPAYZAERK2SBE6WJAG",
    "transaction_type": "COLLECTION",
    "request_currency": "NGN",
    "request_amount": 4000,
    "transaction_currency": "NGN",
    "transaction_amount": 4000,
    "transaction_charge": 80,
    "charge_customer": false,
    "total_credit": 3920,
    "transaction_account": "1771825717058",
    "customer_name": "JOHN DOE",
    "provider_code": "bank_ng",
    "transaction_status": "PROCESSING",
    "status_message": "Transaction Initiated successfully. Deposit funds to the account",
    "payment_instructions": "<p>Please make a payment of <b> NGN 4,000 </b> to the following account.</p><p><table><tr><td>Account number:</td><td></td><td></td><td><b>2121562123</b></td></tr><tr></tr><tr></tr><tr></tr><tr><td>Bank Name:</td><td></td><td></td><td> DUSUPAY</td</tr><tr></tr><tr></tr><tr></tr><tr></tr><tr><td>Beneficiary:</td><td></td><td></td><td> <b> Dusupay Account</b></td></tr><tr></tr></table></p><p><b>Reminder: Use this account only for this transaction, and make sure to complete the payment before the time specified above expires. </b></p>",
    "sub_account_details": {
      "account_name": "DUSUPAY",
      "account_no": "2121562123",
      "bank_name": "WEMA",
      "amount": 4000
    },
    "authorization": {}
  }
}
```

{% endcode %}
{% endtab %}

{% tab title="Request Failed" %}
{% code overflow="wrap" %}

```json
{
  "event": "request.failed",
  "payload": {
    "id": 14848,
    "merchant_reference": "MCTREFQSSBHMHU3RCTLA",
    "internal_reference": "DUSUPAYZAERK2SBE6WJAG",
    "transaction_type": "COLLECTION",
    "request_currency": "NGN",
    "request_amount": 4000,
    "transaction_status": "FAILED",
    "status_message": "Failed to initialize the transaction. System exception!"
  }
}
```

{% endcode %}
{% endtab %}

{% tab title="Transaction Failed" %}
{% code overflow="wrap" %}

```json
{
  "event": "transaction.failed",
  "payload": {
    "id": 26609,
    "merchant_reference": "MCTREFQSSBHMHU3RCTLA",
    "internal_reference": "DUSUPAYZAERK2SBE6WJAG",
    "transaction_type": "COLLECTION",
    "request_currency": "NGN",
    "request_amount": 4000,
    "transaction_currency": "NGN",
    "transaction_charge": 0,
    "transaction_account": "1771825717058",
    "charge_customer": false,
    "total_debit": 0,
    "provider_code": "bank_ng",
    "transaction_amount": 4000,
    "customer_name": "JOHN DOE",
    "transaction_status": "FAILED",
    "status_message": "Transaction Failed. Billing failure"
  }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

Details from the payload need to be displayed for the customer so they can execute the fund to the transfer. A few things to note;

1. You could directly display the content of the ***payment\_instructions*** parameter OR implement a custom display using content in the ***sub\_account\_details*** object.
2. If the customer is bearing the transaction charge (`charge_customer=true`), we recommend that you display the value in the ***transaction\_charge*** so that the customer is aware of surcharge.

## Step 3: Handle the final status webhook

Every merchant account is expected to have configured a callback/webhook URL for collections. For all collections that transition to the final state (COMPLETED, FAILED or CANCELLED), a JSON POST request will be made to the callback URL. Sample callback payloads (request bodies) are shared below. Be sure to check out [Handling Notifications](/utility-functions/handling-notifications-callbacks) to see how you should verify the signature(s) in the request headers and how to respond.

{% tabs %}
{% tab title="Successful Bank Transfer" %}

```json
{
  "event": "transaction.completed",
  "payload": {
    "id": 20760,
    "merchant_reference": "MCTREFQSSBHMHU3RCTLA",
    "internal_reference": "DUSUPAYZAERK2SBE6WJAG",
    "transaction_type": "COLLECTION",
    "request_currency": "NGN",
    "transaction_amount": 4000,
    "transaction_currency": "NGN",
    "transaction_charge": 80,
    "transaction_account": "2121562123",
    "charge_customer": false,
    "total_credit": 3920,
    "provider_code": "bank_ng",
    "request_amount": 4000,
    "customer_name": "JOHN DOE",
    "institution_name": "GUARANTY TRUST BANK",
    "transaction_status": "COMPLETED",
    "status_message": "Transaction Completed Successfully"
  }
}
```

{% endtab %}

{% tab title="Failed Request" %}

```json
{
  "event": "transaction.failed",
  "payload": {
    "id": 26609,
    "merchant_reference": "MCTREFQSSBHMHU3RCTLA",
    "internal_reference": "DUSUPAYZAERK2SBE6WJAG",
    "transaction_type": "COLLECTION",
    "request_currency": "NGN",
    "transaction_amount": 40000,
    "transaction_currency": "NGN",
    "transaction_charge": 0,
    "transaction_account": "2121562456",
    "charge_customer": false,
    "total_credit": 0,
    "provider_code": "mtn_ug",
    "request_amount": 40000,
    "customer_name": "JOHN DOE",
    "transaction_status": "FAILED",
    "status_message": "Request timed out without a transfer"
  }
}
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
We recommend that the webhook/callback requests are verified for authenticity using any of the described signature verification methods.
{% endhint %}


# NGN Bank Transfers - Hosted Page

The hosted page workflow is the other way of accepting NGN bank payments. The merchant initiates a request and in response is given a URL to which the user is redirected to complete the payment

## Overview

When the initial API request is successful, the customer will be redirected to a payment URL where they'll be guided to complete the transaction. During the process, a **unique temporary virtual bank account** will be generated, and it is to this account that the customer will transfer the funds. This virtual bank account is a temporary, single-use bank account that can be used to accept one-time payments for transactions. The account is only valid and usable for a limited period of time and for a single transaction, after which it expires.

## Step 1: Obtain the required data for the payment request

The table below describes the request parameters that are used for the collection/charge request. Most/all will be collected from the paying customer.

<table><thead><tr><th width="219.50128173828125">Parameter</th><th width="93">Type</th><th width="106.5086669921875">Required</th><th>Description</th></tr></thead><tbody><tr><td>merchant_reference</td><td>String</td><td>true</td><td>The unique reference for this request. It must be at least 8 characters long. Alternatively, the value <strong>auto</strong> can be passed, and a unique reference will be created for you by the API</td></tr><tr><td>transaction_method</td><td>String</td><td>true</td><td>The transaction method to be used. This will be <em><strong>BANK</strong></em> for this request</td></tr><tr><td>currency</td><td>String</td><td>true</td><td>The 3-character ISO currency code for the request currency</td></tr><tr><td>amount</td><td>Number</td><td>true</td><td>The amount being requested</td></tr><tr><td>provider_code</td><td>String</td><td>true</td><td>The provider code as obtained from the payment options <a href="/pages/E8qh5wAQIPuCQxEHFHxg#get-payment-options-list">list</a></td></tr><tr><td>customer_name</td><td>String</td><td>false</td><td>The name of the customer</td></tr><tr><td>customer_email</td><td>String</td><td>false</td><td>The email of the customer</td></tr><tr><td>description</td><td>String</td><td>true</td><td>The description/narration for the transaction. Between 10-30 characters</td></tr><tr><td>charge_customer</td><td>Boolean</td><td>false</td><td>Whether or not the customer should bear the charge for the transaction. By default, this is <strong>false</strong> to mean that the merchant bears the charge</td></tr><tr><td>allow_final_status_change</td><td>Boolean</td><td>false</td><td>Whether or not the final transaction status can be altered as described <a href="/pages/oIwPtHN5YxEe4a0U5aCk#transaction-final-status-change">here</a>. By default, this is <strong>true</strong> to mean DusuPay will alter the final transaction status under the circumstances described.</td></tr><tr><td>redirect_url</td><td>String</td><td>true</td><td>The HTTPs redirect URL to which the API will redirect when the payment is successful/failed</td></tr></tbody></table>

After collecting the necessary bank payment information from your customer, prepare your request payload as demonstrated below.

```json
{
	"merchant_reference": "auto",
	"transaction_method": "BANK",
	"currency": "NGN",
	"amount": 4000,
	"provider_code": "bank_ng",
	"customer_email": "johndoe@gmail.com",
	"customer_name": "JOHN DOE",
	"description": "Test Collection",
	"charge_customer": false,
	"allow_final_status_change": true,
	"redirect_url": "https://your-redirect-url"
}
```

<mark style="color:green;">`POST`</mark> `https://sandboxapi.dusupay.com/collections/initialize`

The request is sent as a JSON body as demonstrated by the sample request below. Sample responses (acknowledgement and failure) are also shared.

```powershell
curl -X POST "https://sandboxapi.dusupay.com/collections/initialize" \
   -H 'Content-Type: application/json' \
   -H "x-api-version: 1" \
   -H "public-key: your-public-key" \
   -d '{
        "merchant_reference": "auto",
        "transaction_method": "BANK",
        "currency": "NGN",
        "amount": 4000,
        "provider_code": "bank_ng",
        "customer_email": "johndoe@gmail.com",
        "customer_name": "JOHN DOE",
        "description": "Test Collection",
        "charge_customer": false,
        "allow_final_status_change": true,
        "redirect_url": "https://your-redirect-url"
    }'
```

{% tabs %}
{% tab title="202: Accepted - Request acknowledged for processing" %}

```json
{
    "code": 202,
    "status": "accepted",
    "message": "Request Accepted",
    "data": {
        "internal_reference": "DUSUPAYZAERK2SBE6WJAG",
        "merchant_reference": "MCTREFQSSBHMHU3RCTLA",
        "payment_url": "https://devpay.dusupay.com/pay/collection/DUSUPAYZAERK2SBE6WJAG"
    }
}
```

{% endtab %}

{% tab title="400: Bad Request - Request is not formed as expected" %}

```json
{
    "code": 400,
    "status": "error",
    "message": "Invalid provider code (bank_ug)",
    "data": {}
}
```

{% endtab %}
{% endtabs %}

## Step 2: Redirect to Payment URL

Notice the `payment_url` parameter in the callback body. The URL should be loaded in the browser so that the customer can proceed with the transaction. When the page loads, the customer will be guided through the payment process and on success/failure, the customer will be redirected to the `redirect_url` sent by the merchant in the request. Additionally, a callback/webhook will be sent to the configured collection callback URL.

## Step 3: Handle the redirect and/or callback

### Redirect

On success/failure of the transaction, the customer will be redirected to the URL that was passed in the `redirect_url` request parameter. The sample requests below demonstrated the success and failure scenarios. You can copy the URL and paste it on [this](https://semalt.tools/en/url-parser) online resource in order to view the query parameters therein.

{% tabs %}
{% tab title="Redirect on success" %}
{% code overflow="wrap" %}

```powershell
https://webhook.site/cc0393dc-fe49-401b-ba56-da9e099a3648?id=33911&event=transaction.completed&merchant_reference=MCTREFZQTUDRC38TLYUV&internal_reference=DUSUPAYHYJQ3ZUJMPGFG5&transaction_type=COLLECTION&transaction_status=COMPLETED&status_message=Transaction%20Completed%20Successfully&rsa_signature=Y23aNOaOs6NfOnrhpZrWGAOI4bMR5kNobFnrOBlqF%2FY7f03aBpHZx2rcmU3q2P9zS2w61xs3FsT%2FipRI9F21dNJ0UxDQci7yQ%2B7CZbk6kJQbfX9Ht7OIhH2%2F7cv%2FNUXDmXzZQYST3Qwe9QIhea1SIImWRzR8HKAeEeoVRPLDqxohLX4A0S91hjrZVO%2BjohZJLazIeXFrOaJyIu3xPQ7bzJi%2BfVhw6ry3R7NknIj5y752WpW0CqvxXKr3wPUIXy99tnN%2FCWLG65AmVL9GkBZj0j12%2B8ztBpknPBuGKWxndJB7zkkPTCmA%2BA1m21f3RENYTNH7nHet5zp5Iyq%2BkmtVVUmEcfvfAwAxs0sP0fYTFU8iSX6wvB6UEvk3AWgrprlb1XGd3sRgFgCb3HjzA7%2B8RBi%2Fw59qIXqK4LlbOtZw2tPc7MjLeXq%2BmqzrO8SRxyuvu6lX0HPhaHSXAGq%2BvljA1Kk4ZNLdSWxYCC2IgI7%2BPomrfWW1I6pNIF%2BudPTIYTPN6EEdMUwFBltyAoh%2ByHl6K%2F%2Fcz4FfE6OgZwJkbQ8661sHqiB2ND%2FhSQh%2B0Qre1tQTLx7BGQTSGdD34CLn%2FQICIlqNCvKbMF4ZO208xYaBct01T6Xa23zykkrmlTviEMJgSIo3zj2KpN%2BTF1X5jZOXLszBL20o1SeQlY9%2BhRzshzY%3D&hmac_signature=t%3D1721639784089%2Cs%3D1ec4e0f7749397faea80958bca9ab2c6d8e7448c3ee02f958881a392ce299128
```

{% endcode %}
{% endtab %}

{% tab title="Redirect on failure/cancellation" %}
{% code overflow="wrap" %}

```powershell
https://mysite.com?id=34639&event=transaction.failed&merchant_reference=MCTREF8DQRHYTNCQTACH&internal_reference=DUSUPAY8A54ZUJFGSMENE&transaction_type=COLLECTION&transaction_status=FAILED&status_message=Balance%20Insufficient%20for%20the%20transaction&rsa_signature=yvODC%2BViCiZmlEEEbQX1B%2FfQ0kNzrYqRN0c3Y8MjzZ%2FiMNVzf%2BUS36Ec8P1a0rOKgyc0l7KVbzEJwMZu0nasAHFuCeSOebs5NOHvSNJkAbQE2nZXgxvo1uQtJOMnbYZZKIvswemRTRGEyAKl7bRGc2AXzYRzJBVOYBVDOM0o36FUpfNNFrucVjvrmJJD8YtRtIXk3RjBzXkhV%2B3KAjWx0KU8bh424OyZUwsRTaN0MeuQMFY%2FkeuFvNKPzxKtN%2BaJqcx4aDZes6thevgfCljgne0ibC9Zy1eDCYP8A1T%2BNTS%2FHUZBuy1Y%2BzAmQ%2BMjnoiI%2FVk2I%2BuoRkHU01%2BMV%2Bo4CFGAoheoQ9N%2B7KFN0f4oAdT3G%2BJZtcwitZ0Q3Jo%2FW3aYNyvrPQHt88hjOtVHKhSNEwUJ8QNfy42WiZJOix4p5I5oyePdYGW5ia09MuaY6BkV8o9mdotcWGpn6O1PsxnxfduI6gbGLWtcxjrat75LeMhGjPLYcJA9CQECK7NvBFHOBUA%2F9rR5gReBVEkdVf5N8q551DvJfR4nENxZukaVMzjhq2IIndOHzRXTyXIxVKGhWAAcGXrVKcXvUvh8Np%2FlJg2bz4xWarvbk%2FFSrXYJj5IwNrOB02N5IzoVCGSyE%2BGWjofyUhgrq%2Ftlralnyp0GpXong2Sl8xB0%2Foucm4xtC60%3D&hmac_signature=t%3D1721967214552%2Cs%3D0a3505923c0541166952726c9c32c9dfdec41d51e4b1016093a6f53f0b9a76e9
```

{% endcode %}
{% endtab %}
{% endtabs %}

### Callback/Webhook

Every merchant account is expected to have configured a callback/webhook URL for collections. For all collections that transition to the final state (COMPLETED, FAILED or CANCELLED), a JSON POST request will be made to the callback URL. Sample callback payloads (request bodies) are shared below. Be sure to check out [Handling Notifications](/utility-functions/handling-notifications-callbacks) to see how you should verify the signature(s) in the request headers and how to respond.

{% tabs %}
{% tab title="Successful Bank Transfer" %}

```json
{
    "event": "transaction.completed",
    "payload": {
        "id": 20760,
        "merchant_reference": "MCTREFT2WMNWZ23SBN6Y",
        "internal_reference": "DUSUPAYRMGRXNNYBWATKJ",
        "transaction_type": "COLLECTION",
        "request_currency": "NGN",
        "transaction_amount": 4000,
        "transaction_currency": "NGN",
        "transaction_charge": 80,
        "transaction_account": "2121562123",
        "charge_customer": false,
        "total_credit": 3920,
        "provider_code": "bank_ng",
        "request_amount": 4000,
        "customer_name": "JOHN DOE",
        "institution_name": "GUARANTY TRUST BANK",
        "transaction_status": "COMPLETED",
        "status_message": "Transaction Completed Successfully"
    }
}
```

{% endtab %}

{% tab title="Failed Request" %}

```json
{
      "event": "transaction.failed",
      "payload": {
        "id": 26609,
        "merchant_reference": "MCTREFNRFRTQA6SCWT5X",
        "internal_reference": "DUSUPAYWYUF8CR3ZRCGYU",
        "transaction_type": "COLLECTION",
        "request_currency": "NGN",
        "transaction_amount": 40000,
        "transaction_currency": "NGN",
        "transaction_charge": 0,
        "transaction_account": "2121562456",
        "charge_customer": false,
        "total_credit": 0,
        "provider_code": "mtn_ug",
        "request_amount": 40000,
        "customer_name": "JOHN DOE",
        "transaction_status": "FAILED",
        "status_message": "Request timed out without a transfer"
      }
    }
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
Either of the two (redirect/callback) can be used to confirm the final status of the transaction. We recommend that in either situation, the redirect/callback request is verified (by verifying the signatures)
{% endhint %}


# ZAR Bank Collections

ZAR bank EFT transactions are supported on the DusuPay API. The customer is redirected to the payment URL and there, they're allowed to log into their online banking and complete the transaction.

## Overview

Before starting, your merchant account needs to have been enabled to do funds collections from South African banks. We equally recommend that you go through the [Getting Started](/funds-collection/getting-started) section to have a high-level understanding of the funds collection process.

When the initial API request is successful, the customer will be redirected to a payment URL where they'll be guided to complete the transaction. During the process, the customer chooses their bank and they're guided to access their online banking portal and while there, they authorize the payment for the requested amount.

## Step 1: Obtain the required data for the payment request

The table below describes the request parameters that are used for the collection/charge request. Most/all will be collected from the paying customer.

<table><thead><tr><th width="200">Parameter</th><th width="93">Type</th><th width="98">Required</th><th>Description</th></tr></thead><tbody><tr><td>merchant_reference</td><td>String</td><td>true</td><td>The unique reference for this request. It must be at least 8 characters long. Alternatively, the value <strong>auto</strong> can be passed, and a unique reference will be created for you by the API</td></tr><tr><td>transaction_method</td><td>String</td><td>true</td><td>The transaction method to be used. This will be <em><strong>BANK</strong></em> for this request</td></tr><tr><td>currency</td><td>String</td><td>true</td><td>The 3-character ISO currency code for the request currency</td></tr><tr><td>amount</td><td>Number</td><td>true</td><td>The amount being requested</td></tr><tr><td>provider_code</td><td>String</td><td>true</td><td>The provider code as obtained from the payment options <a href="/pages/E8qh5wAQIPuCQxEHFHxg#get-payment-options-list">list</a></td></tr><tr><td>customer_name</td><td>String</td><td>false</td><td>The name of the customer</td></tr><tr><td>customer_email</td><td>String</td><td>false</td><td>The email of the customer</td></tr><tr><td>description</td><td>String</td><td>true</td><td>The description/narration for the transaction. Between 10-30 characters</td></tr><tr><td>charge_customer</td><td>Boolean</td><td>false</td><td>Whether or not the customer should bear the charge for the transaction. By default, this is <strong>false</strong> to mean that the merchant bears the charge</td></tr><tr><td>allow_final_status_change</td><td>Boolean</td><td>false</td><td>Whether or not the final transaction status can be altered as described <a href="/pages/oIwPtHN5YxEe4a0U5aCk#transaction-final-status-change">here</a>. By default, this is <strong>true</strong> to mean DusuPay will alter the final transaction status under the circumstances described.</td></tr><tr><td>redirect_url</td><td>String</td><td>true</td><td>The HTTPs redirect URL to which the API will redirect when the payment is successful/failed</td></tr></tbody></table>

After collecting the necessary bank payment information from your customer, prepare your request payload as demonstrated below.

```json
{
	"merchant_reference": "auto",
	"transaction_method": "BANK",
	"currency": "ZAR",
	"amount": 50,
	"provider_code": "bank_za",
	"customer_email": "johndoe@gmail.com",
	"customer_name": "JOHN DOE",
	"description": "Test Collection",
	"charge_customer": false,
	"allow_final_status_change": true,
	"redirect_url": "https://your-redirect-url"
}
```

<mark style="color:green;">`POST`</mark> `https://sandboxapi.dusupay.com/collections/initialize`

The request is sent as a JSON body as demonstrated by the sample request below. Sample responses (acknowledgement and failure) are also shared.

```powershell
curl -X POST "https://sandboxapi.dusupay.com/collections/initialize" \
   -H 'Content-Type: application/json' \
   -H "x-api-version: 1" \
   -H "public-key: your-public-key" \
   -d '{
        "merchant_reference": "auto",
        "transaction_method": "BANK",
        "currency": "ZAR",
        "amount": 50,
        "provider_code": "bank_za",
        "customer_email": "johndoe@gmail.com",
        "customer_name": "JOHN DOE",
        "description": "Test Collection",
        "charge_customer": false,
        "allow_final_status_change": true,
        "redirect_url": "https://your-redirect-url"
    }'
```

{% tabs %}
{% tab title="202: Accepted - Request acknowledged for processing" %}

```json
{
    "code": 202,
    "status": "accepted",
    "message": "Request Accepted",
    "data": {
        "internal_reference": "DUSUPAYZAERK2SBE6WJAG",
        "merchant_reference": "MCTREFQSSBHMHU3RCTLA",
        "payment_url": "https://devpay.dusupay.com/pay/collection/DUSUPAYZAERK2SBE6WJAG"
    }
}
```

{% endtab %}

{% tab title="400: Bad Request - Request is not formed as expected" %}

```json
{
    "code": 400,
    "status": "error",
    "message": "Invalid provider code (bank_ug)",
    "data": {}
}
```

{% endtab %}
{% endtabs %}

## Step 2: Redirect to Payment URL

Notice the `payment_url` parameter in the callback body. The URL should be loaded in the browser so that the customer can proceed with the transaction. When the page loads, the customer will be guided through the payment process and on success/failure, the customer will be redirected to the `redirect_url` sent by the merchant in the request. Additionally, a callback/webhook will be sent to the configured collection callback URL.

## Step 3: Handle the redirect and/or callback

### Redirect

On success/failure of the transaction, the customer will be redirected to the URL that was passed in the `redirect_url` request parameter. The sample requests below demonstrated the success and failure scenarios. You can copy the URL and paste it on [this](https://semalt.tools/en/url-parser) online resource in order to view the query parameters therein.

{% tabs %}
{% tab title="Redirect on success" %}
{% code overflow="wrap" %}

```powershell
https://webhook.site/cc0393dc-fe49-401b-ba56-da9e099a3648?id=33911&event=transaction.completed&merchant_reference=MCTREFZQTUDRC38TLYUV&internal_reference=DUSUPAYHYJQ3ZUJMPGFG5&transaction_type=COLLECTION&transaction_status=COMPLETED&status_message=Transaction%20Completed%20Successfully&rsa_signature=Y23aNOaOs6NfOnrhpZrWGAOI4bMR5kNobFnrOBlqF%2FY7f03aBpHZx2rcmU3q2P9zS2w61xs3FsT%2FipRI9F21dNJ0UxDQci7yQ%2B7CZbk6kJQbfX9Ht7OIhH2%2F7cv%2FNUXDmXzZQYST3Qwe9QIhea1SIImWRzR8HKAeEeoVRPLDqxohLX4A0S91hjrZVO%2BjohZJLazIeXFrOaJyIu3xPQ7bzJi%2BfVhw6ry3R7NknIj5y752WpW0CqvxXKr3wPUIXy99tnN%2FCWLG65AmVL9GkBZj0j12%2B8ztBpknPBuGKWxndJB7zkkPTCmA%2BA1m21f3RENYTNH7nHet5zp5Iyq%2BkmtVVUmEcfvfAwAxs0sP0fYTFU8iSX6wvB6UEvk3AWgrprlb1XGd3sRgFgCb3HjzA7%2B8RBi%2Fw59qIXqK4LlbOtZw2tPc7MjLeXq%2BmqzrO8SRxyuvu6lX0HPhaHSXAGq%2BvljA1Kk4ZNLdSWxYCC2IgI7%2BPomrfWW1I6pNIF%2BudPTIYTPN6EEdMUwFBltyAoh%2ByHl6K%2F%2Fcz4FfE6OgZwJkbQ8661sHqiB2ND%2FhSQh%2B0Qre1tQTLx7BGQTSGdD34CLn%2FQICIlqNCvKbMF4ZO208xYaBct01T6Xa23zykkrmlTviEMJgSIo3zj2KpN%2BTF1X5jZOXLszBL20o1SeQlY9%2BhRzshzY%3D&hmac_signature=t%3D1721639784089%2Cs%3D1ec4e0f7749397faea80958bca9ab2c6d8e7448c3ee02f958881a392ce299128
```

{% endcode %}
{% endtab %}

{% tab title="Redirect on failure/cancellation" %}
{% code overflow="wrap" %}

```powershell
https://mysite.com?id=34639&event=transaction.failed&merchant_reference=MCTREF8DQRHYTNCQTACH&internal_reference=DUSUPAY8A54ZUJFGSMENE&transaction_type=COLLECTION&transaction_status=FAILED&status_message=Balance%20Insufficient%20for%20the%20transaction&rsa_signature=yvODC%2BViCiZmlEEEbQX1B%2FfQ0kNzrYqRN0c3Y8MjzZ%2FiMNVzf%2BUS36Ec8P1a0rOKgyc0l7KVbzEJwMZu0nasAHFuCeSOebs5NOHvSNJkAbQE2nZXgxvo1uQtJOMnbYZZKIvswemRTRGEyAKl7bRGc2AXzYRzJBVOYBVDOM0o36FUpfNNFrucVjvrmJJD8YtRtIXk3RjBzXkhV%2B3KAjWx0KU8bh424OyZUwsRTaN0MeuQMFY%2FkeuFvNKPzxKtN%2BaJqcx4aDZes6thevgfCljgne0ibC9Zy1eDCYP8A1T%2BNTS%2FHUZBuy1Y%2BzAmQ%2BMjnoiI%2FVk2I%2BuoRkHU01%2BMV%2Bo4CFGAoheoQ9N%2B7KFN0f4oAdT3G%2BJZtcwitZ0Q3Jo%2FW3aYNyvrPQHt88hjOtVHKhSNEwUJ8QNfy42WiZJOix4p5I5oyePdYGW5ia09MuaY6BkV8o9mdotcWGpn6O1PsxnxfduI6gbGLWtcxjrat75LeMhGjPLYcJA9CQECK7NvBFHOBUA%2F9rR5gReBVEkdVf5N8q551DvJfR4nENxZukaVMzjhq2IIndOHzRXTyXIxVKGhWAAcGXrVKcXvUvh8Np%2FlJg2bz4xWarvbk%2FFSrXYJj5IwNrOB02N5IzoVCGSyE%2BGWjofyUhgrq%2Ftlralnyp0GpXong2Sl8xB0%2Foucm4xtC60%3D&hmac_signature=t%3D1721967214552%2Cs%3D0a3505923c0541166952726c9c32c9dfdec41d51e4b1016093a6f53f0b9a76e9
```

{% endcode %}
{% endtab %}
{% endtabs %}

### Callback/Webhook

Every merchant account is expected to have configured a callback/webhook URL for collections. For all collections that transition to the final state (COMPLETED, FAILED or CANCELLED), a JSON POST request will be made to the callback URL. Sample callback payloads (request bodies) are shared below. Be sure to check out [Handling Notifications](/utility-functions/handling-notifications-callbacks) to see how you should verify the signature(s) in the request headers and how to respond.

{% tabs %}
{% tab title="Successful Bank Transfer" %}

```json
{
    "event": "transaction.completed",
    "payload": {
        "id": 20760,
        "merchant_reference": "MCTREFT2WMNWZ23SBN6Y",
        "internal_reference": "DUSUPAYRMGRXNNYBWATKJ",
        "transaction_type": "COLLECTION",
        "request_currency": "ZAR",
        "transaction_amount": 50,
        "transaction_currency": "ZAR",
        "transaction_charge": 1,
        "transaction_account": "2121562123",
        "charge_customer": false,
        "total_credit": 49,
        "provider_code": "bank_ng",
        "request_amount": 50,
        "customer_name": "JOHN DOE",
        "institution_name": "FNB",
        "transaction_status": "COMPLETED",
        "status_message": "Transaction Completed Successfully"
    }
}
```

{% endtab %}

{% tab title="Failed Request" %}

```json
{
      "event": "transaction.failed",
      "payload": {
        "id": 26609,
        "merchant_reference": "MCTREFNRFRTQA6SCWT5X",
        "internal_reference": "DUSUPAYWYUF8CR3ZRCGYU",
        "transaction_type": "COLLECTION",
        "request_currency": "NGN",
        "transaction_amount": 40000,
        "transaction_currency": "NGN",
        "transaction_charge": 0,
        "transaction_account": "2121562456",
        "charge_customer": false,
        "total_credit": 0,
        "provider_code": "mtn_ug",
        "request_amount": 40000,
        "customer_name": "JOHN DOE",
        "transaction_status": "FAILED",
        "status_message": "Request timed out without a transfer"
      }
    }
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
Either of the two (redirect/callback) can be used to confirm the final status of the transaction. We recommend that in either situation, the redirect/callback request is verified (by verifying the signatures)
{% endhint %}


# Card Payments

The DusuPay API supports card payment as described in the Supported Countries section. The API allows for direct charge (sending card details directly) or the hosted page that's powered by DusuPay.

## Prerequisites

1. The merchant needs to have an approved merchant account on the live platform. This is only achieved after the go-live requirements are met (being on sandbox and clearing the UAT tests).
2. The card payment options need to be configured on the designated merchant account. The support teams will be available to assist with this.

## Collection Workflow Selection

The API allows the merchant to either use the hosted payment page (recommended) or build their own user experience based on the API workflow. With the hosted payment page, DusuPay handles all the different variations of authorizing card payments e.g. OTP, PIN, address verification etc, and this seamless workflow creates a good user experience. Some merchants however wish to create custom experiences using their brand guidelines and, in this situation, the direct charge can be used.

The table below briefly describes the two card payment workflows to select from.

<table><thead><tr><th width="522">Scenario</th><th>Resource</th></tr></thead><tbody><tr><td>You wish to use a managed user experience that handles all card workflow variations without any input from you, even when issuer policies change from time to time to require other parameters. <em><strong>(RECOMMENDED)</strong></em></td><td><a href="/pages/s8hbS5CmD233e2C7DWYz">Hosted Payment Page</a></td></tr><tr><td>You wish to have control of the user experience, page branding and directly call all the API endpoints yourself. (this requires a thorough understanding of the card API workflow and the variations in authorization modes)</td><td><a href="/pages/W3dZsy7UL1A8bEB7Ukql">Direct Card Payment (S2S)</a></td></tr></tbody></table>


# Hosted Payment Page

The hosted page workflow is our recommended way of accepting card payments. The merchant initiates a request and in response is given a URL to which the user is redirected to complete the payment.

## Overview

Before starting, your merchant account needs to have been enabled to do funds collections via card in the specified supported countries. We equally recommend that you go through the [Getting Started](/funds-collection/getting-started) section to have a high-level understanding of the funds collection process.

When the initial API request is successful, a payment URL is returned, and the customer will be redirected to this URL where they'll be guided to complete the transaction. During the process, the customer will fill a card payment form and submit for processing. Depending on the authorization scheme, the customer will be navigated accordingly so that they can complete the payment

## Step 1: Form the payment request payload

The table below describes the request parameters that are used for the collection/charge request. Most/all will be collected from the paying customer.

<table><thead><tr><th width="200">Parameter</th><th width="93">Type</th><th width="98">Required</th><th>Description</th></tr></thead><tbody><tr><td>merchant_reference</td><td>String</td><td>true</td><td>The unique reference for this request. It must be at least 8 characters long. Alternatively, the value <strong>auto</strong> can be passed, and a unique reference will be created for you by the API</td></tr><tr><td>transaction_method</td><td>String</td><td>true</td><td>The transaction method to be used. This will be <em><strong>CARD</strong></em> for this request</td></tr><tr><td>currency</td><td>String</td><td>true</td><td>The 3-character ISO currency code for the request currency</td></tr><tr><td>amount</td><td>Number</td><td>true</td><td>The amount being requested</td></tr><tr><td>provider_code</td><td>String</td><td>true</td><td>The provider code as obtained from the payment options <a href="/pages/E8qh5wAQIPuCQxEHFHxg">list</a></td></tr><tr><td>customer_name</td><td>String</td><td>false</td><td>The name of the customer</td></tr><tr><td>customer_email</td><td>String</td><td>false</td><td>The email of the customer</td></tr><tr><td>description</td><td>String</td><td>true</td><td>The description/narration for the transaction. Between 10-30 characters</td></tr><tr><td>charge_customer</td><td>Boolean</td><td>false</td><td>Whether or not the customer should bear the charge for the transaction. By default, this is <strong>false</strong> to mean that the merchant bears the charge</td></tr><tr><td>allow_final_status_change</td><td>Boolean</td><td>false</td><td>Whether or not the final transaction status can be altered as described <a href="/pages/oIwPtHN5YxEe4a0U5aCk#transaction-final-status-change">here</a>. By default, this is <strong>true</strong> to mean DusuPay will alter the final transaction status under the circumstances described.</td></tr><tr><td>redirect_url</td><td>String</td><td>true</td><td>The HTTPs redirect URL to which the API will redirect when the payment is successful/failed</td></tr></tbody></table>

After collecting the necessary card payment information from your customer, prepare your request payload as demonstrated below.

```json
{
	"merchant_reference": "auto",
	"transaction_method": "CARD",
	"currency": "NGN",
	"amount": 1000,
	"provider_code": "local_ngn",
	"customer_email": "johndoe@gmail.com",
	"customer_name": "JOHN DOE",
	"description": "Test Collection",
	"charge_customer": false,
	"allow_final_status_change": true,
	"redirect_url": "https://your-redirect-url"
}
```

<mark style="color:green;">`POST`</mark> `https://sandboxapi.dusupay.com/collections/initialize`

The request is sent as a JSON body as demonstrated by the sample request below. Sample responses (acknowledgement and failure) are also shared.

```powershell
curl -X POST "https://sandboxapi.dusupay.com/collections/initialize" \
   -H 'Content-Type: application/json' \
   -H "x-api-version: 1" \
   -H "public-key: your-public-key" \
   -d '{
        "merchant_reference": "auto",
        "transaction_method": "CARD",
        "currency": "NGN",
        "amount": 1000,
        "provider_code": "local_ngn",
        "customer_email": "johndoe@gmail.com",
        "customer_name": "JOHN DOE",
        "description": "Test Collection",
        "charge_customer": false,
        "allow_final_status_change": true,
        "redirect_url": "https://your-redirect-url"
    }'
```

{% tabs %}
{% tab title="202: Accepted - Request acknowledged for processing" %}

```json
{
    "code": 202,
    "status": "accepted",
    "message": "Request Accepted",
    "data": {
        "internal_reference": "DUSUPAYZAERK2SBE6WJAG",
        "merchant_reference": "MCTREFQSSBHMHU3RCTLA",
        "payment_url": "https://devpay.dusupay.com/pay/collection/DUSUPAYZAERK2SBE6WJAG"
    }
}
```

{% endtab %}

{% tab title="400: Bad Request - Request not formed as expected" %}

```json
{
    "code": 400,
    "status": "error",
    "message": "Invalid provider code (local_ugx)",
    "data": {}
}
```

{% endtab %}
{% endtabs %}

## Step 2: Redirect to Payment URL

Notice the `payment_url` parameter in the callback body. The URL should be loaded in the browser so that the customer can proceed with the transaction. When the page loads, the customer will be guided through the payment process and on success/failure, the customer will be redirected to the `redirect_url` sent by the merchant in the request. Additionally, a callback/webhook will be sent to the configured collection callback URL.

## Step 3: Handle the redirect and/or callback

### Redirect

On success/failure of the transaction, the customer will be redirected to the URL that was passed in the `redirect_url` request parameter. The sample requests below demonstrated the success and failure scenarios. You can copy the URL and paste it on [this](https://semalt.tools/en/url-parser) online resource in order to view the query parameters therein.

{% tabs %}
{% tab title="Redirect on success" %}
{% code overflow="wrap" %}

```
https://webhook.site/cc0393dc-fe49-401b-ba56-da9e099a3648?id=33911&event=transaction.completed&merchant_reference=MCTREFZQTUDRC38TLYUV&internal_reference=DUSUPAYHYJQ3ZUJMPGFG5&transaction_type=COLLECTION&transaction_status=COMPLETED&status_message=Transaction%20Completed%20Successfully&rsa_signature=Y23aNOaOs6NfOnrhpZrWGAOI4bMR5kNobFnrOBlqF%2FY7f03aBpHZx2rcmU3q2P9zS2w61xs3FsT%2FipRI9F21dNJ0UxDQci7yQ%2B7CZbk6kJQbfX9Ht7OIhH2%2F7cv%2FNUXDmXzZQYST3Qwe9QIhea1SIImWRzR8HKAeEeoVRPLDqxohLX4A0S91hjrZVO%2BjohZJLazIeXFrOaJyIu3xPQ7bzJi%2BfVhw6ry3R7NknIj5y752WpW0CqvxXKr3wPUIXy99tnN%2FCWLG65AmVL9GkBZj0j12%2B8ztBpknPBuGKWxndJB7zkkPTCmA%2BA1m21f3RENYTNH7nHet5zp5Iyq%2BkmtVVUmEcfvfAwAxs0sP0fYTFU8iSX6wvB6UEvk3AWgrprlb1XGd3sRgFgCb3HjzA7%2B8RBi%2Fw59qIXqK4LlbOtZw2tPc7MjLeXq%2BmqzrO8SRxyuvu6lX0HPhaHSXAGq%2BvljA1Kk4ZNLdSWxYCC2IgI7%2BPomrfWW1I6pNIF%2BudPTIYTPN6EEdMUwFBltyAoh%2ByHl6K%2F%2Fcz4FfE6OgZwJkbQ8661sHqiB2ND%2FhSQh%2B0Qre1tQTLx7BGQTSGdD34CLn%2FQICIlqNCvKbMF4ZO208xYaBct01T6Xa23zykkrmlTviEMJgSIo3zj2KpN%2BTF1X5jZOXLszBL20o1SeQlY9%2BhRzshzY%3D&hmac_signature=t%3D1721639784089%2Cs%3D1ec4e0f7749397faea80958bca9ab2c6d8e7448c3ee02f958881a392ce299128
```

{% endcode %}
{% endtab %}

{% tab title="Redirect on failure/cancellation" %}
{% code overflow="wrap" %}

```
https://mysite.com?id=34639&event=transaction.failed&merchant_reference=MCTREF8DQRHYTNCQTACH&internal_reference=DUSUPAY8A54ZUJFGSMENE&transaction_type=COLLECTION&transaction_status=FAILED&status_message=Balance%20Insufficient%20for%20the%20transaction&rsa_signature=yvODC%2BViCiZmlEEEbQX1B%2FfQ0kNzrYqRN0c3Y8MjzZ%2FiMNVzf%2BUS36Ec8P1a0rOKgyc0l7KVbzEJwMZu0nasAHFuCeSOebs5NOHvSNJkAbQE2nZXgxvo1uQtJOMnbYZZKIvswemRTRGEyAKl7bRGc2AXzYRzJBVOYBVDOM0o36FUpfNNFrucVjvrmJJD8YtRtIXk3RjBzXkhV%2B3KAjWx0KU8bh424OyZUwsRTaN0MeuQMFY%2FkeuFvNKPzxKtN%2BaJqcx4aDZes6thevgfCljgne0ibC9Zy1eDCYP8A1T%2BNTS%2FHUZBuy1Y%2BzAmQ%2BMjnoiI%2FVk2I%2BuoRkHU01%2BMV%2Bo4CFGAoheoQ9N%2B7KFN0f4oAdT3G%2BJZtcwitZ0Q3Jo%2FW3aYNyvrPQHt88hjOtVHKhSNEwUJ8QNfy42WiZJOix4p5I5oyePdYGW5ia09MuaY6BkV8o9mdotcWGpn6O1PsxnxfduI6gbGLWtcxjrat75LeMhGjPLYcJA9CQECK7NvBFHOBUA%2F9rR5gReBVEkdVf5N8q551DvJfR4nENxZukaVMzjhq2IIndOHzRXTyXIxVKGhWAAcGXrVKcXvUvh8Np%2FlJg2bz4xWarvbk%2FFSrXYJj5IwNrOB02N5IzoVCGSyE%2BGWjofyUhgrq%2Ftlralnyp0GpXong2Sl8xB0%2Foucm4xtC60%3D&hmac_signature=t%3D1721967214552%2Cs%3D0a3505923c0541166952726c9c32c9dfdec41d51e4b1016093a6f53f0b9a76e9
```

{% endcode %}
{% endtab %}
{% endtabs %}

### Callback/Webhook

Every merchant account is expected to have configured a callback/webhook URL for collections. For all collections that transition to the final state (COMPLETED, FAILED or CANCELLED), a JSON POST request will be made to the callback URL. Sample callback payloads (request bodies) are shared below. Be sure to check out [Handling Notifications](/utility-functions/handling-notifications-callbacks) to see how you should verify the signature(s) in the request headers and how to respond.

{% tabs %}
{% tab title="Successful Card Collection" %}

```json
{
    "event": "transaction.completed",
    "payload": {
        "id": 20760,
        "merchant_reference": "MCTREFT2WMNWZ23SBN6Y",
        "internal_reference": "DUSUPAYRMGRXNNYBWATKJ",
        "transaction_type": "COLLECTION",
        "request_currency": "NGN",
        "transaction_amount": 1000,
        "transaction_currency": "NGN",
        "transaction_charge": 100,
        "transaction_account": "462200XXXXXX5678",
        "charge_customer": false,
        "total_credit": 900,
        "provider_code": "local_ngn",
        "request_amount": 1000,
        "customer_name": "JOHN DOE",
        "institution_name": "Local Verve Card",
        "transaction_status": "COMPLETED",
        "status_message": "Transaction Completed Successfully"
    }
}
```

{% endtab %}

{% tab title="Failed Card Payment" %}

```json
{
      "event": "transaction.failed",
      "payload": {
        "id": 26609,
        "merchant_reference": "MCTREFNRFRTQA6SCWT5X",
        "internal_reference": "DUSUPAYWYUF8CR3ZRCGYU",
        "transaction_type": "COLLECTION",
        "request_currency": "NGN",
        "transaction_amount": 40000,
        "transaction_currency": "NGN",
        "transaction_charge": 0,
        "transaction_account": "462200XXXXXX4420",
        "charge_customer": false,
        "total_credit": 0,
        "provider_code": "local_ngn",
        "request_amount": 40000,
        "customer_name": "JOHN DOE",
        "institution_name": "Local Verve Card",
        "transaction_status": "FAILED",
        "status_message": "Balance insufficient for the transaction"
      }
    }
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
Either of the two (redirect/callback) can be used to confirm the final status of the transaction. We recommend that in either situation, the redirect/callback request is verified (by verifying the signatures)
{% endhint %}


# Direct Card Payment (S2S)

Another way to accept card payments is by using our server-to-server workflow. The detailed guide below will show you how to successfully charge cards on DusuPay using our APIs.

## Overview

1. Before starting, your merchant account needs to have been enabled to do funds collections via card in the specified supported options. Supported card options are shown [here](/getting-started/supported-countries-regions).
2. We equally recommend that you go through the [Getting Started](/funds-collection/getting-started) section to have a high-level understanding of the funds collection process.
3. You're required to be **PCI DSS certified (Level 1)** in order to use the direct card charge workflow.

### PCI DSS Compliance

The Payment Card Industry Data Security Standard (PCI DSS) is a set of global security standards designed to ensure that all entities that process, store or transmit cardholder data and/or sensitive authentication data maintain a secure environment. PCI DSS sets a baseline level of protection for consumers and helps reduce fraud and data breaches across the entire payment ecosystem. It is applicable to any organization that accepts or processes payment cards. Learn more [here](https://www.vikingcloud.com/faq).

{% hint style="info" %}
As a merchant, you can create a payment form on your interface and then send the card details within the request body. Card data however is not sent in plain text but rather as an encrypted string as described in more detail below.
{% endhint %}

## Step 1: Collect the card payment data

After collecting the necessary card and payment information from your customer, prepare your data object to look like the example shown below. In this payload, you may choose to pass your user’s PIN or not. The purpose of this JSON is to prepare the card data for encryption since the request doesn't expect it in plain text. The outcome of the encryption will be value sent in a special parameter named ***card\_cipher***, which is sent alongside the main collection API request

{% tabs %}
{% tab title="Sample JSON for card data" %}

```json
{
    "first_name": "John",
    "last_name": "Doe",
    "card_no": "4622000000005678",
    "exp_month": "06",
    "exp_year": "22",
    "cvv": "123",
    "billing_address": "Second Street",
    "billing_city": "San Francisco",
    "billing_zip": "94105",
    "billing_state": "CA",
    "billing_country": "US"
}
```

{% endtab %}

{% tab title="JSON Parameter Descriptions" %}

<table><thead><tr><th width="161">Parameter</th><th width="88">Type</th><th width="263">Description</th><th>Required</th></tr></thead><tbody><tr><td>first_name</td><td>String</td><td>The first name of the cardholder as registered by the issuer</td><td>YES</td></tr><tr><td>last_name</td><td>String</td><td>The last name of the cardholder as registered by the issuer</td><td>YES</td></tr><tr><td>card_no</td><td>String</td><td>The card number. Usually, the length of 16 to 19</td><td>YES</td></tr><tr><td>exp_month</td><td>String</td><td>The card expiry month. For single-digit months, prefix 0 e.g. <code>06</code></td><td>YES</td></tr><tr><td>exp_year</td><td>String</td><td>The card expiry year</td><td>YES</td></tr><tr><td>cvv</td><td>String</td><td>The card CVV, CVC, etc depending on the nature of the card</td><td>YES</td></tr><tr><td>billing_address</td><td>String</td><td>Billing address as registered by the issuer.</td><td>Only for cards issued in the USA, Canada, and Europe</td></tr><tr><td>billing_city</td><td>String</td><td>Billing city name as registered by the issuer.</td><td>Only for cards issued in the USA, Canada, and Europe</td></tr><tr><td>billing_zip</td><td>String</td><td>The zip/postal code.</td><td>Only for cards issued in the USA, Canada, and Europe</td></tr><tr><td>billing_state</td><td>String</td><td>State name/code</td><td>Only for cards issued in the USA, Canada, and Europe</td></tr><tr><td>billing_country</td><td>String</td><td>The 2-character ISO country code. The country code list can be obtained using the API description that follows</td><td>Only for cards issued in the USA, Canada, and Europe</td></tr></tbody></table>
{% endtab %}
{% endtabs %}

## Step 2: Encrypt the card JSON data

DusuPay ensures the complete security of card data in transit by using RSA encryption. The stringified payment data from Step 1 must be encrypted using the [DusuPay Public Key](/getting-started/dusupay-public-keys) before making the collection request. To encrypt the JSON string correctly, we have organized a few code samples to illustrate how it could be done as shown below.

Copy the public key for the environment you're working with from [here](/getting-started/dusupay-public-keys). This document assumes that the public key would be stored somewhere on your server under the name `dusupay.public.key.pem`

{% tabs %}
{% tab title="PHP" %}
{% code overflow="wrap" lineNumbers="true" %}

```php
<?php
function encryptJSON($jsonString, $publicKeyPath) {
    $publicKey = file_get_contents($publicKeyPath);
    $pubKeyId = openssl_pkey_get_public($publicKey);
    openssl_public_encrypt($jsonString, $encrypted, $pubKeyId);
    return base64_encode($encrypted);
}

// Example usage
$jsonData = json_encode(['first_name' => 'John', 'last_name' => 'Doe', 'card_no' => '4622000000005678', 'exp_month' => '06', 'exp_year' => '2024', 'cvv' => '123', 'billing_address' => 'Second Street', 'billing_city' => 'San Francisco', 'billing_zip' => '94105', 'billing_state' => 'CA', 'billing_country' => 'US']);

$publicKeyPath = 'path-to-file/dusupay.public.key.pem';

try {
    $encryptedData = encryptJSON($jsonData, $publicKeyPath);
    echo "Encrypted data: " . $encryptedData;
} catch (Exception $e) {
    echo "Encryption failed: " . $e->getMessage();
}
?>
```

{% endcode %}
{% endtab %}

{% tab title="NodeJS" %}
{% code overflow="wrap" lineNumbers="true" %}

```javascript
const crypto = require('crypto');
const fs = require('fs');

function encryptJSON(jsonString, publicKeyPath) {
  // Read the public key from file
  const publicKey = fs.readFileSync(publicKeyPath, 'utf8');

  // Convert JSON string to Buffer
  const buffer = Buffer.from(jsonString, 'utf8');

  // Encrypt the buffer
  const encrypted = crypto.publicEncrypt(
    {
      key: publicKey
    },
    buffer
  );

  // Return the encrypted data as base64
  return encrypted.toString('base64');
}

// Example usage
const jsonData = JSON.stringify({ first_name: "John", last_name: "Doe", card_no: "4622000000005678", exp_month: "06", exp_year: "2024", cvv: "123", billing_address: "Second Street", billing_city: "San Francisco", billing_zip: "94105", billing_state: "CA", billing_country: "US" });
const publicKeyPath = 'path-to-file/dusupay.public.key.pem';

try {
  const encryptedData = encryptJSON(jsonData, publicKeyPath);
  console.log('Encrypted data:', encryptedData);
} catch (error) {
  console.error('Encryption failed:', error.message);
}
```

{% endcode %}
{% endtab %}

{% tab title="Java" %}
{% code overflow="wrap" lineNumbers="true" %}

```java
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import javax.crypto.Cipher;

public class JSONEncryptor {
    public static String encryptJSON(String jsonString, String publicKeyPath) throws Exception {
        byte[] keyBytes = Files.readAllBytes(Paths.get(publicKeyPath));
        X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        PublicKey publicKey = kf.generatePublic(spec);

        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] encryptedBytes = cipher.doFinal(jsonString.getBytes());
        return Base64.getEncoder().encodeToString(encryptedBytes);
    }

    public static void main(String[] args) {
        String jsonData = "{\"first_name\":\"John\",\"last_name\":\"Doe\",\"card_no\":\"4622000000005678\",\"exp_month\":\"06\",\"exp_year\":\"22\",\"cvv\":\"123\",\"billing_address\":\"Second Street\",\"billing_city\":\"San Francisco\",\"billing_zip\":\"94105\",\"billing_state\":\"CA\",\"billing_country\":\"US\"}";
        String publicKeyPath = "path-to-file/dusupay.public.key.pem";

        try {
            String encryptedData = encryptJSON(jsonData, publicKeyPath);
            System.out.println("Encrypted data: " + encryptedData);
        } catch (Exception e) {
            System.err.println("Encryption failed: " + e.getMessage());
        }
    }
}
```

{% endcode %}
{% endtab %}

{% tab title="C#" %}
{% code overflow="wrap" lineNumbers="true" %}

```csharp
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

class JSONEncryptor
{
    static string EncryptJSON(string jsonString, string publicKeyPath)
    {
        byte[] publicKeyBytes = File.ReadAllBytes(publicKeyPath);
        using (RSA rsa = RSA.Create())
        {
            rsa.ImportSubjectPublicKeyInfo(publicKeyBytes, out _);
            byte[] jsonBytes = Encoding.UTF8.GetBytes(jsonString);
            byte[] encryptedBytes = rsa.Encrypt(jsonBytes, RSAEncryptionPadding.OaepSHA256);
            return Convert.ToBase64String(encryptedBytes);
        }
    }

    static void Main()
    {
        string jsonData = "{\"first_name\":\"John\",\"last_name\":\"Doe\",\"card_no\":\"4622000000005678\",\"exp_month\":\"06\",\"exp_year\":\"22\",\"cvv\":\"123\",\"billing_address\":\"Second Street\",\"billing_city\":\"San Francisco\",\"billing_zip\":\"94105\",\"billing_state\":\"CA\",\"billing_country\":\"US\"}";
        string publicKeyPath = "path-to-file/dusupay.public.key.pem";

        try
        {
            string encryptedData = EncryptJSON(jsonData, publicKeyPath);
            Console.WriteLine("Encrypted data: " + encryptedData);
        }
        catch (Exception e)
        {
            Console.WriteLine("Encryption failed: " + e.Message);
        }
    }
}
```

{% endcode %}
{% endtab %}

{% tab title="Python" %}
{% code overflow="wrap" lineNumbers="true" %}

```python
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
import base64

def encrypt_json(json_string, public_key_path):
    with open(public_key_path, 'rb') as key_file:
        public_key = RSA.import_key(key_file.read())
    cipher = PKCS1_v1_5.new(public_key)
    encrypted = cipher.encrypt(json_string.encode())
    return base64.b64encode(encrypted).decode()

# Example usage
json_data = '{"first_name":"John","last_name":"Doe","card_no":"4622000000005678","exp_month":"06","exp_year":"22","cvv":"123","billing_address":"Second Street","billing_city":"San Francisco","billing_zip":"94105","billing_state":"CA","billing_country":"US"}'
public_key_path = 'path-to-file/dusupay.public.key.pem'

try:
    encrypted_data = encrypt_json(json_data, public_key_path)
    print("Encrypted data:", encrypted_data)
except Exception as e:
    print("Encryption failed:", str(e))
```

{% endcode %}
{% endtab %}

{% tab title="Ruby" %}
{% code overflow="wrap" lineNumbers="true" %}

```ruby
require 'openssl'
require 'base64'

def encrypt_json(json_string, public_key_path)
  public_key = OpenSSL::PKey::RSA.new(File.read(public_key_path))
  encrypted = public_key.public_encrypt(json_string)
  Base64.strict_encode64(encrypted)
end

# Example usage
json_data = '{"first_name":"John","last_name":"Doe","card_no":"4622000000005678","exp_month":"06","exp_year":"22","cvv":"123","billing_address":"Second Street","billing_city":"San Francisco","billing_zip":"94105","billing_state":"CA","billing_country":"US"}'
public_key_path = 'path-to-file/dusupay.public.key.pem'

begin
  encrypted_data = encrypt_json(json_data, public_key_path)
  puts "Encrypted data: #{encrypted_data}"
rescue => e
  puts "Encryption failed: #{e.message}"
end
```

{% endcode %}
{% endtab %}
{% endtabs %}

## Step 3: Form the payment request payload

The table below describes the request parameters that are used for the collection/charge request. The encrypted string from Step 2 will be sent as part of the request as described in the table below.

<table><thead><tr><th width="200">Parameter</th><th width="93">Type</th><th width="98">Required</th><th>Description</th></tr></thead><tbody><tr><td>merchant_reference</td><td>String</td><td>true</td><td>The unique reference for this request. It must be at least 8 characters long. Alternatively, the value <strong>auto</strong> can be passed, and a unique reference will be created for you by the API</td></tr><tr><td>transaction_method</td><td>String</td><td>true</td><td>The transaction method to be used. This will be <em><strong>CARD</strong></em> for this request</td></tr><tr><td>currency</td><td>String</td><td>true</td><td>The 3-character ISO currency code for the request currency</td></tr><tr><td>amount</td><td>Number</td><td>true</td><td>The amount being requested</td></tr><tr><td>provider_code</td><td>String</td><td>true</td><td>The provider code as obtained from the payment options <a href="/pages/E8qh5wAQIPuCQxEHFHxg">list</a></td></tr><tr><td>customer_name</td><td>String</td><td>false</td><td>The name of the customer</td></tr><tr><td>customer_email</td><td>String</td><td>false</td><td>The email of the customer</td></tr><tr><td><mark style="color:orange;">card_cipher</mark></td><td><mark style="color:orange;">String</mark></td><td><mark style="color:orange;">true</mark></td><td><mark style="color:orange;">The resultant string after encryption above</mark></td></tr><tr><td>description</td><td>String</td><td>true</td><td>The description/narration for the transaction. Between 10-30 characters</td></tr><tr><td>charge_customer</td><td>Boolean</td><td>false</td><td>Whether or not the customer should bear the charge for the transaction. By default, this is <strong>false</strong> to mean that the merchant bears the charge</td></tr><tr><td>allow_final_status_change</td><td>Boolean</td><td>false</td><td>Whether or not the final transaction status can be altered as described <a href="/pages/oIwPtHN5YxEe4a0U5aCk#transaction-final-status-change">here</a>. By default, this is <strong>true</strong> to mean DusuPay will alter the final transaction status under the circumstances described.</td></tr><tr><td>redirect_url</td><td>String</td><td>true</td><td>The HTTPs redirect URL to which the API will redirect when the payment is successful/failed</td></tr></tbody></table>

After collecting the necessary card payment information from your customer, prepare your request payload as demonstrated below.

```json
{
    "merchant_reference": "auto",
    "transaction_method": "CARD",
    "currency": "NGN",
    "amount": 1000,
    "provider_code": "local_ngn",
    "customer_email": "johndoe@gmail.com",
    "customer_name": "JOHN DOE",
    "description": "Test Collection",
    "charge_customer": false,
    "allow_final_status_change": true,
    "card_cipher": "*******",
    "redirect_url": "https://your-redirect-url"
}
```

<mark style="color:green;">`POST`</mark> `https://sandboxapi.dusupay.com/collections/initialize`

The request is sent as a JSON body as demonstrated by the sample request below. Sample responses (acknowledgement and failure) are also shared.

```powershell
curl -X POST "https://sandboxapi.dusupay.com/collections/initialize" \
   -H 'Content-Type: application/json' \
   -H "x-api-version: 1" \
   -H "public-key: your-public-key" \
   -d '{
        "merchant_reference": "auto",
        "transaction_method": "CARD",
        "currency": "NGN",
        "amount": 1000,
        "provider_code": "local_ngn",
        "customer_email": "johndoe@gmail.com",
        "customer_name": "JOHN DOE",
        "description": "Test Collection",
        "charge_customer": false,
        "allow_final_status_change": true,
        "card_cipher": "*******",
        "redirect_url": "https://your-redirect-url"
    }'
```

{% tabs %}
{% tab title="202: Accepted - Request acknowledged for processing" %}

```json
{
    "code": 202,
    "status": "accepted",
    "message": "Request Accepted",
    "data": {
        "internal_reference": "DUSUPAYZAERK2SBE6WJAG",
        "merchant_reference": "MCTREFQSSBHMHU3RCTLA",
        "payment_url": "https://devpay.dusupay.com/pay/collection/DUSUPAYZAERK2SBE6WJAG"
    }
}
```

{% endtab %}

{% tab title="400: Bad Request - Request not formed as expected" %}

```json
{
    "code": 400,
    "status": "error",
    "message": "Invalid provider code (local_ugx)",
    "data": {}
}
```

{% endtab %}
{% endtabs %}

## Step 4: Redirect to Payment URL

Notice the `payment_url` parameter in the callback body. The URL should be loaded in the browser so that the customer can proceed with the transaction. When the page loads, the customer will be guided through the payment process and on success/failure, the customer will be redirected to the `redirect_url` sent by the merchant in the request. Additionally, a callback/webhook will be sent to the configured collection callback URL.

## Step 5: Handle the redirect and/or callback

### Redirect

On success/failure of the transaction, the customer will be redirected to the URL that was passed in the `redirect_url` request parameter. The sample requests below demonstrated the success and failure scenarios. You can copy the URL and paste it on [this](https://semalt.tools/en/url-parser) online resource in order to view the query parameters therein.

{% tabs %}
{% tab title="Redirect on success" %}
{% code overflow="wrap" %}

```
https://webhook.site/cc0393dc-fe49-401b-ba56-da9e099a3648?id=33911&event=transaction.completed&merchant_reference=MCTREFZQTUDRC38TLYUV&internal_reference=DUSUPAYHYJQ3ZUJMPGFG5&transaction_type=COLLECTION&transaction_status=COMPLETED&status_message=Transaction%20Completed%20Successfully&rsa_signature=Y23aNOaOs6NfOnrhpZrWGAOI4bMR5kNobFnrOBlqF%2FY7f03aBpHZx2rcmU3q2P9zS2w61xs3FsT%2FipRI9F21dNJ0UxDQci7yQ%2B7CZbk6kJQbfX9Ht7OIhH2%2F7cv%2FNUXDmXzZQYST3Qwe9QIhea1SIImWRzR8HKAeEeoVRPLDqxohLX4A0S91hjrZVO%2BjohZJLazIeXFrOaJyIu3xPQ7bzJi%2BfVhw6ry3R7NknIj5y752WpW0CqvxXKr3wPUIXy99tnN%2FCWLG65AmVL9GkBZj0j12%2B8ztBpknPBuGKWxndJB7zkkPTCmA%2BA1m21f3RENYTNH7nHet5zp5Iyq%2BkmtVVUmEcfvfAwAxs0sP0fYTFU8iSX6wvB6UEvk3AWgrprlb1XGd3sRgFgCb3HjzA7%2B8RBi%2Fw59qIXqK4LlbOtZw2tPc7MjLeXq%2BmqzrO8SRxyuvu6lX0HPhaHSXAGq%2BvljA1Kk4ZNLdSWxYCC2IgI7%2BPomrfWW1I6pNIF%2BudPTIYTPN6EEdMUwFBltyAoh%2ByHl6K%2F%2Fcz4FfE6OgZwJkbQ8661sHqiB2ND%2FhSQh%2B0Qre1tQTLx7BGQTSGdD34CLn%2FQICIlqNCvKbMF4ZO208xYaBct01T6Xa23zykkrmlTviEMJgSIo3zj2KpN%2BTF1X5jZOXLszBL20o1SeQlY9%2BhRzshzY%3D&hmac_signature=t%3D1721639784089%2Cs%3D1ec4e0f7749397faea80958bca9ab2c6d8e7448c3ee02f958881a392ce299128
```

{% endcode %}
{% endtab %}

{% tab title="Redirect on failure/cancellation" %}
{% code overflow="wrap" %}

```
https://mysite.com?id=34639&event=transaction.failed&merchant_reference=MCTREF8DQRHYTNCQTACH&internal_reference=DUSUPAY8A54ZUJFGSMENE&transaction_type=COLLECTION&transaction_status=FAILED&status_message=Balance%20Insufficient%20for%20the%20transaction&rsa_signature=yvODC%2BViCiZmlEEEbQX1B%2FfQ0kNzrYqRN0c3Y8MjzZ%2FiMNVzf%2BUS36Ec8P1a0rOKgyc0l7KVbzEJwMZu0nasAHFuCeSOebs5NOHvSNJkAbQE2nZXgxvo1uQtJOMnbYZZKIvswemRTRGEyAKl7bRGc2AXzYRzJBVOYBVDOM0o36FUpfNNFrucVjvrmJJD8YtRtIXk3RjBzXkhV%2B3KAjWx0KU8bh424OyZUwsRTaN0MeuQMFY%2FkeuFvNKPzxKtN%2BaJqcx4aDZes6thevgfCljgne0ibC9Zy1eDCYP8A1T%2BNTS%2FHUZBuy1Y%2BzAmQ%2BMjnoiI%2FVk2I%2BuoRkHU01%2BMV%2Bo4CFGAoheoQ9N%2B7KFN0f4oAdT3G%2BJZtcwitZ0Q3Jo%2FW3aYNyvrPQHt88hjOtVHKhSNEwUJ8QNfy42WiZJOix4p5I5oyePdYGW5ia09MuaY6BkV8o9mdotcWGpn6O1PsxnxfduI6gbGLWtcxjrat75LeMhGjPLYcJA9CQECK7NvBFHOBUA%2F9rR5gReBVEkdVf5N8q551DvJfR4nENxZukaVMzjhq2IIndOHzRXTyXIxVKGhWAAcGXrVKcXvUvh8Np%2FlJg2bz4xWarvbk%2FFSrXYJj5IwNrOB02N5IzoVCGSyE%2BGWjofyUhgrq%2Ftlralnyp0GpXong2Sl8xB0%2Foucm4xtC60%3D&hmac_signature=t%3D1721967214552%2Cs%3D0a3505923c0541166952726c9c32c9dfdec41d51e4b1016093a6f53f0b9a76e9
```

{% endcode %}
{% endtab %}
{% endtabs %}

### Callback/Webhook

Every merchant account is expected to have configured a callback/webhook URL for collections. For all collections that transition to the final state (COMPLETED, FAILED or CANCELLED), a JSON POST request will be made to the callback URL. Sample callback payloads (request bodies) are shared below. Be sure to check out [Handling Notifications](/utility-functions/handling-notifications-callbacks) to see how you should verify the signature(s) in the request headers and how to respond.

{% tabs %}
{% tab title="Successful Card Collection" %}

```json
{
    "event": "transaction.completed",
    "payload": {
        "id": 20760,
        "merchant_reference": "MCTREFT2WMNWZ23SBN6Y",
        "internal_reference": "DUSUPAYRMGRXNNYBWATKJ",
        "transaction_type": "COLLECTION",
        "request_currency": "NGN",
        "transaction_amount": 1000,
        "transaction_currency": "NGN",
        "transaction_charge": 100,
        "transaction_account": "462200XXXXXX5678",
        "charge_customer": false,
        "total_credit": 900,
        "provider_code": "local_ngn",
        "request_amount": 1000,
        "customer_name": "JOHN DOE",
        "institution_name": "Local Verve Card",
        "transaction_status": "COMPLETED",
        "status_message": "Transaction Completed Successfully"
    }
}
```

{% endtab %}

{% tab title="Failed Card Payment" %}

```json
{
      "event": "transaction.failed",
      "payload": {
        "id": 26609,
        "merchant_reference": "MCTREFNRFRTQA6SCWT5X",
        "internal_reference": "DUSUPAYWYUF8CR3ZRCGYU",
        "transaction_type": "COLLECTION",
        "request_currency": "NGN",
        "transaction_amount": 40000,
        "transaction_currency": "NGN",
        "transaction_charge": 0,
        "transaction_account": "462200XXXXXX4420",
        "charge_customer": false,
        "total_credit": 0,
        "provider_code": "local_ngn",
        "request_amount": 40000,
        "customer_name": "JOHN DOE",
        "institution_name": "Local Verve Card",
        "transaction_status": "FAILED",
        "status_message": "Balance insufficient for the transaction"
      }
    }
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
Either of the two (redirect/callback) can be used to confirm the final status of the transaction. We recommend that in either situation, the redirect/callback request is verified (by verifying the signatures)
{% endhint %}


# Getting Started

Payouts, also sometimes called Disbursements, are outbound fund transfers from your merchant account to supported mobile money channels or bank accounts.

The DusuPay API supports funds payout/disbursement via a number of methods e.g. Mobile Money, Bank Transfer and many other methods will be supported along the way. The API enables you to send payments via a simple, secure process that requires only the bank/phone information of the recipients.

The merchant is expected to have configured the **payout notification URL** as well as the **payout verification URL** on the merchant account profile. The notification URL will receive webhook/callback requests when the payout is successful/failed. The verification URL on the other hand allows the DusuPay platform to do a "handshake" with the merchant's platform to verify the authenticity of the request as described in step 2 below.

The payouts/disbursements section will describe the process flows for every available payment method. However, every payout will follow the steps described below, in the order of occurrence.

{% hint style="info" %}
Before payouts can be initiated, the merchant needs to avail the funds for payout. This can be achieved by requesting transfer of funds collected or deposited prior as described in the [Availing Payout Funds](/appendix/availing-payout-funds) section.
{% endhint %}

The sequence diagram below graphically describes the payout workflow. Detailed documentation thereafter.

```mermaid
sequenceDiagram
    actor Customer
    participant Merchant Portal
    participant DusuPay
    participant Processor

    Customer->>Merchant Portal: Initiates payout request
    Merchant Portal->>DusuPay: POST payout/send-funds
    DusuPay-->>Merchant Portal: 202 Acknowledgement
    DusuPay->>Merchant Portal: GET https://your-payout-verification-url?reference=your-ref (request verification)
    Merchant Portal-->>DusuPay: 200 Response
    DusuPay->>Processor: Request funds disbursement
    Processor-->>Customer: Execute funds disbursement
    Processor->>DusuPay: Notification of completion
    DusuPay->>Merchant Portal: transaction.completed callback request
```

### Step 1: The payout API request

The merchant will form the payout request payload (based on the selected payment method) and send it to the API for processing. If there are any errors with the parameters, these would be communicated immediately in the response otherwise the API would respond with an acknowledgement. Take the following the mobile money request and a sample acknowledgement.

```json
{
    "merchant_reference": "MCTREFT2WMNWZ23SBN6Y",
    "transaction_method": "MOBILE_MONEY",
    "currency": "UGX",
    "amount": 1000,
    "provider_code": "mtn_ug",
    "msisdn": "256777000001",
    "customer_name": "JOHN DOE",
    "description": "Test Payout"
}

/*sample acknowledgement would be something like the following*/
{
    "code": 202,
    "status": "accepted",
    "message": "Request Accepted",
    "data": {
        "internal_reference": "DUSUPAYRMGRXNNYBWATKJ",
        "merchant_reference": "MCTREFT2WMNWZ23SBN6Y"
    }
}
```

However, should this process fail in any way even after the acknowledgement e.g. if the merchant is making too many requests to the same phone number, the following failure callback will be sent to the payout callback URL.

```json
{
  "event": "request.failed",
  "payload": {
    "id": 20760,
    "merchant_reference": "MCTREFT2WMNWZ23SBN6Y",
    "internal_reference": "DUSUPAYRMGRXNNYBWATKJ",
    "transaction_type": "PAYOUT",
    "request_currency": "UGX",
    "request_amount": 1000,
    "transaction_status": "FAILED",
    "status_message": "Failed to initialize the transaction. System exception!"
  }
}
```

### Step 2: Payout Request Verification

For every payout request sent to the DusuPay API, a request will be made to the merchant platform via the configured payout verification URL. This is one of the platform's security measures and the purpose of the request is to ensure the merchant platform (database) is "aware" of the transaction. The merchant simply needs to ensure that the configured URL is operational. When a request is sent to it, query your records to confirm that the reference is among your records and in the status/state to be processed. If this is true, respond with a simple HTTP code **200** strictly, which to the API will be translated as permission to process the transaction. Any other response will be regarded "denial" and the API will not move ahead with the processing.

A sample verification request could be like the following. The GET request will have a single query parameter `reference` that holds the value of the merchant reference sent in the payout request prior. The example below assumes that the `merchant_reference` in the request was ***MCTREFDWWJTDYTEAHEQP.***

```powershell
curl -X GET https://your-payout-verification-url?reference=MCTREFDWWJTDYTEAHEQP
```

{% hint style="info" %}
If the response to that request is HTTP code 200, the API will proceed to queue the transaction for processing and after processing, a notification will be sent to the payout notification URL to communicate the final status of the transaction as described in step 3
{% endhint %}

### Step 3: Final status notification

This is the notification that communicates the very final status of the transaction, and this would mark the close of the transaction process. Sample completed and failed transaction callbacks/notifications shared below.

```json
/*sample completed transaction callback*/
{
    "event": "transaction.completed",
    "payload": {
        "id": 20760,
        "merchant_reference": "MCTREFT2WMNWZ23SBN6Y",
        "internal_reference": "DUSUPAYRMGRXNNYBWATKJ",
        "transaction_type": "PAYOUT",
        "request_currency": "UGX",
        "transaction_amount": 10000,
        "transaction_currency": "UGX",
        "transaction_charge": 1000,
        "transaction_account": "256787008803",
        "charge_customer": false,
        "total_debit": 11000,
        "provider_code": "mtn_ug",
        "request_amount": 10000,
        "customer_name": "JOHN DOE",
        "transaction_status": "COMPLETED",
        "status_message": "Transaction Completed Successfully"
    }
}

/*sample failed transaction callback*/
{
    "event": "transaction.failed",
    "payload": {
        "id": 26609,
        "merchant_reference": "MCTREFNRFRTQA6SCWT5X",
        "internal_reference": "DUSUPAYWYUF8CR3ZRCGYU",
        "transaction_type": "PAYOUT",
        "request_currency": "UGX",
        "transaction_amount": 10000,
        "transaction_currency": "UGX",
        "transaction_charge": 0,
        "transaction_account": "256777000002",
        "charge_customer": false,
        "total_debit": 0,
        "provider_code": "mtn_ug",
        "request_amount": 10000,
        "customer_name": "JOHN DOE",
        "transaction_status": "FAILED",
        "status_message": "Balance Insufficient for the transaction"
    }
}
```

{% hint style="info" %}
**Cross-currency Transfers**

If the request currency is different e.g. USD from the payment provider currency (transaction currency) e.g. GHS, the API will convert the USD to GHS and your GHS balance will be debited with the converted amount. More details [here](/appendix/cross-currency-transactions).
{% endhint %}


# Mobile Money Payouts

The DusuPay API enables you to transfer funds directly from your available balance to a mobile money account. The documentation below explains further.

{% hint style="info" %}
We recommend checking out the [Getting Started](/payouts-disbursements/getting-started) section to understand the basics of payouts first and the general workflow. This guide assumes that you have read that
{% endhint %}

## Overview

The currently supported mobile money channels are listed [here](/getting-started/supported-countries-regions) (to be updated from time to time). Test mobile money phone numbers are also described in [this](/getting-started/sandbox-test-accounts) section. It's very important that you track the available balance on your PAYOUT wallet before initiating these transactions.

## Step 1: Obtain the required data for the payment request

The table below describes the request parameters that are used for the payout/disbursement request. Most/all will be collected from the paying customer.

<table><thead><tr><th width="197">Parameter</th><th width="93">Type</th><th width="98">Required</th><th>Description</th></tr></thead><tbody><tr><td>merchant_reference</td><td>String</td><td>true</td><td>The unique reference for this request. It must be at least 8 characters long.</td></tr><tr><td>transaction_method</td><td>String</td><td>true</td><td>The transaction method to be used. This will be <em><strong>MOBILE_MONEY</strong></em> for this request</td></tr><tr><td>currency</td><td>String</td><td>true</td><td>The 3-character ISO currency code for the request currency</td></tr><tr><td>amount</td><td>Number</td><td>true</td><td>The amount to be transferred</td></tr><tr><td>provider_code</td><td>String</td><td>true</td><td>The provider code as obtained from the payment options <a href="/pages/E8qh5wAQIPuCQxEHFHxg#get-payment-options-list">list</a></td></tr><tr><td>account_number</td><td>String</td><td>true</td><td>The phone number of the recipient. This should be sent in international format e.g. 256777000001 for Ugandan numbers</td></tr><tr><td>customer_name</td><td>String</td><td>true</td><td>The name of the customer</td></tr><tr><td>description</td><td>String</td><td>true</td><td>The description/narration for the transaction. Between 10-30 characters</td></tr></tbody></table>

After collecting the necessary mobile money payment information from your customer, prepare your request payload as demonstrated below.

```json
{
	"merchant_reference": "MCTREFT2WMNWZ23SBN6Y",
	"transaction_method": "MOBILE_MONEY",
	"currency": "UGX",
	"amount": 4000,
	"provider_code": "mtn_ug",
	"account_number": "256777000001",
	"customer_name": "JOHN DOE",
	"description": "Test Payout"
}
```

<mark style="color:green;">`POST`</mark> `https://sandboxapi.dusupay.com/payout/send-funds`

The request is sent as a JSON body as demonstrated by the sample request below. Sample responses (acknowledgement and failure) are also shared.

```powershell
curl -X POST "https://sandboxapi.dusupay.com/payout/send-funds" \
   -H 'Content-Type: application/json' \
   -H "x-api-version: 1" \
   -H "public-key: your-public-key" \
   -H "secret-key: your-secret-key" \
   -d '{
        "merchant_reference": "MCTREFT2WMNWZ23SBN6Y",
        "transaction_method": "MOBILE_MONEY",
        "currency": "UGX",
        "amount": 10000,
        "provider_code": "mtn_ug",
        "account_number": "256777000001",
        "customer_name": "JOHN DOE",
        "description": "Test Payout"
    }'
```

{% tabs %}
{% tab title="202: Accepted - Request acknowledged for processing" %}

```json
{
    "code": 202,
    "status": "accepted",
    "message": "Request Accepted",
    "data": {
        "internal_reference": "DUSUPAYRMGRXNNYBWATKJ",
        "merchant_reference": "MCTREFT2WMNWZ23SBN6Y"
    }
}
```

{% endtab %}

{% tab title="400: Bad Request - Request is not formed as expected" %}

```json
{
    "code": 400,
    "status": "error",
    "message": "256752000001 is not a valid MTN Mobile Money Uganda (mtn_ug) phone number",
    "data": {}
}
```

{% endtab %}
{% endtabs %}

## Step 2: Handshake - Verify transaction

{% hint style="info" %}
The API will attempt to verify the transaction from your platform as described in [this](/payouts-disbursements/getting-started#step-2-payout-request-verification) section. Ensure that the correct URL is configured on your merchant account and that you're handling the verification request appropriately.
{% endhint %}

## Step 3: Handle the final status webhook

Every merchant account is expected to have configured a callback/webhook URL for payouts. For all payouts that transition to the final state (COMPLETED or FAILED), a JSON POST request will be made to the callback URL. Sample callback payloads (request bodies) are shared below. Be sure to check out [Handling Notifications](/utility-functions/handling-notifications-callbacks) to see how you should verify the signature(s) in the request headers and how to respond.

{% tabs %}
{% tab title="Successful Mobile Money Payout" %}

```json
{
    "event": "transaction.completed",
    "payload": {
        "id": 20760,
        "merchant_reference": "MCTREFT2WMNWZ23SBN6Y",
        "internal_reference": "DUSUPAYRMGRXNNYBWATKJ",
        "transaction_type": "PAYOUT",
        "request_currency": "UGX",
        "transaction_amount": 10000,
        "transaction_currency": "UGX",
        "transaction_charge": 1000,
        "transaction_account": "256777000001",
        "charge_customer": false,
        "total_debit": 11000,
        "provider_code": "mtn_ug",
        "request_amount": 10000,
        "customer_name": "JOHN DOE",
        "transaction_status": "COMPLETED",
        "status_message": "Transaction Completed Successfully"
    }
}
```

{% endtab %}

{% tab title="Failed Mobile Money Payout" %}

```json
{
    "event": "transaction.failed",
    "payload": {
        "id": 20760,
        "merchant_reference": "MCTREFT2WMNWZ23SBN6Y",
        "internal_reference": "DUSUPAYRMGRXNNYBWATKJ",
        "transaction_type": "PAYOUT",
        "request_currency": "UGX",
        "transaction_amount": 10000,
        "transaction_currency": "UGX",
        "transaction_charge": 0,
        "transaction_account": "256777000002",
        "charge_customer": false,
        "total_debit": 0,
        "provider_code": "mtn_ug",
        "request_amount": 10000,
        "customer_name": "JOHN DOE",
        "transaction_status": "FAILED",
        "status_message": "Balance Insufficient for the transaction"
    }
}
```

{% endtab %}
{% endtabs %}


# Bank Account Transfers

Send money quickly to different bank accounts across multiple supported countries with DusuPay. The document below explains further.

{% hint style="info" %}
We recommend checking out the [Getting Started](/payouts-disbursements/getting-started) section to understand the basics of payouts first and the general workflow. This guide assumes that you have read that
{% endhint %}

## Overview

The currently supported payout bank channels are listed [here](/getting-started/supported-countries-regions) (to be updated from time to time). Test bank accounts are also described in [this](/getting-started/sandbox-test-accounts#bank-supported-payout-banks) section. It's very important that you track the available balance on your PAYOUT wallet before initiating these transactions.

## Step 1: Obtain the required data for the payment request

The table below describes the request parameters that are used for the payout/disbursement request. Most/all will be collected from the paying customer.

<table><thead><tr><th width="243">Parameter</th><th width="93">Type</th><th>Description</th></tr></thead><tbody><tr><td>merchant_reference</td><td>String</td><td><strong>Required -</strong> The unique reference for this request. It must be at least 8 characters long.</td></tr><tr><td>transaction_method</td><td>String</td><td><strong>Required -</strong> The transaction method to be used. This will be <em><strong>BANK</strong></em> for this request</td></tr><tr><td>currency</td><td>String</td><td><strong>Required -</strong> The 3-character ISO currency code for the request currency</td></tr><tr><td>amount</td><td>Number</td><td><strong>Required -</strong> The amount to be transferred</td></tr><tr><td>provider_code</td><td>String</td><td><strong>Required -</strong> The provider code as obtained from the payment options <a href="/pages/E8qh5wAQIPuCQxEHFHxg#get-payment-options-list">list</a></td></tr><tr><td>account_number</td><td>String</td><td><strong>Required -</strong> The bank account number of the recipient.</td></tr><tr><td>customer_name</td><td>String</td><td><strong>Required -</strong> The name of the customer</td></tr><tr><td>description</td><td>String</td><td><strong>Required -</strong> The description/narration for the transaction. Between 10-30 characters</td></tr><tr><td>extra_params.bank_code</td><td>String</td><td><strong>Required -</strong> The bank code of the recipient bank as obtained from the payout bank <a href="/pages/PNEenOXxCdc7b4E7AFjA">list</a></td></tr></tbody></table>

After collecting the necessary mobile money payment information from your customer, prepare your request payload as demonstrated below.

```json
{
    "merchant_reference": "MCTREFT2WMNWZ23SBN6Y",
    "transaction_method": "BANK",
    "currency": "NGN",
    "amount": 4000,
    "provider_code": "bank_ng",
    "account_number": "2121562123",
    "customer_name": "JOHN DOE",
    "description": "Test Payout",
    "extra_params": {
        "bank_code": "access_bank_ng"
    }
}
```

<mark style="color:green;">`POST`</mark> `https://sandboxapi.dusupay.com/payout/send-funds`

The request is sent as a JSON body as demonstrated by the sample request below. Sample responses (acknowledgement and failure) are also shared.

```powershell
curl -X POST "https://sandboxapi.dusupay.com/payout/send-funds" \
   -H 'Content-Type: application/json' \
   -H "x-api-version: 1" \
   -H "public-key: your-public-key" \
   -H "secret-key: your-secret-key" \
   -d '{
        "merchant_reference": "MCTREFT2WMNWZ23SBN6Y",
        "transaction_method": "BANK",
        "currency": "NGN",
        "amount": 4000,
        "provider_code": "bank_ng",
        "account_number": "2121562123",
        "customer_name": "JOHN DOE",
        "description": "Test Payout",
        "extra_params": {
           "bank_code": "access_bank_ng"
         }
    }'
```

{% tabs %}
{% tab title="202: Accepted - Request acknowledged for processing" %}

```json
{
    "code": 202,
    "status": "accepted",
    "message": "Request Accepted",
    "data": {
        "internal_reference": "DUSUPAYRMGRXNNYBWATKJ",
        "merchant_reference": "MCTREFT2WMNWZ23SBN6Y"
    }
}
```

{% endtab %}

{% tab title="400: Bad Request - Request is not formed as expected" %}

```json
{
    "code": 400,
    "status": "error",
    "message": "256752000001 is not a valid MTN Mobile Money Uganda (mtn_ug) phone number",
    "data": {}
}
```

{% endtab %}
{% endtabs %}

## Step 2: Handshake - Verify transaction

{% hint style="info" %}
The API will attempt to verify the transaction from your platform as described in [this](/payouts-disbursements/getting-started#step-2-payout-request-verification) section. Ensure that the correct URL is configured on your merchant account and that you're handling the verification request appropriately.
{% endhint %}

## Step 3: Handle the final status webhook

Every merchant account is expected to have configured a callback/webhook URL for payouts. For all payouts that transition to the final state (COMPLETED or FAILED), a JSON POST request will be made to the callback URL. Sample callback payloads (request bodies) are shared below. Be sure to check out [Handling Notifications](/utility-functions/handling-notifications-callbacks) to see how you should verify the signature(s) in the request headers and how to respond.

{% tabs %}
{% tab title="Successful Bank Payout" %}

```json
{
    "event": "transaction.completed",
    "payload": {
        "id": 20760,
        "merchant_reference": "MCTREFT2WMNWZ23SBN6Y",
        "internal_reference": "DUSUPAYRMGRXNNYBWATKJ",
        "transaction_type": "PAYOUT",
        "request_currency": "NGN",
        "transaction_amount": 4000,
        "transaction_currency": "NGN",
        "transaction_charge": 100,
        "transaction_account": "2121562123",
        "charge_customer": false,
        "total_debit": 4100,
        "provider_code": "bank_ng",
        "request_amount": 4000,
        "customer_name": "JOHN DOE",
        "transaction_status": "COMPLETED",
        "status_message": "Transaction Completed Successfully"
    }
}
```

{% endtab %}

{% tab title="Failed Bank Payout" %}

```json
{
    "event": "transaction.failed",
    "payload": {
        "id": 20760,
        "merchant_reference": "MCTREFT2WMNWZ23SBN6Y",
        "internal_reference": "DUSUPAYRMGRXNNYBWATKJ",
        "transaction_type": "PAYOUT",
        "request_currency": "NGN",
        "transaction_amount": 4000,
        "transaction_currency": "NGN",
        "transaction_charge": 0,
        "transaction_account": "2121562123",
        "charge_customer": false,
        "total_debit": 0,
        "provider_code": "bank_ng",
        "request_amount": 4000,
        "customer_name": "JOHN DOE",
        "transaction_status": "FAILED",
        "status_message": "Balance Insufficient for the transaction"
    }
}
```

{% endtab %}
{% endtabs %}


# HMAC Signature Verification

The section describes how the HMAC signature sent in the callback header OR in the redirect data can be verified

## Obtain the Signing Key

The signing key is an alpha-numeric string generated by our platform during your merchant account creation and it is stored against your account record. This value can be found under your account details in the merchant dashboard. DusuPay uses this value to create the HMAC signature and the same will be used when verifying the signature. It is recommended that it is copied and stored safely together with the security keys.

Below is the sample callback data to be used for the demonstration.

```json
{
    "event": "transaction.completed",
    "payload": {
        "id": 20760,
        "merchant_reference": "MCTREFT2WMNWZ23SBN6Y",
        "internal_reference": "DUSUPAYRMGRXNNYBWATKJ",
        "transaction_type": "COLLECTION",
        "request_currency": "UGX",
        "transaction_amount": 2000000,
        "transaction_currency": "UGX",
        "transaction_charge": 60000,
        "transaction_account": "256787008803",
        "charge_customer": false,
        "total_credit": 1940000,
        "provider_code": "mtn_ug",
        "request_amount": 2000000,
        "customer_name": "JOHN DOE",
        "transaction_status": "COMPLETED",
        "status_message": "Transaction Completed Successfully"
    }
}
```

## Next Steps

1. Obtain the value of the `hmac-signature` header (if callback) OR the value of the `hmac_signature` query parameter (if redirect). The value sent in the signature header takes the format `t=timestamp,s=hmac_hash`
2. Form the string payload to be used in signature verification. This is obtained by concatenating values of the callback/redirect data in the format; `event:merchant_reference:internal_reference:transaction_type:transaction_status` and these values are obtained from the callback/redirect data. The string payload in this case would therefore be `transaction.completed:MCTREFT2WMNWZ23SBN6Y:DUSUPAYRMGRXNNYBWATKJ:COLLECTION:COMPLETED`
3. Create the hmac hash of the string payload.
4. Compare the resulting hash to the value in the hmac-signature header. Equality means the signature is valid.

{% tabs %}
{% tab title="PHP" %}

```php
<?php

public function isValidSignature() {
    $strPayload = "transaction.completed:MCTREFT2WMNWZ23SBN6Y:DUSUPAYRMGRXNNYBWATKJ:COLLECTION:COMPLETED";
    $signingKey = "your signing key string";
    $hmacSignature = "value of hmac-signature header";

    try {
      $timestamp = null;
      $hmacHash = null;

      // Split the hmacSignature into key-value pairs
      foreach (explode(",", $hmacSignature) as $sig_part) {
        [$key, $value] = explode("=", $sig_part);
        switch ($key) {
          case "t":
            $timestamp = $value;
            break;
          case "s":
            $hmacHash = $value;
            break;
        }
      }

      // Optional timestamp check based on your logic

      // Calculate the HMAC signature
      $signature = hash_hmac("sha256", $strPayload, $signingKey, false);

      // Compare the calculated and provided signatures
      return $signature === $hmacHash;
    } catch (Exception $e) {
      return false;
    }
}

?>
```

{% endtab %}

{% tab title="NodeJS" %}

```javascript
const crypto = require('crypto');

function isValidSignature() {
    const strPayload = "transaction.completed:MCTREFT2WMNWZ23SBN6Y:DUSUPAYRMGRXNNYBWATKJ:COLLECTION:COMPLETED";
    const signingKey = "your signing key string";
    const hmacSignature = "value of hmac-signature header";

    try {
        let timestamp, hmacHash;
        for (const sig_part of hmacSignature.split(",")) {
            const [key, value] = sig_part.split("=");
            switch (key) {
                case "t":
                    timestamp = value;
                    break;
                case "s":
                    hmacHash = value;
                    break;
            }
        }

        /*you can optionally check the timestamp. your current timestamp and the timestamp in the header should not be more than 30 seconds apart*/

        const signature = crypto.createHmac("sha256", signingKey).update(strPayload).digest("hex");
        /*true or false*/
        return signature === hmacHash;
    } catch (e) {
        return false;
    }
}
```

{% endtab %}

{% tab title="Java" %}

```java
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;

public class SignatureValidator {

  private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";

  public static boolean isValidSignature(String signingKey, String strPayload, String hmacSignature)
      throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
    // Extract timestamp and signature hash from hmacSignature
    Map<String, String> signatureParts = parseSignatureParts(hmacSignature);
    String timestamp = signatureParts.get("t");
    String hmacHash = signatureParts.get("s");

    // Optional timestamp check based on your logic

    // Calculate the HMAC signature
    byte[] calculatedSignature = calculateHmac(signingKey, strPayload);
    String encodedSignature = Base64.getEncoder().encodeToString(calculatedSignature);

    // Compare the calculated and provided signatures
    return encodedSignature.equals(hmacHash);
  }

  private static Map<String, String> parseSignatureParts(String hmacSignature) {
    Map<String, String> parts = new HashMap<>();
    for (String sigPart : hmacSignature.split(",")) {
      String[] keyValue = sigPart.split("=");
      parts.put(keyValue[0], keyValue[1]);
    }
    return parts;
  }

  private static byte[] calculateHmac(String signingKey, String strPayload)
      throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
    byte[] decodedKey = Base64.getDecoder().decode(signingKey);
    Mac mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
    mac.init(new SecretKeySpec(decodedKey, HMAC_SHA256_ALGORITHM));
    return mac.doFinal(strPayload.getBytes("UTF-8"));
  }

  public static void main(String[] args) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
    // Replace these values with your actual data
    String signingKey = "your_signing_key";
    String strPayload = "transaction.completed:MCTREFT2WMNWZ23SBN6Y:DUSUPAYRMGRXNNYBWATKJ:COLLECTION:COMPLETED";
    String hmacSignature = "value of hmac-signature header";

    // Call the isValidSignature method and print the result
    boolean isValid = isValidSignature(signingKey, strPayload, hmacSignature);
    System.out.println("Signature is valid: " + isValid);
  }
}
```

{% endtab %}

{% tab title="C#" %}

```csharp
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;

public class SignatureValidator
{
    private const string HmacSha256Algorithm = "HmacSha256";

    public static bool IsValidSignature(string signingKey, string strPayload, string hmacSignature)
    {
        try
        {
            // Extract timestamp and signature hash (optional timestamp check not included)
            var signatureParts = SplitAndParseSignatureParts(hmacSignature);
            var hmacHash = signatureParts["s"];

            // Calculate the HMAC signature
            var calculatedSignature = CalculateHmac(signingKey, strPayload);
            var encodedSignature = Convert.ToBase64String(calculatedSignature);

            // Compare the calculated and provided signatures
            return encodedSignature.Equals(hmacHash);
        }
        catch (Exception ex)
        {
            return false;
        }
    }

    private static Dictionary<string, string> SplitAndParseSignatureParts(string hmacSignature)
    {
        var parts = new Dictionary<string, string>();
        foreach (var sigPart in hmacSignature.Split(','))
        {
            var keyValue = sigPart.Split('=');
            parts.Add(keyValue[0], keyValue[1]);
        }
        return parts;
    }

    private static byte[] CalculateHmac(string signingKey, string strPayload)
    {
        using (var hmac = HMACSHA256.Create())
        {
            hmac.Key = Convert.FromBase64String(signingKey);
            return hmac.ComputeHash(Encoding.UTF8.GetBytes(strPayload));
        }
    }

    public static void Main(string[] args)
    {
        // Replace these values with your actual data
        string signingKey = "your_signing_key";
        string strPayload = "transaction.completed:MCTREFT2WMNWZ23SBN6Y:DUSUPAYRMGRXNNYBWATKJ:COLLECTION:COMPLETED";
        string hmacSignature = "value of hmac-signature header";

        // Call the isValidSignature method and print the result
        bool isValid = IsValidSignature(signingKey, strPayload, hmacSignature);
        Console.WriteLine("Signature is valid: {0}", isValid);
    }
}

```

{% endtab %}

{% tab title="Python" %}

```python
import base64
import hmac
import hashlib

def is_valid_signature(signing_key, str_payload, hmac_signature):
    try:
        # Extract timestamp and signature hash (optional timestamp check not included)
        signature_parts = {key: value for key, value in map(lambda x: x.split("="), hmac_signature.split(","))}
        hmac_hash = signature_parts["s"]

        # Calculate the HMAC signature
        calculated_signature = hmac.new(signing_key.encode(), str_payload.encode(), hashlib.sha256).hexdigest()

        # Compare the calculated and provided signatures
        return calculated_signature == hmac_hash
    except Exception:
        return False

# Replace these values with your actual data
signing_key = "your_signing_key"
str_payload = "transaction.completed:MCTREFT2WMNWZ23SBN6Y:DUSUPAYRMGRXNNYBWATKJ:COLLECTION:COMPLETED"
hmac_signature = "value of hmac-signature header"

# Call the is_valid_signature function and print the result
is_valid = is_valid_signature(signing_key, str_payload, hmac_signature)
print("Signature is valid:", is_valid)
```

{% endtab %}

{% tab title="Ruby" %}

```ruby
require 'base64'
require 'digest/hmac'

def is_valid_signature?(signing_key, str_payload, hmac_signature)
  begin
    # Extract timestamp and signature hash (optional timestamp check not included)
    signature_parts = hmac_signature.split(",").map { |part| part.split("=") }.to_h
    hmac_hash = signature_parts["s"]

    # Calculate the HMAC signature
    calculated_signature = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, signing_key, str_payload))

    # Compare the calculated and provided signatures
    return calculated_signature == hmac_hash
  rescue StandardError
    return false
```

{% endtab %}
{% endtabs %}

Below is a sample signature generated using the signing key: **SGNKYUEMYFDEHRWGPEUG**

```
t=1720633393293,s=d7e5264c92bd58279541309cad80a19889a5e9a10a944f418e52383c6ea5fcfe
```


# RSA Signature Verification

The section describes how the RSA signature sent in the callback header can be verified. The signature is generated using RSA Signing. For verification to succeed, the public key is required.

## Obtain the Public Key - Based on environment

Copy the public key for the environment you're working with from [here](/getting-started/dusupay-public-keys). This document assumes that the public key would be stored somewhere on your server under the name `dusupay.public.key.pem`

## Next Steps

Below is the sample callback data for this demonstration

```json
{
    "event": "transaction.completed",
    "payload": {
        "id": 20760,
        "merchant_reference": "MCTREFT2WMNWZ23SBN6Y",
        "internal_reference": "DUSUPAYRMGRXNNYBWATKJ",
        "transaction_type": "COLLECTION",
        "request_currency": "UGX",
        "transaction_amount": 2000000,
        "transaction_currency": "UGX",
        "transaction_charge": 60000,
        "transaction_account": "256787008803",
        "charge_customer": false,
        "total_credit": 1940000,
        "provider_code": "mtn_ug",
        "request_amount": 2000000,
        "customer_name": "JOHN DOE",
        "transaction_status": "COMPLETED",
        "status_message": "Transaction Completed Successfully"
    }
}
```

1. Obtain the value of the `rsa-signature` header (if callback) OR the value of the `rsa_signature` query parameter (if redirect).
2. Form the string payload to be used in signature verification. This is obtained by concatenating values of the callback/redirect data in the format; `event:merchant_reference:internal_reference:transaction_type:transaction_status` and these values are obtained from the callback/redirect data. The string payload would therefore be `transaction.completed:MCTREFT2WMNWZ23SBN6Y:DUSUPAYRMGRXNNYBWATKJ:COLLECTION:COMPLETED`
3. Use the public key obtained above to verify the signature as described in the sample source codes below.

{% tabs %}
{% tab title="PHP" %}

```php
<?php

public function isValidSignature() {
    $file = "path-to-file/dusupay.public.key.pem";
    $keyContent = file_get_contents($file);
    $publicKey = openssl_get_publickey($keyContent);
    $strPayload = "transaction.completed:MCTREFT2WMNWZ23SBN6Y:DUSUPAYRMGRXNNYBWATKJ:COLLECTION:COMPLETED";
    $signature = base64_decode("value-of-rsa-signature");

    /*true or false*/
    return openssl_verify($strPayload, $signature, $publicKey, "sha256") == 1;
}

?>
```

{% endtab %}

{% tab title="NodeJS" %}

```javascript
const crypto = require('crypto');
const fs = require('fs');

function isValidSignature() {
    const strPayload = "transaction.completed:MCTREFT2WMNWZ23SBN6Y:DUSUPAYRMGRXNNYBWATKJ:COLLECTION:COMPLETED";
    const signature = "value-of-rsa-signature";
    const publicKeyFile = "path-to-file/dusupay.public.key.pem";
    const publicKey = fs.readFileSync(publicKeyFile).toString().replace(/\\n/g, '\n');

    const verify = crypto.createVerify("SHA256");
    verify.write(strPayload);
    verify.end();

    /*true or false*/
    return verify.verify(publicKey, signature, 'base64');
}
```

{% endtab %}

{% tab title="Java" %}

```java
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class SignatureVerifier {

    public boolean isValidSignature() throws Exception {
        // Read public key from file
        Path pathToFile = Paths.get("path-to-file/dusupay.public.key.pem");
        byte[] keyBytes = Files.readAllBytes(pathToFile);

        // Decode public key
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PublicKey publicKey = keyFactory.generatePublic(keySpec);

        // Signature and payload
        String strPayload = "transaction.completed:MCTREFT2WMNWZ23SBN6Y:DUSUPAYRMGRXNNYBWATKJ:COLLECTION:COMPLETED";
        byte[] signatureBytes = Base64.getDecoder().decode("value-of-rsa-signature");

        // Verify signature
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initVerify(publicKey);
        signature.update(strPayload.getBytes());
        return signature.verify(signatureBytes);
    }

    public static void main(String[] args) throws Exception {
        SignatureVerifier verifier = new SignatureVerifier();
        System.out.println(verifier.isValidSignature());
    }
}

```

{% endtab %}

{% tab title="C#" %}

```csharp
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

public class SignatureVerifier
{
    public bool IsValidSignature()
    {
        // Read public key from file
        string pathToFile = @"path-to-file\dusupay.public.key.pem";
        string keyContent = File.ReadAllText(pathToFile);

        // Decode public key
        RSA rsa = RSA.Create();
        rsa.ImportFromPem(keyContent);

        // Signature and payload
        string strPayload = "transaction.completed:MCTREFT2WMNWZ23SBN6Y:DUSUPAYRMGRXNNYBWATKJ:COLLECTION:COMPLETED";
        byte[] signatureBytes = Convert.FromBase64String("value-of-rsa-signature");

        // Verify signature
        var verifier = new RSAPKCS1SignatureDeformatter(rsa);
        verifier.SetHashAlgorithm("SHA256");
        byte[] payloadBytes = Encoding.UTF8.GetBytes(strPayload);
        byte[] sha256Hash;

        using (SHA256 sha256 = SHA256.Create())
        {
            sha256Hash = sha256.ComputeHash(payloadBytes);
        }

        return verifier.VerifySignature(sha256Hash, signatureBytes);
    }

    public static void Main(string[] args)
    {
        SignatureVerifier verifier = new SignatureVerifier();
        Console.WriteLine(verifier.IsValidSignature());
    }
}

```

{% endtab %}

{% tab title="Python" %}

```python
import base64
import hashlib
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.serialization import load_pem_public_key

def is_valid_signature():
    # Read public key from file
    with open("path-to-file/dusupay.public.key.pem", "rb") as key_file:
        key_content = key_file.read()

    # Decode public key
    public_key = load_pem_public_key(key_content, backend=default_backend())

    # Signature and payload
    str_payload = "transaction.completed:MCTREFT2WMNWZ23SBN6Y:DUSUPAYRMGRXNNYBWATKJ:COLLECTION:COMPLETED"
    signature_bytes = base64.b64decode("value-of-rsa-signature")

    # Verify signature
    verifier = public_key.verifier(
        signature_bytes,
        padding.PKCS1v15(),
        hashlib.sha256
    )
    verifier.update(str_payload.encode())

    try:
        verifier.verify()
        return True
    except Exception as e:
        print("Signature verification failed:", e)
        return False

```

{% endtab %}

{% tab title="Ruby" %}

```ruby
require 'openssl'
require 'base64'

def is_valid_signature
  # Read public key from file
  file = File.read("path-to-file/dusupay.public.key.pem")

  # Decode public key
  public_key = OpenSSL::PKey::RSA.new(file)

  # Signature and payload
  str_payload = "transaction.completed:MCTREFT2WMNWZ23SBN6Y:DUSUPAYRMGRXNNYBWATKJ:COLLECTION:COMPLETED"
  signature_bytes = Base64.decode64("value-of-rsa-signature")

  # Verify signature
  sha256 = OpenSSL::Digest::SHA256.new
  result = public_key.verify(sha256, signature_bytes, str_payload)

  result
end

```

{% endtab %}
{% endtabs %}

Below is an example of a generated RSA signature (rsa-signature) on the sandbox environment. Feel free to verify it using the sample payload above and the public key.

{% code overflow="wrap" %}

```
bX6aa5avegrpqS7I9eK6OZXD14RkuEuKCkRgWHIGnXsI2Vr2O/NrRXUHLzaTm0ujk48hLdGV6dVxzapkB3NBOtHT8cntyXLX7rw5PsYg2FDsUT/7l5uH0xugG7JzLuY1J72GVKwNBMRu6VEJo9c43OLSha5Lldket7NBNub5ZO6zD0PIN6fSblp/FGuycfasxbjilpKwdXH9x1EDOOW2Ds4gyHesZjON/jiQuvs/qeekXqBCRVh/5D0tKfGoAGaug5sa3hOhSyskeiHihslpPBs5Yw4xy7ajuHNKCRlq6w8PsTW6RXsbcTGJbowpGv1j1xJoUxjVCIr5vJtwHIKXU9v1S8pZLc4Z5nYV/ejCSZZrMYB6Fg5n0bCz4zgUTbSmES3G/0Cthb/40zbDX83reFxweLLqO1VRQVd+3CFijLSalKmtvzFkAP2SSN6NpC/9yJfvOnq7sXt3u4igMsIkChzSYF/OZksZlCyC4q7zTR5k+7XJmpuE4FeYKWDaOSFtqRYEfsdA7+VgPdHaMGLW9wwBKA867DBvNuZC0lP5wEOkbqCVOmVtcbbXDDKUo3nybNRMky9Lz7fptIrdB8m7ZVdVkLZ6OZIM3vq3qx2y8cb5jqO8lUmsNIf+BJtVxhlz7kwc2NQEC34ga92wQoDVQWWv9211tF7fCOSnkA8QSc0=
```

{% endcode %}


# API Reference

{% openapi src="/files/gejC30kY7PVqsAJadmaA" path="/data/payment-providers" method="get" %}
[dusupay-swagger.json](https://388154480-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJJeVyAFgsYo2FQfi2OB0%2Fuploads%2Fgit-blob-ce3a368d7fbf251216ba1a9ac86692eaa4c3f268%2Fdusupay-swagger.json?alt=media)
{% endopenapi %}

{% openapi src="/files/gejC30kY7PVqsAJadmaA" path="/data/wallet-balances" method="get" %}
[dusupay-swagger.json](https://388154480-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJJeVyAFgsYo2FQfi2OB0%2Fuploads%2Fgit-blob-ce3a368d7fbf251216ba1a9ac86692eaa4c3f268%2Fdusupay-swagger.json?alt=media)
{% endopenapi %}

{% openapi src="/files/gejC30kY7PVqsAJadmaA" path="/data/transaction/verify/{merchant\_reference}" method="get" %}
[dusupay-swagger.json](https://388154480-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJJeVyAFgsYo2FQfi2OB0%2Fuploads%2Fgit-blob-ce3a368d7fbf251216ba1a9ac86692eaa4c3f268%2Fdusupay-swagger.json?alt=media)
{% endopenapi %}

{% openapi src="/files/gejC30kY7PVqsAJadmaA" path="/collections/initialize" method="post" %}
[dusupay-swagger.json](https://388154480-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJJeVyAFgsYo2FQfi2OB0%2Fuploads%2Fgit-blob-ce3a368d7fbf251216ba1a9ac86692eaa4c3f268%2Fdusupay-swagger.json?alt=media)
{% endopenapi %}

{% openapi src="/files/gejC30kY7PVqsAJadmaA" path="/collections/authorize" method="post" %}
[dusupay-swagger.json](https://388154480-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJJeVyAFgsYo2FQfi2OB0%2Fuploads%2Fgit-blob-ce3a368d7fbf251216ba1a9ac86692eaa4c3f268%2Fdusupay-swagger.json?alt=media)
{% endopenapi %}

{% openapi src="/files/gejC30kY7PVqsAJadmaA" path="/data/payout-bank-codes" method="get" %}
[dusupay-swagger.json](https://388154480-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJJeVyAFgsYo2FQfi2OB0%2Fuploads%2Fgit-blob-ce3a368d7fbf251216ba1a9ac86692eaa4c3f268%2Fdusupay-swagger.json?alt=media)
{% endopenapi %}

{% openapi src="/files/gejC30kY7PVqsAJadmaA" path="/payout/send-funds" method="post" %}
[dusupay-swagger.json](https://388154480-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FJJeVyAFgsYo2FQfi2OB0%2Fuploads%2Fgit-blob-ce3a368d7fbf251216ba1a9ac86692eaa4c3f268%2Fdusupay-swagger.json?alt=media)
{% endopenapi %}


# Merchant Account Transfers

The process of Merchant Account Transfers involves the movement of funds from one merchant Account (From Account) to another  Merchant Account (To Account). Here are the steps;-

**Step 1: Click on Account Transfers Menu.**

<figure><img src="/files/GC10TLnuQJqpqyYZgzhL" alt=""><figcaption></figcaption></figure>

**Step 2: Click "New Account Transfer" Button.**

<figure><img src="/files/H0p5pUcW8LcePKLEL3Ti" alt=""><figcaption></figcaption></figure>

**Step 3: Click the Dropdown, select From Account.**

<figure><img src="/files/sRULS9hU8SJeZJ8z3Ke7" alt=""><figcaption></figcaption></figure>

**Step 4: Click the Dropdown, select From Wallet.**

<figure><img src="/files/VweFekVs7HzrU9BwU1Sm" alt=""><figcaption></figcaption></figure>

**Step 5: Click Dropdown, select Receiver Account.**

<figure><img src="/files/VNpDUe0awiqtRtQhwu8m" alt=""><figcaption></figcaption></figure>

**Step 6: Click Dropdown, Select To Wallet.**

<figure><img src="/files/4IzSznELLIgtJu6jOQ6x" alt=""><figcaption></figcaption></figure>

**Step 7: Enter Amount to Transfer.**

<figure><img src="/files/I38rAKy2cDkNV1JDsrtY" alt=""><figcaption></figcaption></figure>

**Step 8: Click the "Narration/Purpose" field.** **Enter Narration.**

<figure><img src="/files/6cry6JeYrj5wtvUyn961" alt=""><figcaption></figcaption></figure>

**Step 9: Click "Create" Button to Submit Request**.

<figure><img src="/files/kQ9j9kMDS81ieUDP1ZtQ" alt=""><figcaption></figcaption></figure>

**Step 10: On the pop up window which appears, "Click Submit for Processing" Button**<br>

<figure><img src="/files/hLXmszaITL7aVuwUk28u" alt=""><figcaption></figcaption></figure>

11. **Once you have confirmed and submitted for Processing, Click "Close" Button.**

<figure><img src="/files/Ii3AfyffsvWNr5B5Qg7G" alt=""><figcaption></figcaption></figure>


# Availing Payout Funds

Some merchants wish that collected funds can be used for processing payouts/disbursements. This section what the merchant needs to do to avail balance for handling payouts

The platform requires that the merchant explicitly requests for funds to be availed for payouts/disbursements. This way, the merchant has full control of what their collections/deposits are used for. When the merchant initiates this request on the platform, the finance team handles the request and when approved, the merchant will be notified via email.

**Step 1:** Click on Sub-Account Transfers Menu.

<figure><img src="/files/6OrAzSPAXfpdi3JhiFvx" alt=""><figcaption></figcaption></figure>

**Step 2:** Click on "Create" Butto&#x6E;**.**

<figure><img src="/files/QbNR1rstuLp8jBRsr84o" alt=""><figcaption></figcaption></figure>

**Step 3:** Click "Payout Funds Transfer".

<figure><img src="/files/yN6ncxhRl0oMu5rGWxA6" alt=""><figcaption></figcaption></figure>

**Step 4:** Click "From Account" dropdown. Select Account.

<figure><img src="/files/SqaLqydIKhfcKRdm8yXA" alt=""><figcaption></figcaption></figure>

**Step 5:** Click "From Wallet" dropdown. Select Wallet.

<figure><img src="/files/TwITKDGzGkYCsxrDQmec" alt=""><figcaption></figcaption></figure>

**Step 6:** Click "Receiver Account" dropdown. Select Account.

<figure><img src="/files/wrWeYHX8WPf3bnDD4J1X" alt=""><figcaption></figcaption></figure>

**Step7:** Click "To Wallet" dropdown. Select Wallet.

<figure><img src="/files/d6xolc90KFBGFxaUEuON" alt=""><figcaption></figcaption></figure>

**Step 8**: Click the "Transfer Amount " field. Enter Amount.

<figure><img src="/files/Sd99r5AeBi9zuXQCpSh9" alt=""><figcaption></figcaption></figure>

**Step 9:** Click the "Narration/Purpose" field. Enter Narration.

<figure><img src="/files/g4GhPwmMEAvJ2ZIuF2je" alt=""><figcaption></figcaption></figure>

**Step 10:** Click "Create" button.

<figure><img src="/files/PfZpIwwRWMOb6iAYtyg9" alt=""><figcaption></figcaption></figure>

**Step 11:** Click "Submit For Processing" button.

<figure><img src="/files/AbuIdEAbSRTgDIPa8TFn" alt=""><figcaption></figcaption></figure>

**Step 12:** Click Confirm "Submit For Processing" button.

<figure><img src="/files/bYXUvIiVRy6x622EHfVK" alt=""><figcaption></figcaption></figure>

**Step 13:** Click Close to return to the Sub Account Transfers Report.

<figure><img src="/files/vAgSHn3TaHR7KRy7UZUz" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
Once the finance executes the transfer and approves, you'll have funds available for doing payouts and therefore can proceed to send funds to your desired recipients.
{% endhint %}


# Sub Account Transfers

Merchants holding more than one balance are allowed the ability to transfer funds between currency wallets. The section below describes the steps.

The nature of sub account transfers is such that; two currencies are involved. This therefore means that the merchant and the finance team will agree on the conversion rate in order for the merchant to receive their desired destination amount. The ability to transfer funds across different currencies is crucial for two purposes as described in the table below.

<table><thead><tr><th width="212">Purpose</th><th>Description</th></tr></thead><tbody><tr><td>Availing Payout Funds</td><td>You may for example be interested in doing UGX payouts but currently only hold balance in other currencies e.g. KES. This sub account transfer workflow allows you the ability to convert your available KES into UGX and then you can avail UGX for payouts.</td></tr><tr><td>Funds Settlement</td><td>DusuPay has a strict funds settlement process where we can only settle a few currencies to the bank otherwise crypto (USDT) settlements are the choice. This would therefore mean that when you want to settle funds held in alternative currencies, you may need to first convert said funds to USDT and then initiate the funds settlement.<br><br>Such a conversion uses this sub account transfer process.</td></tr></tbody></table>

**Step 1:** Click on Sub-Account Transfers Menu.

<figure><img src="/files/6OrAzSPAXfpdi3JhiFvx" alt=""><figcaption></figcaption></figure>

**Step 2:** Click on "Create" Butto&#x6E;**.**

<figure><img src="/files/QbNR1rstuLp8jBRsr84o" alt=""><figcaption></figcaption></figure>

**Step 3:** Click "Sub Account Transfer".

<figure><img src="/files/q1p0wOqFMySfXclRBFse" alt=""><figcaption></figcaption></figure>

**Step 4:** Click "From Account" dropdown. Select Account.

<figure><img src="/files/uUlfyJR8uyk3j2RiHjhB" alt=""><figcaption></figcaption></figure>

**Step 5:** Click "From Wallet" dropdown. Select Wallet.

<figure><img src="/files/n4yOp88Oo84iGtgHTydL" alt=""><figcaption></figcaption></figure>

**Step 6:** Click "Receiver Account" dropdown. Select Account.

<figure><img src="/files/drZiHUfDvJ2w2Rd0ixdb" alt=""><figcaption></figcaption></figure>

**Step7:** Click "To Wallet" dropdown. Select Wallet.

<figure><img src="/files/aTgsfI62G17PHGrDWaGA" alt=""><figcaption></figcaption></figure>

**Step 8**: Click the "Transfer Amount " field. Enter Amount.

<figure><img src="/files/2sPWaMvxZXnxEIWMa5j1" alt=""><figcaption></figcaption></figure>

**Step 9:** Click the "Narration/Purpose" field. Enter Narration.

<figure><img src="/files/g4GhPwmMEAvJ2ZIuF2je" alt=""><figcaption></figcaption></figure>

**Step 10:** Click "Create" button.

<figure><img src="/files/PfZpIwwRWMOb6iAYtyg9" alt=""><figcaption></figcaption></figure>

**Step 11:** Click "Submit For Processing" button.

<figure><img src="/files/AbuIdEAbSRTgDIPa8TFn" alt=""><figcaption></figcaption></figure>

**Step 12:** Click Confirm "Submit For Processing" button.

<figure><img src="/files/bYXUvIiVRy6x622EHfVK" alt=""><figcaption></figcaption></figure>

**Step 13:** Click Close to return to the Sub Account Transfers Report.

<figure><img src="/files/vAgSHn3TaHR7KRy7UZUz" alt=""><figcaption></figcaption></figure>


# Funds Settlement

Some merchants wish that collected fund can be settled every so often. This section describes how the merchant can initiate a funds settlement request to the finance team for handling.

**Step 1: Click "Settlement Requests" Menu.**

<figure><img src="/files/I3Y5wIVGikpeOFQ6PqgM" alt=""><figcaption></figcaption></figure>

**Step 2: Click "New Settlement Request" Button.**

<figure><img src="/files/Ij0I0nr8x452HeFtw92h" alt=""><figcaption></figcaption></figure>

**Step 3: Click Dropdown, Select Merchant Account.**

<figure><img src="/files/vp6sqlAfK1MDo3QjPMnW" alt=""><figcaption></figcaption></figure>

**Step 4: Click Dropdown, Select Currency.**

<figure><img src="/files/ejzPtb1wMMhL6n7icCY4" alt=""><figcaption></figcaption></figure>

**Step 5: Click Dropdown, Select Source Wallet.**

<figure><img src="/files/cwtenbqVZlBhYK9xGQ3U" alt=""><figcaption></figcaption></figure>

**Step 6: Click the "Request Amount (Value Required)" field. Enter Settlement Amount.**

<figure><img src="/files/BDSUz7xZyRw1Epc5UGyq" alt=""><figcaption></figcaption></figure>

**Step 7: Select Dropdown, Select Merchant Account Bank.**

<figure><img src="/files/BinAuWw3lPVta741p3nL" alt=""><figcaption></figcaption></figure>

**Step 8: Click "Create " Button.**

<figure><img src="/files/cmgnXGQujNzmK51U1HHp" alt=""><figcaption></figcaption></figure>

**Step 9: On the popup window, Click "Submit for Processing".**

<figure><img src="/files/CVrzuCGDFB8QhoXl71cC" alt=""><figcaption></figcaption></figure>

**Step 10: Once you have Confirmed and Submitted for Processing, Click "Cancel" Button to return to the Settlement Requests Report.**

<figure><img src="/files/uYgGuMZgI0lW90X5yHsa" alt=""><figcaption></figcaption></figure>


# Transaction Audit Logs

This section describes the detailed description of how a given transaction was handled on the platform until it received a final status.

**Click "Transaction Requests" Menu.**

<figure><img src="/files/n9JW8DadoV2LdUfZDE1S" alt=""><figcaption></figcaption></figure>

**From the Report displayed, Click on the Merchant Reference**

<figure><img src="/files/gizPN06mJLUxORiAnUy5" alt=""><figcaption></figcaption></figure>

After Clicking the merchant Reference, this will open another page with transaction details for the selected reference.

\
**On that page is a section called "Event Logs" which explain the different events on a transaction.**

**COLLECTION**<br>

a. Event logs for **a Completed Collection**

<figure><img src="/files/e3REcXtEHWeiS36uxQxZ" alt=""><figcaption></figcaption></figure>

b. Event logs for **a Failed Collection**

<figure><img src="/files/wa7rHa7l9XlONGycVaqY" alt=""><figcaption></figcaption></figure>

## PAYOUT

a. Event logs for **a Failed Payout**

<figure><img src="/files/9o0pGxMDwXhnHXfTZgNg" alt=""><figcaption></figcaption></figure>

b. Event logs for **a Completed Payout.**

<figure><img src="/files/gjAiH7X6YbZ4olS7UHiY" alt=""><figcaption></figcaption></figure>


# Cross Currency Transactions

The DusuPay API allows for cross currency transactions whereby the request currency does not match the currency of the selected payment option. The platform handles the conversion appropriately.

The payment options provided by the DusuPay API have specific transaction currencies as seen from the response [here](/utility-functions/payment-options). These currencies therefore correspond to the currencies in which DusuPay is able to hold balances. The platform however allows the merchant to send any currency in the collection/payout requests for example the request could be for USD 100 but the selected payment option has transaction currency as GHS or XAF etc.

The DusuPay API has a robust workflow to ensure the conversion happens to the transaction currency/amount since that's the currency the payment provider supports. When the transaction completes successfully, the merchant balance would be updated in the transaction currency. The API uses the prevailing market exchange rates to do the conversion.

Consider a sample Mobile Money collection request where the requested currency is USD and the payment option is mtn\_ug (MTN Mobile Money Uganda). In the example, we assume the conversion rate to be 1 USD = 3800 UGX and that the transaction charge is 1%

```json
{
    "merchant_reference": "MCTREFT2WMNWZ23SBN6Y",
    "transaction_method": "MOBILE_MONEY",
    "currency": "USD",
    "amount": 100,
    "provider_code": "mtn_ug",
    "msisdn": "256777000001",
    "customer_name": "JOHN DOE",
    "description": "Test Collection"
}
```

On completion, the callback would look like the following. Take critical note on the callback parameters; `request_currency`, `request_amount`, `transaction_currency` and `transaction_amount`

```json
{
    "event": "transaction.completed",
    "payload": {
        "id": 20760,
        "merchant_reference": "MCTREFT2WMNWZ23SBN6Y",
        "internal_reference": "DUSUPAYRMGRXNNYBWATKJ",
        "transaction_type": "COLLECTION",
        "request_currency": "USD",
        "request_amount": 100,
        "transaction_currency": "UGX",
        "transaction_amount": 380000,
        "transaction_charge": 3800,
        "transaction_account": "256777000001",
        "charge_customer": false,
        "total_credit": 376200,
        "provider_code": "mtn_ug",
        "customer_name": "JOHN DOE",
        "transaction_status": "COMPLETED",
        "status_message": "Transaction Completed Successfully"
    }
}
```

{% hint style="info" %}
This workflow gives merchants the flexibility to bill their customers in other currencies, while allowing the customers to pay/get paid in the currencies at their disposal, and using the payment options that are most convenient. This workflow applies to both collections and payouts
{% endhint %}


