Setup
Daraja
- To get started head over to daraja and sign up for a developer account or login if you already have one.
- On my apps tab, create a new sandbox app and name it whatever you want, then tick all the check boxes and click on create app.
- You will be redirected to the app details page where you will find your consumer key and consumer secret.
- Save these somewhere safe as you will need them later.
- Navigate to the APIs tab and on M-pesa Express click on Simulate, on the input prompt select the app you just created.
- This will auto populate some fields for you, you can leave them as they are.
- Scroll down and click on test credentials.
- Save your initiator password and passkey somewhere safe as you will need them later.
Ngrok
- Go to ngrok and sign up for a free account or login if you already have one.
- To install on ubuntu
sudo snap install ngrok
or download the zip file from the website and extract it. - To connect your account to ngrok run
ngrok authtoken <your authtoken>
and replace with your authtoken. - We will get back to ngrok later, let's first setup our rails app.
Rails
- Create a new rails app
rails new <app-name> --api
in my caserails new daraja-test --api
. - Add the following gems to your gemfile and run
bundle install
.
gem 'rack-cors'
gem 'rest-client'
- We need to create an M-pesa resource, all datatypes are strings.
- Run
rails g resource Mpesa phoneNumber amount checkoutRequestID merchantRequestID mpesaReceiptNumber
. - We also need a model for the access token, run
rails g model AccessToken token
. - Run
rails db:migrate
Configurations
- Navigate to config/environments/development.rb and add the following code.
config.hosts << /[a-z0-9]+\.ngrok\.io/
- This will allow us to access our rails app from ngrok.
- Navigate to config/initializers/cors.rb and add the following code or uncomment the existing code.
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*', headers: :any, methods: [:get, :post, :options]
end
end
- Be sure to replace origins 'example.com' with origins '*'if you uncomment existing code instead of adding the above code.
Environment variables
- Inside the config folder create a file called local_env.yml and add the following code.
MPESA_CONSUMER_KEY: '<your consumer key>'
MPESA_CONSUMER_SECRET: '<your consumer secret>'
MPESA_PASSKEY: '<your passkey>'
MPESA_SHORTCODE: '174379'
MPESA_INITIATOR_NAME: 'testapi'
MPESA_INITIATOR_PASSWORD: '<your initiator password>'
CALLBACK_URL: '< your ngrok url>'
REGISTER_URL: "https://sandbox.safaricom.co.ke/mpesa/c2b/v1/registerurl"
** Note about the CALLBACK_URL **
- To get you callback url first run your rails server
rails s
and copy the url from the terminal. - Then navigate to a new terminal and run
ngrok http <port number>
and replace with the port number from your rails server. - This will generate a url that you can use as your callback url.
- In my case above
ngrok http://127.0.0.1:3000
orngrok http 3000
the url generated washttps://5d5b-105-161-115-83.in.ngrok.io
- Note that the url generated by ngrok changes every time you run it, so you will need to update your local_env.yml file with the new url every time you run ngrok.
- Navigate to the ngrok url, you should see the page below, click on visit site which should take you to your rails app.
- If you get a Blocked Host error, check these stackoverflow solutions.
- In my case I had to replace
config.hosts << /[a-z0-9]+\.ngrok\.io/
withconfig.hosts.clear
in config/environments/development.rb. This however is not recommended for production. - Remember to add your local_env.yml file to your .gitignore file.
- We need rails to load our environment variables, to do this add the following code to config/application.rb.
config.before_configuration do
env_file = File.join(Rails.root, 'config', 'local_env.yml')
YAML.load(File.open(env_file)).each do |key, value|
ENV[key.to_s] = value
end if File.exists?(env_file)
end
- Wheeww!! That was a lot of configurations, let's now implement the code.
Implementing the code
- First we need to write private methods to generate and get an access token from the Authorization API.
- Generate Access Token Request -> Gives you a time bound access token to call allowed APIs in the sandbox.
- Get Access Token -> Used to check if generate_acces_token_request is successful or not then it reads the responses and extracts the access token from the response and saves it to the database.
- Add the following code to app/controllers/mpesa_controller.rb.
- First require the rest-client gem
require 'rest-client'
then add the following code.
private
def generate_access_token_request
@url = "https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials"
@consumer_key = ENV['MPESA_CONSUMER_KEY']
@consumer_secret = ENV['MPESA_CONSUMER_SECRET']
@userpass = Base64::strict_encode64("#{@consumer_key}:#{@consumer_secret}")
headers = {
Authorization: "Bearer #{@userpass}"
}
res = RestClient::Request.execute( url: @url, method: :get, headers: {
Authorization: "Basic #{@userpass}"
})
res
end
def get_access_token
res = generate_access_token_request()
if res.code != 200
r = generate_access_token_request()
if res.code != 200
raise MpesaError('Unable to generate access token')
end
end
body = JSON.parse(res, { symbolize_names: true })
token = body[:access_token]
AccessToken.destroy_all()
AccessToken.create!(token: token)
token
end
Stk Push Request
- Under APIs -> M-pesa Express you can simulate a stk push request by selecting your app and changing Party A and Phone Number to your phone number.
- Looking at the JSON the request body has the following parameters; { BusinessShortCode - The organization shortcode used to receive the transaction. Password - The password for encrypting the request.(Base64 encoded string,a combination of your BusinessShortCode, Passkey and Timestamp) Timestamp - The timestamp of the transaction in the format yyyymmddhhiiss TransactionType - The type of transaction (CustomerPayBillOnline or CustomerBuyGoodsOnline) Amount - The amount being transacted PartyA - The phone number sending the money. PartyB - The organization shortcode receiving the funds.Can be the same as the business shortcode. PhoneNumber - The mobile number to receive the STK push.Can be the same as Party A. CallBackURL - The url to where responses from M-Pesa will be sent to. Should be valid and secure. AccountReference - Value displayed to the customer in the STK Pin prompt message. TransactionDesc - A description of the transaction. }
- You can read more on their documentation -> Lipa Na M-pesa Online API -> Request Parameter Definition.
- Add the following code to app/controllers/mpesa_controller.rb.
def stkpush
phoneNumber = params[:phoneNumber]
amount = params[:amount]
url = "https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest"
timestamp = "#{Time.now.strftime "%Y%m%d%H%M%S"}"
business_short_code = ENV["MPESA_SHORTCODE"]
password = Base64.strict_encode64("#{business_short_code}#{ENV["MPESA_PASSKEY"]}#{timestamp}")
payload = {
'BusinessShortCode': business_short_code,
'Password': password,
'Timestamp': timestamp,
'TransactionType': "CustomerPayBillOnline",
'Amount': amount,
'PartyA': phoneNumber,
'PartyB': business_short_code,
'PhoneNumber': phoneNumber,
'CallBackURL': "#{ENV["CALLBACK_URL"]}/callback_url",
'AccountReference': 'Codearn',
'TransactionDesc': "Payment for Codearn premium"
}.to_json
headers = {
Content_type: 'application/json',
Authorization: "Bearer #{get_access_token}"
}
response = RestClient::Request.new({
method: :post,
url: url,
payload: payload,
headers: headers
}).execute do |response, request|
case response.code
when 500
[ :error, JSON.parse(response.to_str) ]
when 400
[ :error, JSON.parse(response.to_str) ]
when 200
[ :success, JSON.parse(response.to_str) ]
else
fail "Invalid response #{response.to_str} received."
end
end
render json: response
end
- Navigate to routes.rb and add the following code.
post 'stkpush', to: 'mpesas#stkpush'
- Open postman and make a post request to your ngrok-url/stkpush with the following parameters.
{
"phoneNumber": "2547xxxxxxxx",
"amount": "1"
}
- The request sents an STK push to the phone number provided.
- Your response should look like this.
{
"MerchantRequestID": "xxxx-xxxx-xxxx-xxxx",
"CheckoutRequestID": "ws_CO_XXXXXXXXXXXXXXXXXXXXXXXXX",
"ResponseCode": "0",
"ResponseDescription": "Success. Request accepted for processing",
"CustomerMessage": "Success. Request accepted for processing"
}
- Save the CheckoutRequestID for the next step.
Stk Query Request
- We can use the mpesa query to check if the payment was successful or not.
- Under APIs -> M-pesa Express you can simulate a query a stk push request by selecting your app and inputing the CheckoutRequestID you got from the previous step.
- The request body has the following parameters; { BusinessShortCode - The organization shortcode used to receive the transaction. Password - The password for encrypting the request.(Base64 encoded string,a combination of your BusinessShortCode, Passkey and Timestamp) Timestamp - The timestamp of the transaction in the format yyyymmddhhiiss CheckoutRequestID - The CheckoutRequestID used to identify the transaction on M-Pesa. }
- Add the following code to app/controllers/mpesa_controller.rb.
def stkquery
url = "https://sandbox.safaricom.co.ke/mpesa/stkpushquery/v1/query"
timestamp = "#{Time.now.strftime "%Y%m%d%H%M%S"}"
business_short_code = ENV["MPESA_SHORTCODE"]
password = Base64.strict_encode64("#{business_short_code}#{ENV["MPESA_PASSKEY"]}#{timestamp}")
payload = {
'BusinessShortCode': business_short_code,
'Password': password,
'Timestamp': timestamp,
'CheckoutRequestID': params[:checkoutRequestID]
}.to_json
headers = {
Content_type: 'application/json',
Authorization: "Bearer #{ get_access_token }"
}
response = RestClient::Request.new({
method: :post,
url: url,
payload: payload,
headers: headers
}).execute do |response, request|
case response.code
when 500
[ :error, JSON.parse(response.to_str) ]
when 400
[ :error, JSON.parse(response.to_str) ]
when 200
[ :success, JSON.parse(response.to_str) ]
else
fail "Invalid response #{response.to_str} received."
end
end
render json: response
end
- Navigate to routes.rb and add the following code.
post 'stkquery', to: 'mpesas#stkquery'
- Open postman and make a post request to your ngrok-url/stkquery with the following parameters.
{
"checkoutRequestID": "ws_CO_XXXXXXXXXXXXXXXXXXXXXXXXX"
}
- Your response should look like this.
[
"success",
{
"ResponseCode": "0",
"ResponseDescription": "The service request has been accepted successsfully",
"MerchantRequestID": "8491-75014543-2",
"CheckoutRequestID": "ws_CO_12122022094855872768372439",
"ResultCode": "1032",
"ResultDesc": "Request cancelled by user"
}
]
- You can use the ResultCode to check if the payment was successful or not.
- Your mpesas_controller.rb should look like this.
class MpesasController < ApplicationController
require 'rest-client'
# stkpush
def stkpush
phoneNumber = params[:phoneNumber]
amount = params[:amount]
url = "https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest"
timestamp = "#{Time.now.strftime "%Y%m%d%H%M%S"}"
business_short_code = ENV["MPESA_SHORTCODE"]
password = Base64.strict_encode64("#{business_short_code}#{ENV["MPESA_PASSKEY"]}#{timestamp}")
payload = {
'BusinessShortCode': business_short_code,
'Password': password,
'Timestamp': timestamp,
'TransactionType': "CustomerPayBillOnline",
'Amount': amount,
'PartyA': phoneNumber,
'PartyB': business_short_code,
'PhoneNumber': phoneNumber,
'CallBackURL': "#{ENV["CALLBACK_URL"]}/callback_url",
'AccountReference': 'Codearn',
'TransactionDesc': "Payment for Codearn premium"
}.to_json
headers = {
Content_type: 'application/json',
Authorization: "Bearer #{get_access_token}"
}
response = RestClient::Request.new({
method: :post,
url: url,
payload: payload,
headers: headers
}).execute do |response, request|
case response.code
when 500
[ :error, JSON.parse(response.to_str) ]
when 400
[ :error, JSON.parse(response.to_str) ]
when 200
[ :success, JSON.parse(response.to_str) ]
else
fail "Invalid response #{response.to_str} received."
end
end
render json: response
end
# stkquery
def stkquery
url = "https://sandbox.safaricom.co.ke/mpesa/stkpushquery/v1/query"
timestamp = "#{Time.now.strftime "%Y%m%d%H%M%S"}"
business_short_code = ENV["MPESA_SHORTCODE"]
password = Base64.strict_encode64("#{business_short_code}#{ENV["MPESA_PASSKEY"]}#{timestamp}")
payload = {
'BusinessShortCode': business_short_code,
'Password': password,
'Timestamp': timestamp,
'CheckoutRequestID': params[:checkoutRequestID]
}.to_json
headers = {
Content_type: 'application/json',
Authorization: "Bearer #{ get_access_token }"
}
response = RestClient::Request.new({
method: :post,
url: url,
payload: payload,
headers: headers
}).execute do |response, request|
case response.code
when 500
[ :error, JSON.parse(response.to_str) ]
when 400
[ :error, JSON.parse(response.to_str) ]
when 200
[ :success, JSON.parse(response.to_str) ]
else
fail "Invalid response #{response.to_str} received."
end
end
render json: response
end
private
def generate_access_token_request
@url = "https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials"
@consumer_key = ENV['MPESA_CONSUMER_KEY']
@consumer_secret = ENV['MPESA_CONSUMER_SECRET']
@userpass = Base64::strict_encode64("#{@consumer_key}:#{@consumer_secret}")
headers = {
Authorization: "Bearer #{@userpass}"
}
res = RestClient::Request.execute( url: @url, method: :get, headers: {
Authorization: "Basic #{@userpass}"
})
res
end
def get_access_token
res = generate_access_token_request()
if res.code != 200
r = generate_access_token_request()
if res.code != 200
raise MpesaError('Unable to generate access token')
end
end
body = JSON.parse(res, { symbolize_names: true })
token = body[:access_token]
AccessToken.destroy_all()
AccessToken.create!(token: token)
token
end
end
- Your routes.rb should look like this.
Rails.application.routes.draw do
post 'stkpush', to: 'mpesas#stkpush'
post 'stkquery', to: 'mpesas#stkquery'
end
I followed this tutorial and run into some errors and decided to write this article with some more clear steps.
The full code for this here.
I hope you found this helpful. If you have any questions, feel free to reach out to me on email: annetotoh@gmail.com.
THANK YOU!
Top comments (4)
Thanks for sharing this article. For those getting the block host error, for the free tier of ngrok, you should use this in config/environments/development.rb:
This was really helpful! Thank you.
Nice Article
Hello, nice article... however i have some question... if i deploy my api will i still use the ngrok url or will i use the api url