Deposits
Deposits allow you to request payment from a customer. Funds will be moved from the customer’s mobile money wallet to your account in pawaPay.
In this guide, we’ll go through step by step on how to approach building a high quality deposit flow.
If you haven’t already, check out the following information to set you up for success with this guide.
What you should know
Understand some consideration to take into account when working with mobile money.
How to start
Sort out API tokens and callbacks.
Initiating a deposit
Let’s start by hard-coding everything. Throughout this guide we will be making the flow more dynamic and improving the customer experience incrementally.
Initiate the deposit
Let’s send this payload to the initiate deposit endpoint.
Let’s see what’s in this payload.
We ask you to generate a UUIDv4 depositId
to uniquely identify this deposit.
This is so that you always have a reference to the deposit you are initiating, even if you do not receive a response from us due to network errors.
This allows you to always reconcile all payments between your system and pawaPay.
You should store this depositId in your system before initiating the deposit with pawaPay.
The amount
and currency
should be relatively self-explanatory.
This is the amount
that will be collected from the customer.
Any fees will be deducted from that amount after the collection has completed.
Then the provider
and phoneNumber
specify exactly who is paying.
Mobile money wallets are identified by the phone number, like bank accounts are by their account number.
The provider
specifies which mobile money operator this wallet is registered at. For example MTN Ghana or MPesa Kenya.
You will receive a response for the initiation.
The status
shows whether this deposit was ACCEPTED
for processing or not.
We will go through failure modes later in the guide.
We will also take a look later how nextStep
can be used, but for now it’s safe to ignore it.
The customer will approve the payment
Now that the deposit has been initiated, the customer will receive a PIN prompt on their phone to authorise the payment.
This step only happens when initiating live payments on your production account.
On your sandbox account, this step is skipped!
Get the final status of the payment
Now that the customer has approved the payment, you will receive a callback from us to your configured callback URL.
The main thing to focus on here is the status
which is COMPLETED
indicating that the deposit has been processed successfully and the funds have been collected to your wallet on your pawaPay account.
If you have not configured callbacks, you can always poll the check deposit status endpoint for the final status of the payment.
And done!
Congratulations! You have now made your very first deposit with pawaPay. We made a call to pawaPay to initiate the deposit. And we found out the final status of that payment.
But there’s a little more to a high quality integration. In the next sections, we will go into more details on:
- How to make this easy to expand to new markets and providers
- How to handle failures so that discrepancies between your system and pawaPay don’t happen
- How to cover countries with providers that use different authorisations methods for payments
- And more…
Asking for payment details from the customer
In the first part we harcoded everything. Now let’s take a look at how to ask that information from the customer who is paying.
We are focusing on the use case where you know the amount to be paid already. Later in the guide, we will also cover the case where the customer can choose the amount to pay.
Some providers do not support decimals in amount, so amounts like “100.50” might not be possible.
You can find how providers support decimals in the amount in the Providers section.
Showing the providers
The active configuration endpoint has useful and important information including which providers and markets have been configured on your pawaPay account.
In most cases you already know from which country the customer is from. And since we are implementing deposits, we can fetch the configuration for deposits only.
As you can see, in providers
we have a list of all the providers that have been configured on your account for initiating deposits.
The displayName
contains the name of the provider.
You can show those as options to your customer.
To enable new payment providers being automatically added to your deposit flows, we’ve also added the logo of the provider as the logo
.
This way it is possible to implement the deposit flow dynamically, so that when new providers are enabled on your pawaPay account, they become available for your customers without additional effort.
This might look something like this:
Do not have a default provider. For example, as the first selection in a dropdown. This often gets missed by customers causing payments to fail due to being sent to the wrong provider.
Ask for the phone number
In the active configuration endpoint there is already the prefix
within the country object.
This the country calling code for that country.
We recommend showing that to the customer in front of the input box for entering the phone number.
This makes it clear that the number they enter should not include the country code.
We have also added flag
to the country object to make it look a little nicer.
This might look something like this:
Now let's validate the number and predict the provider
To make sure the number entered by the customer is a valid phone number, let’s use the predict provider endpoint.
First, concatenate the prefix
to what was entered by the customer as the phone number.
You can now send it to validation. We will handle basic things like whitespace, characters etc. We also make sure the number of digits is correct for the country and handle leading zeros.
If the phone number is not valid, a failure will be returned. You can show the customer a validation error. Otherwise, you will get the following response:
The phoneNumber
is the sanitized MSISDN format of the phone number that you can use to initiate the deposit.
We strongly recommend using this endpoint for validating numbers especially due to some countries not strictly following ITU E.164.
Also, in the response, you will find the provider
. This is the predicted provider for this phoneNumber
.
We recommend preselecting the predicted provider for the customer.
In most countries we see very high accuracy for predictions removing another step from the payment experience.
We recommend allowing the customer to override the prediction as the accuracy is not 100%.
You should now have a payment page similar to this:
Initiate the deposit
Now all information is either coming from the active configuration endpoint, the customer and your system (the amount to pay). We can call the initiate deposit endpoint with that information.
The response and callback with the final status are the same as in the first part of the guide.
Please keep in mind that not all providers support amounts with decimals. You can find which providers do and which ones don’t from the Providers section.
Done again!
We now made a real deposit flow collecting information from different sources and avoiding any hard-coded data. This way, enabling new providers will not cause additional changes for you.
Next, let’s take a look at what to do while we are waiting for the customer to authorise the payment.
After initiating the deposit
After you initiate a deposit, the customer will need to authorise it. While there are a couple of different ways providers handle payment authorisation, the most common one involves a PIN prompt popping up on the customers phone. In this step, we will focus on that type of authorisation flow. Later in the guide, we will cover the rest as well.
Finding the payment authorisation type
We already used the active configuration endpoint to find the providers configured on your pawaPay account. The type of authorisation used by the provider is also available from that endpoint.
There are three properties here that we will focus on.
The authType
shows the type of authorisation that this provider uses. We are focusing on PROVIDER_AUTH
currently.
Other authTypes
will be covered later in the guide.
The pinPrompt
property is only applicable for providers with authType
of PROVIDER_AUTH
.
It indicates whether or not the PIN prompt that is part of the authorisation will pop up on the customers phone automatically (AUTOMATIC
) or if they need to take some action for that (MANUAL
).
The pinPromptRevivable
shows whether it’s possible for the customer to get the PIN prompt to pop up again in case it fails on the first attempt.
Now that we know the specifics of the authorisation flow of the provider, let’s implement the screen to show the customer after having initiated the deposit.
Customer experience for AUTOMATIC `pinPrompt`
For pinPrompt
value of AUTOMATIC
we can show the customer a screen indicating that they should authorise the payment by entering their mobile money PIN on their phone.
On the PIN prompt, there is usually a reference to the payment provider (pawaPay or one of its local subsidiaries).
You should show that to the customer as part of the message to assure that it’s a valid payment authorisation request.
You can find it from the response as nameDisplayedToCustomer
.
Here’s an example of what you might show to the customer while waiting for the callback with the final status of this payment.
What if the PIN prompt is revivable?
If pinPromptRevivable
is true for the provider, it means that the customer can revive the PIN prompt in case something happens on the first attempt.
For example, this might happen when they can’t find their phone before the PIN prompt times out, they happen to get a call at the same time or there is a network issue delivering the PIN prompt.
In this case, we recommend waiting about 10-15 seconds after initiation and showing the customer instructions on how they can revive the PIN prompt to try again. The specific instructions are in the response from active configuration endpoint as ‘pinPromptInstructions’.
The channel
for reviving the PIN prompt indicates where the customer is able to revive the PIN prompt.
This is predominantly USSD
indicating they need to dial a USSD code on their phone to revive the PIN prompt.
The displayName
includes a message on the intent of the instructions. You are free to use it or come up with your own message.
The quickLink
(if available) includes the href that you can use to predial the USSD shortcode.
You should use it as the href
of an <a />
tag or button.
This way, when the customer is using the same phone to visit your site that they are paying with, they can press to predial the USSD code.
The instructions
include the exact steps they should take to revive the PIN.
You can iterate over them to show the instructions for reviving the PIN prompt.
You can use the text
of each instruction directly or if you want to emphasise key properties, you can use the template
and variables
.
Just surrond the variables
of an instruction with the emphasis you need and replace them in the template.
Customer experience for MANUAL `pinPrompt`
In case of pinPrompt
value of MANUAL
, the customer needs to take some action to get to the PIN prompt.
While most customers know what to do and they will get an SMS with instructions anyway, we’ve seen improved success rates with clear instructions as part of payment experience.
You can get the instructions to show for this exactly the same way as for PIN prompt revival.
And done!
Now we have made sure that the customer always knows what they need to do to authorise a payment.
Next let’s take a look now how to handle failures.
Avoiding failures on initiation
Payment initiation can fail for various reasons. Let’s see how we can best handle those.
Handling provider downtime
Providers may have downtime. We monitor providers performance and availability 24/7. For your operational and support teams, we have a status page. From there they can subscribe to get updates in email or slack for all the providers they are interested in.
To avoid failed payment attempts, we’ve also exposed this information over API from both the provider availability and active configuration endpoints. This way you can be up front with the customer before they attempt to pay.
Based on whether the status
of the specific provider is OPERATIONAL
or CLOSED
you can inform the customer up front that this provider is currently not available and they can attempt again later.
Handling decimals in amount and transaction limits
Every time you initiate a deposit you should confirm that the status
in the response is ACCEPTED
.
If the status is REJECTED
the failureReason
will contain both the failureCode
and failureMessage
indicating what has happened.
Most of those failures are avoidable if handled beforehand.
The failureMessage
from pawaPay API is meant for you and your support and operations teams.
You are free to decide what message to show to the customer.
In the above example, where the provider does not support decimal places in amount, you need to preempt that by rounding the amounts. Depending on your use case, you might do that preemptively by adjusting local pricing or dynamically before payment.
Second thing to consider is that customers mobile money wallets have limits on how big the payments can be. Customers are able to get those limits increased on their mobile money wallets by contacting their provider and going through extended KYC. By default, we have set the transaction limits on your pawaPay account based on the most common wallet limits.
Regarding currencies, the only country available through pawaPay that supports multiple currencies is DRC.
These are exposed in the active configuration endpoint as an array of currencies
.
You can access both the decimal places supported and transaction limits from the active configuration endpoint.
You can use those to ensure inputs to initiate deposit are cleaned before initiation and rejections don’t happen.
Ensure the right phone number format
The deposit endpoint expects the phoneNumber
in the MSISDN format.
You can use the predict provider endpoint to clean up the input you receive from the customer. Make sure the input starts with the country code.
If the phone number is not valid, a failure will be returned. You can show the customer a validation error.
The phoneNumber
in the response will be in the correct format to initiate the deposit.
One initiation per depositId
All payments initiated with pawaPay are idempotent based on their ID.
You must always generate a new UUIDv4 for the depositId
.
When you attempt to use the same depositId
more than once the payment will be rejected.
Handling provider availability
We already discussed how to show to the customer which payment methods are currently not available due to provider downtime. There is usually some time between when you fetch and show this information to the customer and when they initiate the payment. We still need to ensure accurate information is shown in case the provider goes down during that time.
If a payment is initiated during provider downtime, it will be rejected.
Many customers will have mobile money wallets with different providers. It’s reasonable to ask them to pay with another provider or to attempt again later.
Handling HTTP 500 with failureCode UNKNOWN_ERROR
The UNKNOWN_ERROR
failureCode indicates that something unexpected has gone wrong when processing the payment.
There is no status
in the response in this case.
It is not safe to assume the initiation has failed for this deposit.
You should verify the status of the deposit using the check deposit status endpoint.
Only if the deposit is NOT_FOUND
should it be considered FAILED
.
We will take a look later in the guide, how to ensure consistency of payment statuses between your system and pawaPay.
And done!
We have now handled different failures that might happen during initiation.
Next, let’s take a look at payment failures that can happen during processing.
Handling processing failures
Handling failures during processing
As the pawaPay API is asynchronous, you will get a deposit callback with the final status of the deposit.
If the status
of the deposit is FAILED
you can find further information about the failure from failureReason
.
It includes the failureCode
and the failureMessage
indicating what has gone wrong.
The failureMessage
from pawaPay API is meant for you and your support and operations teams.
You are free to decide what message to show to the customer.
Find all the failure codes and implement handling as you choose.
Operation specific processing failures are also documented in the API reference:
We recommend allowing easy retries for customers by taking the customer back to the payment information collection screen and showing the failure reason on that page. This way they can quickly try again.
We have standardised the numerous different failure codes and scenarios with all the different providers.
The specificity of the failure codes varies by provider.
The UNSPECIFIED_FAILURE
code indicates that the provider indicated a failure with the payment, but did not provide any more specifics on the reason of the failure.
In case there is a general failure, the UNKNOWN_ERROR
failureCode will be returned.
And done!
We have now also taken care of failures that can happen during payment processing. This way the customer knows what has happened and can take appropriate action to try again.
Now let’s take a look at some markets where the payment authorisation flow is a little different than usual.
Ensuring consistency
When working with financial APIs there are some considerations to take to ensure that you never think a payment is failed, when it is actually successful or vice versa. It is essential to keep systems in sync on the statuses of payments.
Let’s take a look at some considerations and pseudocode to ensure consistency.
Defensive status handling
All statuses should be checked defensively without assumptions.
Handling network errors and system crashes
The key reason we require you to provide a depositId
for each payment is to ensure that you can always ask us what is the status of a payment, even if you never get a response from us.
You should always store this depositId
in your system before initiating a deposit.
The important thing to notice here is that we only mark a payment as FAILED when there is a clear indication of it’s failure.
We use the check deposit status endpoint when in doubt whether the payment was ACCEPTED
by pawaPay.
Implementing an automated reconciliation cycle
Implementing the considerations listed above avoids almost all discrepancies of payment statuses between your system and pawaPay. When using callbacks to receive the final statuses of payments, issues like network connectivity, system downtime, and configuration errors might cause the callback not to be received by your system. To avoid keeping your customers waiting, we strongly recommend implementing a status recheck cycle.
This might look something like the following.
Having followed the rest of the guide, with this simple reconciliation cycle, you should not have any inconsistencies between your system and pawaPay. Having these checks automated will take a load off your operations and support teams as well.
Other payment authorisation flows
Overwhelmingly providers use a PIN prompt to authorise payments. There are some markets that need support for alternative authorisation flows:
- Burkina Faso
- Ivory Coast
- Senegal
If you are not looking to go live in these markets, you can skip this section of the guide.
In this case, we recommend filtering out all providers that do not have authType
of PROVIDER_AUTH
from your calls to active configuration.
Let’s implement support for these authorisation flows as well to ensure that all providers can be used.
Preauthorised flows
Let's add support for preauthorised payments
Currently, the only provider using preauthorised payments is Orange in Burkina Faso. Preauthorisation means that the customer needs to authorise the payment before initiation the payment.
For Orange in Burkina Faso the customer needs to generate an OTP through Oranges USSD menu. Let’s make sure they know how to do that by showing them step-by-step instructions for that. Let’s get those instructions from the active configuration endpoint.
The channel for reviving the PIN prompt indicates where the customer is able to generate the OTP. For Orange in Burkina Faso, this is USSD indicating they need to dial a USSD code on their phone to revive the PIN prompt.
The displayName includes a message on the intent of the instructions. You are free to use it or come up with your own message.
The quickLink includes the href that you can use to predial the USSD shortcode. You should use it as the href of an tag or button. This way, when the customer is using the same phone to visit your site that they are paying with, they can click to predial the USSD code.
The instructions include the exact steps they should take to generate the OTP. You can iterate over them to show the instructions to generate the OTP. You can use the text of each instruction directly or if you want to emphasise key properties, you can use the template and variables. Just surrond the variables of an instruction with the emphasis you need and replace them in the template.
You also need to provide the customer a place to provide the preauthentication OTP. The customer would then follow the instructions on their phone to generate the OTP.
Dial into the USSD menu of the provider
Enter their mobile money PIN to generate the OTP
Get the OTP
Provide it to you
You must then include this OTP as the preAuthorisationCode
into the request to initiate deposit.
You can now just tell the customer that the payment is processing and wait for a callback from pawaPay with the final status of the payment.
And done!
We can now get paid by customers using Orange in Burkina Faso.
Now let’s look at redirection based authorisation flows.
Redirection based flows
Some providers use redirection based authorisation. This is slightly more involved as you will need to redirect the customer to a URL where they can authorise the payment. This is used by Wave in both Senegal and Ivory Coast.
Let’s look at how that might look like for a customer using Wave in Senegal.
Allow the customer to initiate the payment
Initiate deposit
You can now initiate the deposit. After the authorisation is completed, the customer will be forwarded to…
successfulUrl
if the payment completed successfully.failedUrl
if the payment failed to complete.
You will receive a response for this with information on what to do next.
The nextStep
property indicates that the next thing to do is to retrieve the authorizationUrl
to forward the customer to.
Get the authorizationUrl
If you have configured callbacks, you will receive a callback with the authorizationUrl
.
If you do not have callbacks configured, you can always poll check deposit status.
The response will keep returning GET_AUTH_URL
as the value of nextStep
until the provider makes the authorizationUrl
available.
Then the response will include the authorizationUrl
and the value of nextStep
will have changed to REDIRECT_TO_AUTH_URL
.
This is usually very fast, but since the provider needs to return it, polling should be implemented.
Forward the customer to authorise the payment
You can now use the authorizationUrl
that you retrieved in the previous step to forward the customer to it.
If they are on desktop, they will see a QR code that they can scan with their phone that has the Wave app installed.
The customer can then authorise the payment
If the customer is using your site from the phone that has Wave app installed, they will be immmediately redirected to the app to confirm the payment.
Once that's done...
…they will see a confirmation screen and then depending on the result of the payment, be forwarded back to either the successfulUrl
or failedUrl
.
Confirm the payment status
We will of course send you a callback with the final status of the payment, as usual.
We do recommend validating the status of the deposit on your successfulUrl
or failedUrl
from the check deposit status endpoint.
And done!
You have now built the payment experience allowing customers to pay you regardless of which provider they are using.
Customer chooses the amount
For some use cases, the customer should decide how much they are paying.
Let’s take a look at how to support that.
Decimals support
Not all providers support decimals in amount - amounts like 100.50 will fail to process. This information is available for each provider in active configuration endpoint.
The possible values are NONE
and TWO_PLACES
. It’s easy to provide dynamic validation now that is appropriate for the provider.
Transaction limits
Customers’ mobile money wallets have limits on how big the payments can be. Customers are able to get those limits increased on their mobile money wallets by contacting their provider and going through extended KYC. By default, we have set the transaction limits on your pawaPay account based on the most common wallet limits.
The limits for the provider will be available from the active configuration endpoint.
It’s easy to provide dynamic validation ensuring the amount is between minAmount
and maxAmount
.
And done!
Now customers can choose the amount to pay without making multiple attempts.
Payments in reconciliation
When using pawaPay, you might find that a payment status is IN_RECONCILIATION
.
This means that there was a problem determining the correct final status of a payment.
When using pawaPay all payments are reconciled by default and automatically - we validate all final statuses to ensure there are no discrepancies.
When encountering payments that are IN_RECONCILIATION
you do not need to take any action.
The payment has already been sent to our automatic reconciliation engine and it’s final status will be determined soon.
The reconciliation time varies by provider. Payments that turn out to be successful are reconciled faster.
What to do next?
We’ve made everything easy to test in our sandbox environment before going live.
Test different failure scenarios
We have different phone numbers that you can use to test various failure scenarios on your sandbox account.
Review failure codes
Make sure all the failure codes are handled.
Add another layer of security
To ensure your funds are safe even if your API token should leak, you can always implement signatures for financial calls to add another layer of security.
And when you are ready to go live
Have a look at what to consider to make sure everything goes well.