{"id":19360051,"url":"https://github.com/annastacia-dev/daraja-test","last_synced_at":"2025-04-23T11:33:01.211Z","repository":{"id":138768737,"uuid":"574813206","full_name":"Annastacia-dev/daraja-test","owner":"Annastacia-dev","description":"Implementing m-pesa stk push and query","archived":false,"fork":false,"pushed_at":"2024-08-30T09:17:32.000Z","size":624,"stargazers_count":18,"open_issues_count":4,"forks_count":6,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-02T15:04:09.290Z","etag":null,"topics":["mpesa","mpesa-api","mpesa-payments","mpesa-sdk","rails","rails-api","ruby"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Annastacia-dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-12-06T05:57:40.000Z","updated_at":"2024-08-30T09:17:35.000Z","dependencies_parsed_at":"2024-05-15T21:05:34.634Z","dependency_job_id":null,"html_url":"https://github.com/Annastacia-dev/daraja-test","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Annastacia-dev%2Fdaraja-test","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Annastacia-dev%2Fdaraja-test/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Annastacia-dev%2Fdaraja-test/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Annastacia-dev%2Fdaraja-test/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Annastacia-dev","download_url":"https://codeload.github.com/Annastacia-dev/daraja-test/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250425618,"owners_count":21428590,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["mpesa","mpesa-api","mpesa-payments","mpesa-sdk","rails","rails-api","ruby"],"created_at":"2024-11-10T07:16:58.404Z","updated_at":"2025-04-23T11:33:00.520Z","avatar_url":"https://github.com/Annastacia-dev.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Implementing M-pesa STK push and STK push query APIs in Rails API\n### Setup\n#### Daraja\n- To get started head over to [daraja](https://developer.safaricom.co.ke/) and sign up for a developer account or login if you already have one.\n- 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.\n- You will be redirected to the app details page where you will find your consumer key and consumer secret.\n- Save these somewhere safe as you will need them later.\n- Navigate to the APIs tab and on M-pesa Express click on Simulate, on the input prompt select the app you just created.\n- This will autopopulate some fields for you, you can leave them as they are.\n- Scroll down and click on test credentials.\n- Save your initiator password and passkey somewhere safe as you will need them later.\n\n#### Ngrok\n- Go to [ngrok](https://ngrok.com/) and sign up for a free account or login if you already have one.\n- To install on ubuntu ``` sudo snap install ngrok ``` or download the zip file from the website and extract it.\n- To connect your account to ngrok run ``` ngrok authtoken \u003cyour authtoken\u003e ``` and replace \u003cyour authtoken\u003e with your authtoken.\n- We will get back to ngrok later, let's first setup our rails app.\n\n#### Rails\n- Create a new rails app ``` rails new \u003capp-name\u003e --api ``` in my case ``` rails new daraja-test --api  ```.\n- Add the following gems to your gemfile and run ``` bundle install ```.\n\n```ruby\ngem 'rack-cors'\ngem 'rest-client'\n```\n- We need to create an M-pesa resource, all datatypes are strings.\n- Run ``` rails g resource Mpesa phoneNumber amount checkoutRequestID  merchantRequestID mpesaReceiptNumber ```.\n- We also need a model for the access token, run ``` rails g model AccessToken token```.\n- Run ``` rails db:migrate ``` \n\n#### Configurations\n- Navigate to config/environments/development.rb and add the following code.\n```ruby\nconfig.hosts \u003c\u003c /[a-z0-9]+\\.ngrok\\.io/\n```\n- This will allow us to access our rails app from ngrok.\n- Navigate to config/initializers/cors.rb and add the following code or uncomment the existing code.\n```ruby\nRails.application.config.middleware.insert_before 0, Rack::Cors do\n  allow do\n    origins '*'\n    resource '*', headers: :any, methods: [:get, :post, :options]\n  end\nend\n```\n- Be sure to replace origins 'example.com' with origins '*'if you uncomment existing code instead of adding the above code.\n\n#### Environment variables\n- Inside the config folder create a file called local_env.yml and add the following code.\n```ruby\nMPESA_CONSUMER_KEY: '\u003cyour consumer key\u003e'\nMPESA_CONSUMER_SECRET: '\u003cyour consumer secret\u003e'\nMPESA_PASSKEY: '\u003cyour passkey\u003e'\nMPESA_SHORT_CODE: '174379'\nMPESA_INITIATOR_NAME: 'testapi'\nMPESA_INITIATOR_PASSWORD: '\u003cyour initiator password\u003e'\nCALLBACK_URL: '\u003c your ngrok url\u003e'\nREGISTER_URL: \"https://sandbox.safaricom.co.ke/mpesa/c2b/v1/registerurl\"\n```\n** Note about the CALLBACK_URL **\n- To get you callback url first run your rails server ``` rails s ``` and copy the url from the terminal.\n- Then go to ngrok and run ``` ngrok http \u003cport number\u003e ``` and replace \u003cport number\u003e with the port number from your rails server.\n- This will generate a url that you can use as your callback url.\n- In my case above ``` ngrok http http://127.0.0.1:3000 ``` and the url generated was ``` https://5d5b-105-161-115-83.in.ngrok.io```\n- ***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.***\n- Navigate to the ngrok url, you should see the page below, click on visit site which should take you to your rails app.\n- If you get a Blocked Host error, check these [stackoverflow](https://stackoverflow.com/questions/53878453/upgraded-rails-to-6-getting-blocked-host-error) solutions.\n- In my case I had to replace ``` config.hosts \u003c\u003c /[a-z0-9]+\\.ngrok\\.io/ ``` with ``` config.hosts.clear``` in config/environments/development.rb. This however is not recommended for production.\n\n- Remember to add your local_env.yml file to your .gitignore file.\n\n- We need rails to load our environment variables, to do this add the following code to config/application.rb.\n```ruby\nconfig.before_configuration do\n  env_file = File.join(Rails.root, 'config', 'local_env.yml')\n  YAML.load(File.open(env_file)).each do |key, value|\n    ENV[key.to_s] = value\n  end if File.exists?(env_file)\nend\n```\n\n- Wheeww!! That was a lot of configurations, let's now implement the code.\n\n#### Implementing the code\n- First we need to write private methods to generate and get an access token from the Authorization API.\n- Generate Access Token Request -\u003e Gives you a time bound access token to call allowed APIs in the sandbox. \n- Get Access Token -\u003e 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.\n- Add the following code to app/controllers/mpesa_controller.rb.\n- First require the rest-client gem ``` require 'rest-client' ``` and add then following code.\n```ruby\nprivate\n\n    def generate_access_token_request\n        @url = \"https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials\"\n        @consumer_key = ENV['MPESA_CONSUMER_KEY']\n        @consumer_secret = ENV['MPESA_CONSUMER_SECRET']\n        @userpass = Base64::strict_encode64(\"#{@consumer_key}:#{@consumer_secret}\")\n        headers = {\n            Authorization: \"Bearer #{@userpass}\"\n        }\n        res = RestClient::Request.execute( url: @url, method: :get, headers: {\n            Authorization: \"Basic #{@userpass}\"\n        })\n        res\n    end\n\n    def get_access_token\n        res = generate_access_token_request()\n        if res.code != 200\n        r = generate_access_token_request()\n        if res.code != 200\n        raise MpesaError('Unable to generate access token')\n        end\n        end\n        body = JSON.parse(res, { symbolize_names: true })\n        token = body[:access_token]\n        AccessToken.destroy_all()\n        AccessToken.create!(token: token)\n        token\n    end\n```\n\n##### Stk Push Request\n- Under APIs -\u003e M-pesa Express you can [simulate](https://developer.safaricom.co.ke/APIs/MpesaExpressSimulate) a stk push request by selecting your app and changing Party A and Phone Number to your phone number.\n- Looking at the JSON the request body has the following parameters;\n{\n  BusinessShortCode - The organization shortcode used to receive the transaction.\n  Password - The password for encrypting the request.(Base64 encoded string,a combination of your BusinessShortCode, Passkey and Timestamp)\n  Timestamp - The timestamp of the transaction in the format yyyymmddhhiiss\n  TransactionType - The type of transaction (CustomerPayBillOnline or CustomerBuyGoodsOnline)\n  Amount - The amount being transacted\n  PartyA - The phone number sending the money.\n  PartyB - The organization shortcode receiving the funds.Can be the same as the business shortcode.\n  PhoneNumber - The mobile number to receive the STK push.Can be the same as Party A.\n  CallBackURL - The url to where responses from M-Pesa will be sent to. Should be valid and secure.\n  AccountReference - Value displayed to the customer in the STK Pin prompt message.\n  TransactionDesc - A description of the transaction.\n}\n- You can read more on their [documentation](https://developer.safaricom.co.ke/Documentation) -\u003e Lipa Na M-pesa Online API -\u003e Request Parameter Definition.\n- Add the following code to app/controllers/mpesa_controller.rb.\n```ruby\n def stkpush\n        phoneNumber = params[:phoneNumber]\n        amount = params[:amount]\n        url = \"https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest\"\n        timestamp = \"#{Time.now.strftime \"%Y%m%d%H%M%S\"}\"\n        business_short_code = ENV[\"MPESA_SHORTCODE\"]\n        password = Base64.strict_encode64(\"#{business_short_code}#{ENV[\"MPESA_PASSKEY\"]}#{timestamp}\")\n        payload = {\n        'BusinessShortCode': business_short_code,\n        'Password': password,\n        'Timestamp': timestamp,\n        'TransactionType': \"CustomerPayBillOnline\",\n        'Amount': amount,\n        'PartyA': phoneNumber,\n        'PartyB': business_short_code,\n        'PhoneNumber': phoneNumber,\n        'CallBackURL': \"#{ENV[\"CALLBACK_URL\"]}/callback_url\",\n        'AccountReference': 'Codearn',\n        'TransactionDesc': \"Payment for Codearn premium\"\n        }.to_json\n\n        headers = {\n        Content_type: 'application/json',\n        Authorization: \"Bearer #{access_token}\"\n        }\n\n        response = RestClient::Request.new({\n        method: :post,\n        url: url,\n        payload: payload,\n        headers: headers\n        }).execute do |response, request|\n        case response.code\n        when 500\n        [ :error, JSON.parse(response.to_str) ]\n        when 400\n        [ :error, JSON.parse(response.to_str) ]\n        when 200\n        [ :success, JSON.parse(response.to_str) ]\n        else\n        fail \"Invalid response #{response.to_str} received.\"\n        end\n        end\n        render json: response\n    end\n```\n- Navigate to routes.rb and add the following code.\n```ruby\npost 'stkpush', to: 'mpesa#stkpush'\n```\n- Open postman and make a post request to your ngrok url with the following parameters.\n```json\n{\n    \"phoneNumber\": \"2547xxxxxxxx\",\n    \"amount\": \"1\"\n}\n```\n- The request sents an STK push to the phone number provided.\n- Your response should look like this.\n```json\n{\n    \"MerchantRequestID\": \"xxxx-xxxx-xxxx-xxxx\",\n    \"CheckoutRequestID\": \"ws_CO_XXXXXXXXXXXXXXXXXXXXXXXXX\",\n    \"ResponseCode\": \"0\",\n    \"ResponseDescription\": \"Success. Request accepted for processing\",\n    \"CustomerMessage\": \"Success. Request accepted for processing\"\n}\n```\n- Save the CheckoutRequestID for the next step.\n\n#### Stk Query Request\n- We can use the mpesa query to check if the payment was successful or not.\n- Under APIs -\u003e M-pesa Express you can simulate a [query](https://developer.safaricom.co.ke/APIs/MpesaExpressSimulate) a stk push request by selecting your app and inputing the CheckoutRequestID you got from the previous step.\n- The request body has the following parameters;\n{\n  BusinessShortCode - The organization shortcode used to receive the transaction.\n  Password - The password for encrypting the request.(Base64 encoded string,a combination of your BusinessShortCode, Passkey and Timestamp)\n  Timestamp - The timestamp of the transaction in the format yyyymmddhhiiss\n  CheckoutRequestID - The CheckoutRequestID used to identify the transaction on M-Pesa.\n}\n- Add the following code to app/controllers/mpesa_controller.rb.\n```ruby\ndef stkquery\n        url = \"https://sandbox.safaricom.co.ke/mpesa/stkpushquery/v1/query\"\n        timestamp = \"#{Time.now.strftime \"%Y%m%d%H%M%S\"}\"\n        business_short_code = ENV[\"MPESA_SHORTCODE\"]\n        password = Base64.strict_encode64(\"#{business_short_code}#{ENV[\"MPESA_PASSKEY\"]}#{timestamp}\")\n        payload = {\n        'BusinessShortCode': business_short_code,\n        'Password': password,\n        'Timestamp': timestamp,\n        'CheckoutRequestID': params[:checkoutRequestID]\n        }.to_json\n\n        headers = {\n        Content_type: 'application/json',\n        Authorization: \"Bearer #{ access_token }\"\n        }\n\n        response = RestClient::Request.new({\n        method: :post,\n        url: url,\n        payload: payload,\n        headers: headers\n        }).execute do |response, request|\n        case response.code\n        when 500\n        [ :error, JSON.parse(response.to_str) ]\n        when 400\n        [ :error, JSON.parse(response.to_str) ]\n        when 200\n        [ :success, JSON.parse(response.to_str) ]\n        else\n        fail \"Invalid response #{response.to_str} received.\"\n        end\n        end\n        render json: response\n    end\n```\n- Navigate to routes.rb and add the following code.\n```ruby\npost 'stkquery', to: 'mpesa#stkquery'\n```\n- Open postman and make a post request to your ngrok url with the following parameters.\n```json\n{\n    \"checkoutRequestID\": \"ws_CO_XXXXXXXXXXXXXXXXXXXXXXXXX\"\n}\n```\n- Your response should look like this.\n```json\n[\n    \"success\",\n    {\n        \"ResponseCode\": \"0\",\n        \"ResponseDescription\": \"The service request has been accepted successsfully\",\n        \"MerchantRequestID\": \"8491-75014543-2\",\n        \"CheckoutRequestID\": \"ws_CO_12122022094855872768372439\",\n        \"ResultCode\": \"1032\",\n        \"ResultDesc\": \"Request cancelled by user\"\n    }\n]\n```\n- You can use the ResultCode to check if the payment was successful or not.\n- Your mpesas_controller.rb should look like this.\n```ruby\nclass MpesasController \u003c ApplicationController\n    \n    require 'rest-client'\n\n    # stkpush\n     def stkpush\n        phoneNumber = params[:phoneNumber]\n        amount = params[:amount]\n        url = \"https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest\"\n        timestamp = \"#{Time.now.strftime \"%Y%m%d%H%M%S\"}\"\n        business_short_code = ENV[\"MPESA_SHORTCODE\"]\n        password = Base64.strict_encode64(\"#{business_short_code}#{ENV[\"MPESA_PASSKEY\"]}#{timestamp}\")\n        payload = {\n        'BusinessShortCode': business_short_code,\n        'Password': password,\n        'Timestamp': timestamp,\n        'TransactionType': \"CustomerPayBillOnline\",\n        'Amount': amount,\n        'PartyA': phoneNumber,\n        'PartyB': business_short_code,\n        'PhoneNumber': phoneNumber,\n        'CallBackURL': \"#{ENV[\"CALLBACK_URL\"]}/callback_url\",\n        'AccountReference': 'Codearn',\n        'TransactionDesc': \"Payment for Codearn premium\"\n        }.to_json\n\n        headers = {\n        Content_type: 'application/json',\n        Authorization: \"Bearer #{access_token}\"\n        }\n\n        response = RestClient::Request.new({\n        method: :post,\n        url: url,\n        payload: payload,\n        headers: headers\n        }).execute do |response, request|\n        case response.code\n        when 500\n        [ :error, JSON.parse(response.to_str) ]\n        when 400\n        [ :error, JSON.parse(response.to_str) ]\n        when 200\n        [ :success, JSON.parse(response.to_str) ]\n        else\n        fail \"Invalid response #{response.to_str} received.\"\n        end\n        end\n        render json: response\n    end\n\n    # stkquery\n\n    def stkquery\n        url = \"https://sandbox.safaricom.co.ke/mpesa/stkpushquery/v1/query\"\n        timestamp = \"#{Time.now.strftime \"%Y%m%d%H%M%S\"}\"\n        business_short_code = ENV[\"MPESA_SHORTCODE\"]\n        password = Base64.strict_encode64(\"#{business_short_code}#{ENV[\"MPESA_PASSKEY\"]}#{timestamp}\")\n        payload = {\n        'BusinessShortCode': business_short_code,\n        'Password': password,\n        'Timestamp': timestamp,\n        'CheckoutRequestID': params[:checkoutRequestID]\n        }.to_json\n\n        headers = {\n        Content_type: 'application/json',\n        Authorization: \"Bearer #{ access_token }\"\n        }\n\n        response = RestClient::Request.new({\n        method: :post,\n        url: url,\n        payload: payload,\n        headers: headers\n        }).execute do |response, request|\n        case response.code\n        when 500\n        [ :error, JSON.parse(response.to_str) ]\n        when 400\n        [ :error, JSON.parse(response.to_str) ]\n        when 200\n        [ :success, JSON.parse(response.to_str) ]\n        else\n        fail \"Invalid response #{response.to_str} received.\"\n        end\n        end\n        render json: response\n    end\n\n    private\n\n    def generate_access_token_request\n        @url = \"https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials\"\n        @consumer_key = ENV['MPESA_CONSUMER_KEY']\n        @consumer_secret = ENV['MPESA_CONSUMER_SECRET']\n        @userpass = Base64::strict_encode64(\"#{@consumer_key}:#{@consumer_secret}\")\n        headers = {\n            Authorization: \"Bearer #{@userpass}\"\n        }\n        res = RestClient::Request.execute( url: @url, method: :get, headers: {\n            Authorization: \"Basic #{@userpass}\"\n        })\n        res\n    end\n\n    def access_token\n        res = generate_access_token_request()\n        if res.code != 200\n        r = generate_access_token_request()\n        if res.code != 200\n        raise MpesaError('Unable to generate access token')\n        end\n        end\n        body = JSON.parse(res, { symbolize_names: true })\n        token = body[:access_token]\n        AccessToken.destroy_all()\n        AccessToken.create!(token: token)\n        token\n    end\n\n\nend\n```\n- Your routes.rb should look like this.\n```ruby\nRails.application.routes.draw do\n    post 'stkpush', to: 'mpesas#stkpush'\n    post 'stkquery', to: 'mpesas#stkquery'\nend\n```\n- The full code for this tutorial can be found [here](https://github.com/Annastacia-dev/daraja-test).\n\nThat's it for this tutorial. I hope you found it helpful. If you have any questions, feel free to reach out to me on email: annetotoh@gmail.com.\nTHANK YOU!\n\n\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fannastacia-dev%2Fdaraja-test","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fannastacia-dev%2Fdaraja-test","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fannastacia-dev%2Fdaraja-test/lists"}