Setup Mpesa SDK Push with Ruby On Rails
After many hours of researching for a gem or article explaining the process of intergrating M-Pesa SDK Push to ruby on rails but kept on hitting multiple dead ends. I decided to do create a Rails mini app api to demo the basics of a simple SDK Push and polling (checking if payment is successfull or might have encounted any errors)
Can Access the Code here -> https://github.com/layersony/ruby-on-rails-mpesa-payment-blog
Requirements
- M-Pesa Daraja Account and a sandbox app Create one here
- Ruby and Ruby on Rails
- Ngrok download here
- Ruby Gems -> rest-client 2.0 and rack-cors
Create M-Pesa Sandbox App
Navigate to https://developer.safaricom.co.ke/MyApps
Click on create New app
Make sure you give your sandbox app a name then tick all the checkboxes
After your App has being created, Click on the show Key/Secret to get the Consumer Key and Consumer Secret
Create Ruby on Rails M-Pesa Mini Api
Create a mininal Rails app
rails new ROR_Mpesa — api — minimal
Create a payment resource
rails g resource Mpesa checkoutRequestID merchantRequestID amount mpesaReceiptNumber phoneNumber — no-test-framework
then,
rails db:migrate db:seed
Open your favorite Code Editior and lets start coding
Basic Configuration
Lets start with basic configs
Navigate to config/environments/development.rb and add the following code. this will enable you have dynamic urls
config.hosts << /[a-z0–9-.]+\.ngrok\.io/
then navigate to config/initializrs/cors.rb and replace with this code
Config environment variables
Inside config folder create a file called local_env.yml and set your secrets in there. DO NOT FORGET to add the local_env.yml to your .gitignore
to access the secrets use ENV[“MPESA_SHORTCODE”]
But we need rails to know where our secrets are so add the following code to config/application.rb inside class Application < Rails::Application
config.before_configuration doenv_file = File.join(Rails.root, 'config', 'local_env.yml')YAML.load(File.open(env_file)).each do |key, value|ENV[key.to_s] = valueend if File.exists?(env_file)end
Full module should look like this
The Main Code
Navigate to mpesas_controller.rb and lets create the following functions get_access_token, generate_access_token_request, stkpush, callback_url
Lets start with Private functions
Generate Access Token Request -> Gives you a time bound access token to call allowed APIs it provides you with an access token thou it returns the response form api
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
Create a rails model for access_token
rails g model AccessToken token — no-test-framework
First import rest-client inside MpesasContoller in the first line
require ‘rest-client’
Private functions
generate_access_token_request
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}"})resend
get_access_token
def get_access_tokenres = generate_access_token_request()if res.code != 200r = generate_access_token_request()if res.code != 200raise MpesaError('Unable to generate access token')endendbody = JSON.parse(res, { symbolize_names: true })token = body[:access_token]AccessToken.destroy_all()AccessToken.create!(token: token)tokenend
Let’s Create the STKPush function
Do use the mpesa daraja express located in API’s for more information
stkpush -> will break it down in various parts
- Payload
Phonenumber and amount should be provided for this is will be Post request
timestamp -> should be in this format “%Y%m%d%H%M%S”
password -> should be encoded with base64 format (business_short_code + mpesa_passkey+timestamp)
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': 'Trial ROR Mpesa','TransactionDesc': "ROR trial"}.to_json
2. Headers
headers = {content_type: 'application/json',Authorization:"Bearer #{get_access_token}"}
3. Send Response
response = RestClient::Request.new({method: :post,url: url,payload: payload,headers: headers}).execute do |response, request|case response.codewhen 500[ :error, JSON.parse(response.to_str) ]when 400[ :error, JSON.parse(response.to_str) ]when 200[ :success, JSON.parse(response.to_str) ]elsefail "Invalid response #{response.to_str} received."endendrender json: response
Full function should look this
Create Route for the functions
navigate to config/routes
Fire up you ngrok using this command
ngrok http http://127.0.0.1:3000
Call the stkpush url using the https://…… link
Open up Insomia or Postman, create a new request and the request method should be Post.
the data should be in Json format
{
“phoneNumber”:”2547XXXXXXXXX”,
“amount”:”1"
}
When request is send an SDK Push Prompt is sent to phoneNumber provided above
The response after the request is
[
"success",
{
"MerchantRequestID": "18552-53113685-1",
"CheckoutRequestID": "ws_CO_30102022195943475748681351",
"ResponseCode": "0",
"ResponseDescription": "Success. Request accepted for processing",
"CustomerMessage": "Success. Request accepted for processing"
}
]
Confirm Payment Using Mpesa
After one has paid you can use the mpesa query api to check if the payment was successful or not
Navigate to mpesas_controller and add the following code
def polling_payment # Check if payment has been paid checkoutId = params[:checkoutId] 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}") url = ENV['QUERY_URL'] headers = { content_type: 'application/json', Authorization: "Bearer #{get_access_token}" } payload = { 'BusinessShortCode' => business_short_code, 'Password' => password, 'Timestamp' => timestamp, 'CheckoutRequestID' => checkoutId }.to_json
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: { msg: response }end
Then create a route for payment polling
Switch to Postman or Insomnia and create a Post request
required data for this endpoint is CheckoutRequestID (retrive this from the stkpush response)
"CheckoutRequestID": "ws_CO_30102022195943475748681351"
Json format should be
{
"checkoutRequestID":"ws_CO_30102022195943475748681351"
}
The Response should be
[
"success",
{
"ResponseCode": "0",
"ResponseDescription": "The service request has been accepted successsfully",
"MerchantRequestID": "18552-53113685-1",
"CheckoutRequestID": "ws_CO_30102022195943475748681351",
"ResultCode": "0",
"ResultDesc": "The service request is processed successfully."
}
]
You can use ResultDesc as a message prompt for your Client
NOTES:
You can access the Code Here
Remember to hide you Daraja Consumer Key and Secret
Do Reach Out for more Information, Question or Feedback concerning the above topic