USSD charge
Hey👋. We recommend checking out the overview to understand the basics of direct charge first. This guide assumes you've read that.
Direct USSD charge allows you to collect payments via USSD. With USSD charge, you call our API to create a charge, then your customer completes the payment by dialling their bank's USSD code on their mobile phone. Once the payment is completed, we'll notify you via webhook.
Initiating the payment
First, you'll need the customer's payment details. Since it's a USSD transaction, the only detail you need is the account_bank
, the code matching the customer's bank. You can provide a simple UI for the user to select their bank.
USSD payments are currently only supported for the following banks:
- Access bank (
044
) - Ecobank (
050
) - Fidelity bank (
070
) - First bank of Nigeria (
011
) - First city monument bank (FCMB) (
214
) - Guaranty trust bank (
058
) - Heritage bank (
030
) - Keystone bank (
082
) - Lotus bank (
303
) - Premium Trust bank (
105
) - Stanbic IBTC bank (
221
) - Sterling bank (
232
) - Union bank (
032
) - United bank for Africa (UBA) (
033
) - Unity Bank (
215
) - VFD microfinance bank (
090110
) - Wema bank (
035
) - Zenith bank (
057
)
Now, combine that with the rest of the payment details to create the payload and send to our charge USSD endpoint. You'll need to specify amount
, currency
, email
and a unique tx_ref
. You can also specify more details, such as the customer's fullname
, phone_number
, and custom meta
information. See the endpoint documentation for details.
// Install with: npm i flutterwave-node-v3
const Flutterwave = require('flutterwave-node-v3');
const flw = new Flutterwave(process.env.FLW_PUBLIC_KEY, process.env.FLW_SECRET_KEY);
const payload = {
account_bank: '058',
amount: 7500,
currency: 'NGN',
email: 'chunkylover53@aol.com',
tx_ref: this.generateTransactionReference(),
fullname: 'Homer Simpson',
};
flw.Charge.ussd(payload)
.then(console.log)
.catch(console.log);
// Install with: composer require flutterwavedev/flutterwave-v3
$flw = new \Flutterwave\Rave(getenv('FLW_SECRET_KEY'));
// Set `PUBLIC_KEY` as an environment variable
$ussdChargeService = new \Flutterwave\Ussd();
$payload = [
"account_bank" => '058',
"amount" => 7500,
"currency" => 'NGN',
"email" => 'chunkylover53@aol.com',
"tx_ref" => $this->generateTransactionReference(),
"fullname" => 'Homer Simpson',
];
$response = $ussdChargeService->ussd($payload);
print_r($response);
# Install with: gem install flutterwave_sdk
require 'flutterwave_sdk'
flw = Flutterwave.new(ENV["FLW_PUBLIC_KEY"], ENV["FLW_SECRET_KEY"], ENV["FLW_ENCRYPTION_KEY"])
charge_ussd = USSD.new(flw)
payload = {
account_bank: '058',
amount: 7500,
currency: 'NGN',
email: 'chunkylover53@aol.com',
fullname: 'Homer Simpson',
tx_ref: generate_transaction_reference,
}
response = charge_ussd.initiate_charge payload
print response
curl --request POST \
--url https://api.flutterwave.com/v3/charges?type=ussd \
--header 'Authorization: Bearer YOUR_SECRET_KEY' \
--header 'content-type: application/json' \
--data '{
"account_bank": "058",
"amount": 7500,
"currency": "NGN",
"email": "chunkylover53@aol.com",
"fullname": "Homer Simpson",
"tx_ref": "BJUYU399fcd43"
}'
Handling the response
You'll get a response that looks like this:
{
"status": "success",
"message": "Charge initiated",
"data": {
"id": 276641033,
"tx_ref": "BJUYU399fcd43",
"flw_ref": "FLW693741590884271975",
"device_fingerprint": "N/A",
"amount": 7500,
"charged_amount": 7500,
"app_fee": 491.2,
"merchant_fee": 0,
"processor_response": "Transaction in progress",
"auth_model": "USSD",
"currency": "NGN",
"ip": "N/A",
"narration": "MerchantName",
+ "status": "pending",
"payment_type": "ussd",
"fraud_status": "ok",
"charge_type": "normal",
"created_at": "2020-05-31T00:17:51.000Z",
"account_id": 17321,
"customer": {
"id": 210466255,
"phone_number": null,
"name": "Homer Simpson",
"email": "chunkylover53@aol.com",
"created_at": "2020-05-31T00:17:50.000Z"
},
+ "payment_code": "9039"
},
"meta": {
"authorization": {
"mode": "ussd",
+ "note": "*889*767*9039#"
}
}
}
Let's break this down:
status
is"successful"
, meaning that the charge was initiated successfullydata.status
is"pending"
, meaning that the customer needs to authorize the transaction via USSDdata.payment_code
contains a code for this transaction. For GTBank USSD payments, the user will be prompted to enter this after dialing the USSD code.meta.authorization
contains the important details to complete the payment. Thenote
field holds the USSD code that the customer needs to dial from the mobile number linked to their bank account. Typically, it will be in the form*<bank_code>*xxx*<payment_code>#
.
You should instruct your customer to dial the code in meta.authorization.note
(and enter the payment_code
if necessary to complete the payment.
Completing the payment
To complete the payment, the customer needs to dial the USSD code and authorize the charge.
In Test Mode, USSD transactions will automatically be paid (transition to "successful") after a few seconds.
We'll send you a webhook notification when the payment is completed. Here's what that looks like:
{
"event": "charge.completed",
"data": {
"id": 2073992,
"tx_ref": "BJUYU399fcd43",
"flw_ref": "flwm3s4m0c1620380894041",
"device_fingerprint": "N/A",
"amount": 7500,
"currency": "NGN",
"charged_amount": 7500,
"app_fee": 2000,
"merchant_fee": 0,
"processor_response": "Transaction Successful",
"auth_model": "USSD",
"ip": "::ffff:10.30.86.54",
"narration": "MerchantName",
"status": "successful",
"payment_type": "ussd",
"created_at": "2020-05-31T00:17:51.000Z",
"account_id": 17321,
"meta": null,
"customer":{
"id": 841600,
"name": "Homer Simpson",
"phone_number": "N/A",
"email": "chunkylover53@aol.com",
"created_at":"2021-05-07T09:48:13.000Z"
}
}
}
In your webhook handler, you can then verify the payment and credit your customer with whatever they paid for. See our guide to transaction verification for details.
const transactionId = payload.data.id;
await flw.Transaction.verify({ id: transactionId });
$transactionId = $payload['data']['id'];
$ussdChargeService->verifyTransaction($transactionId);
transaction_id = params[:data][:id]
charge_ussd.verify_charge transaction_id
You'll get a response that looks like this, and you can see that the data.status
field is now "successful"
.
{
"status": "success",
"message": "Transaction fetched successfully",
"data": {
"id": 276641033,
"tx_ref": "BJUYU399fcd43",
"flw_ref": "FLW693741590884271975",
"device_fingerprint": "N/A",
"amount": 7500,
"charged_amount": 7500,
"app_fee": 2000,
"merchant_fee": 0,
"processor_response": "Transaction Successful",
"auth_model": "USSD",
"currency": "NGN",
"ip": "::ffff:10.30.86.54",
"narration": "MerchantName",
"status": "successful",
"payment_type": "ussd",
"created_at": "2020-05-31T00:17:51.000Z",
"account_id": 17321,
"customer":{
"id": 841600,
"name": "Homer Simpson",
"phone_number": "N/A",
"email": "chunkylover53@aol.com",
"created_at":"2021-05-07T09:48:13.000Z"
},
"meta": null,
"amount_settled": 5500
}
}
All done! 🎉🎉
Putting it together
Here's what a full implementation might look like:
// In an Express-like app:
// The route where we initiate payment
app.post('/pay', async (req, res) => {
const payload = {
account_bank: req.body.bank_code,
amount: req.session.amount,
currency: 'NGN',
email: req.user.email,
tx_ref: generateTransactionReference(),
fullname: req.user.name,
}
const response = await flw.Charge.ussd(payload);
if (response.status !== 'success') {
// Something went wrong; bail
res.redirect('back');
}
const ussdCode = response.meta.authorization.note;
const paymentCode = response.payment_code;
res.send(`
To complete the payment, dial <strong>${ussdCode}</strong> from the mobile number linked to your bank account.
If you're prompted for a payment code, enter <strong>${paymentCode}</strong>.
`);
});
// Our webhook endpoint. See the guide on webhooks for a more thorough sample
app.post("/flw-webhook", (req, res) => {
// Check signature, log payload, etc
// Schedule a job that checks for the status of the payment
transactionVerificationQueue.add({id: req.body.data.id});
res.status(200).end();
});
// In a Laravel-like app:
// The route where we initiate payment
Route::post('/pay', function ($req) {
$payload = [
"account_bank" => $req->input('bank_code'),
"amount" => session('amount'),
"currency" => 'NGN',
"email" => auth()->email,
"tx_ref" => generateTransactionReference(),
"fullname" => auth()->name,
];
$response = $ussdChargeService->ussd($payload);
if ($response['status'] !== 'success') {
// Something went wrong; bail
return redirect()->back();
}
$ussdCode = $response['meta']['authorization']['note'];
$paymentCode = $response['payment_code'];
return <<<HTML
To complete the payment, dial <strong>$ussdCode</strong> from the mobile number linked to your bank account.
If you're prompted for a payment code, enter <strong>$paymentCode</strong>.
HTML;
});
// Our webhook endpoint. See the guide on webhooks for a more thorough sample
Route::post('/flw-webhook', function ($req) {
// Check signature, log payload, etc
// Schedule a job that checks for the status of the payment
dispatch(new CheckTransactionStatus($req->input('data')['id']);
return response();
});
# In a Rails-like app:
# Handles POST /pay
# The route where we initiate payment
def pay
payload = {
account_bank: params[:bank_code],
amount: session[:amount],
currency: 'NGN',
email: current_user.email,
fullname: current_user.name,
tx_ref: generate_transaction_reference,
}
response = charge_ussd.initiate_charge payload
if response['status'] != 'success'
# Something went wrong; bail
return redirect_back
end
ussd_code = response['meta']['authorization']['note']
payment_code = response['payment_code']
render html: <<~HTML
To complete the payment, dial <strong>#{ussd_code}</strong> from the mobile number linked to your bank account.
If you're prompted for a payment code, enter <strong>#{payment_code}</strong>.
HTML
end
# Our webhook endpoint. See the guide on webhooks for a more thorough sample
def flw_webhook
# Check signature, log payload, etc
# Schedule a job that checks for the status of the payment
CheckTransactionStatus.perform_later params[:data][:id]
head :ok
end