Skip to content

Setup Mpesa STK Push and STK Push Query with Ruby On Rails APIs

License

Notifications You must be signed in to change notification settings

Felix-Barosio/Mpesa-ROR

Repository files navigation

Setup for M-pesa stk push and query on Rails APIs.

Rails App API to demo the basics of a simple stk push and stk push query (checking if payment is successfull or might have encounted any errors) on M-pesa.

Setup

  • In order to implement it, install the following:

Daraja Safaricom Developers Portal

  • Get started by login in or create Daraja Developer free Account.
  • On apps tab, create a new sandbox app and give it your preferd name. Proceed to 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. This will be crucial later during setup.
  • Navigate to the APIs tab and on M-pesa Express click on Simulate, on the input prompt select the app you just created.
  • Scroll down and click on test credentials. The initiator password and passkey will crucial also later.

Ngrok

  • Login or create ngrok free account.
  • Install on ubuntu by sudo snap install ngrok or download from website.
  • Connect your account to ngrok run ngrok authtoken <your authtoken>.

Rails

  • Create a new rails app rails new <name> --api --minimal.
  • Install or Add Gems below.
gem 'rest-client'
gem 'rack-cors'
  • Run bundle install.

  • Create M-pesa resource. Run rails g resource Mpesa phoneNumber amount checkoutRequestID merchantRequestID mpesaReceiptNumber --no-test-framework.

  • Also add Access Token. Run rails g model AccessToken token --no-test-framework.

  • 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, :put, :patch, :delete, :options, :head]
  end

end

Be sure to replace origins 'example.com' with origins '*' if you uncomment existing code.

Config Environment Variables

  • Inside the config folder create a file config/local_env.yml and add the following code.
MPESA_CONSUMER_KEY: "<your consumer key>"
MPESA_CONSUMER_SECRET: "<your consumer secret>"
MPESA_SHORTCODE: "<your mpesa shortcode>"
MPESA_INITIATOR_NAME: "testapi"
MPESA_PASSKEY: "<your passkey>"
MPESA_INITIATOR_PASSWORD: "<your initiator password>"
CALLBACK_URL: "<your ngrok url>"
REGISTER_URL: "https://sandbox.safaricom.co.ke/mpesa/c2b/v1/registerurl"

IMPORTANT: Add the local_env.yml to the .gitignore file to hide your Daraja Consumer Key and Secret.

Note about the CALLBACK_URL.

  • To aquire callback url, run your rails server rails s and copy the url http://127.0.0.1:3000 from the terminal.

  • Open a new terminal for ngrok and run ngrok http <port number> and replace <port number> with the port number from your rails server http://127.0.0.1:3000 hence generating a url that you can use as your callback_url.

  • My example: ngrok http http://127.0.0.1:3000 and the url generated is https://d0e6-154-154-16-76.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 ngrok url, to open the link, click on visit site which should take you to your rails app. If you get a Blocked Host Error, check out this stackoverflow solutions.

  • The solution that worked for my test is to replace config.hosts << /[a-z0-9]+\.ngrok\.io/ with config.hosts.clear in config/environments/development.rb, though this is not recommended for production.

  • To load rails on our environment variables, 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

Implementing The Main Code.

  • To get started, we start with the 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 it provides you with an access token.

  • 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 this code to app/controllers/mpesas_controller.rb.

  • First add rest-client gem.

require 'rest-client'

generate_access_token_request

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

get_access_token

private

...

    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

  • On 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.
  payload = {
    BusinessShortCode --> The organization shortcode used to receive the transaction.
    Password --> Should be encoded with base64 format (business_short_code + mpesa_passkey+timestamp)
    Timestamp --> The timestamp of the transaction in the format “%Y%m%d%H%M%S”
    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.
  }
  • Read more on the 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': 'Mpesa_ROR',
            'TransactionDesc': "Payment for Mpesa_ROR"
        }.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 config/routes.rb and add this code.
post 'stkpush', to: 'mpesas#stkpush'
  • Open up Postman or Insomia or Thunder Client, create a new POST /stkpush request to your ngrok url with the following parameters.
{
  "phoneNumber": "2547xxxxxxxx",
  "amount": "1"
}
  • When request is sent, an STK Push Prompt is sent to phoneNumber provided above. The response should look like this.
[
  "success",
  {
    "MerchantRequestID": "xxxx-xxxx-xxxx-xxxx",
    "CheckoutRequestID": "ws_CO_XXXXXXXXXXXXXXXXXXXXXXXXXX",
    "ResponseCode": "0",
    "ResponseDescription": "Success. Request accepted for processing",
    "CustomerMessage": "Success. Request accepted for processing"
  }
]

STK push query request

  • After one has paid, you can use the mpesa query api to check if the payment was successful or not
  • On APIs ---> M-pesa Express you can simulate a query, a stk quesry push request by selecting your app and inputing the CheckoutRequestID you got from the previous step.
  payload = {
    BusinessShortCode --> The organization shortcode used to receive the transaction.
    Password --> Should be encoded with base64 format (business_short_code + mpesa_passkey+timestamp).
    Timestamp --> The timestamp of the transaction in the format “%Y%m%d%H%M%S”
    CheckoutRequestID --> The CheckoutRequestID used to identify the m-pesa transaction.
  }
  • Add the following code to app/controllers/mpesas_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,
             # Check if payment has been paid
            '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 config/routes.rb and add this code.
post 'stkquery', to: 'mpesas#stkquery'
  • Open up Postman or Insomia or Thunder Client, create a new POST /stkquery request to your ngrok url with the following parameters.
{
  "checkoutRequestID": "ws_CO_XXXXXXXXXXXXXXXXXXXXXXXXXX"
}
  • Response looks like this:
[
  "success",
  {
    "ResponseCode": "0",
    "ResponseDescription": "The service request has been accepted successsfully",
    "MerchantRequestID": "xxxx-xxxx-xxxxxxxxx-x",
    "CheckoutRequestID": "ws_CO_XXXXXXXXXXXXXXXXXXXXXXXXXX",
    "ResultCode": "0",
    "ResultDesc": "The service request is processed successfully."
  }
]

You can use ResultDesc as a message prompt for your Client.

  • The resulting Full Code on app/controllers/mpesas_controller.rb.
class MpesasController < ApplicationController

    require 'rest-client'

    # stkpush
    # make payment

    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': 'Mpesa_ROR',
            'TransactionDesc': "Payment for Mpesa_ROR"
        }.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
    # confirm if payment is gone through

    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,
             # Check if payment has been paid
            '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 config/routes.rb should look like this.
Rails.application.routes.draw do
  resources :mpesas
  post 'stkpush', to: 'mpesas#stkpush'
  post 'stkquery', to: 'mpesas#stkquery'
end
  • The full code can be accessed here from my repository.

N/B - Remember to add the your local_env.yml file in .gitignore.

Author Info

About

Setup Mpesa STK Push and STK Push Query with Ruby On Rails APIs

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages