mobile-pesa 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: db79393cf87f6b160a078adea84a309e577ad21586d30394e6878c47d0426cdf
4
+ data.tar.gz: c70af84a9fec001435b43ab0c79f26fec7c4ad94ec343c7ae506c1247c133c6d
5
+ SHA512:
6
+ metadata.gz: 976f73cff909f1b8cb493de47894bd3acf8b03d59ffb993c08f132c13bf0c223d19cddcbf2663697fdc433452ab6ffdb4cee102a28861d3c0bc9fc98afdf66e2
7
+ data.tar.gz: 2ea2bdf435f03e9f4a8117a490095edf2db7128a44f83b71c29f1014422dacda979156ede18c028e0cbf85b3b9f994e765aadb232022bae3fac1406db6c81172
@@ -0,0 +1,16 @@
1
+ name: Ruby
2
+
3
+ on: [push,pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v2
10
+ - name: Set up Ruby
11
+ uses: ruby/setup-ruby@v1
12
+ with:
13
+ ruby-version: 3.0.2
14
+ bundler-cache: true
15
+ - name: Run the default task
16
+ run: bundle exec rake
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+ .env
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,13 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.4
3
+
4
+ Style/StringLiterals:
5
+ Enabled: true
6
+ EnforcedStyle: double_quotes
7
+
8
+ Style/StringLiteralsInInterpolation:
9
+ Enabled: true
10
+ EnforcedStyle: double_quotes
11
+
12
+ Layout/LineLength:
13
+ Max: 120
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2021-12-04
4
+
5
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in mobile-pesa.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+ gem "rspec", "~> 3.0"
10
+ gem "rubocop", "~> 1.7"
data/Gemfile.lock ADDED
@@ -0,0 +1,57 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ mobile-pesa (0.1.1)
5
+ colorize
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ ast (2.4.2)
11
+ colorize (0.8.1)
12
+ diff-lcs (1.4.4)
13
+ parallel (1.21.0)
14
+ parser (3.0.3.1)
15
+ ast (~> 2.4.1)
16
+ rainbow (3.0.0)
17
+ rake (13.0.6)
18
+ regexp_parser (2.2.0)
19
+ rexml (3.2.5)
20
+ rspec (3.10.0)
21
+ rspec-core (~> 3.10.0)
22
+ rspec-expectations (~> 3.10.0)
23
+ rspec-mocks (~> 3.10.0)
24
+ rspec-core (3.10.1)
25
+ rspec-support (~> 3.10.0)
26
+ rspec-expectations (3.10.1)
27
+ diff-lcs (>= 1.2.0, < 2.0)
28
+ rspec-support (~> 3.10.0)
29
+ rspec-mocks (3.10.2)
30
+ diff-lcs (>= 1.2.0, < 2.0)
31
+ rspec-support (~> 3.10.0)
32
+ rspec-support (3.10.3)
33
+ rubocop (1.23.0)
34
+ parallel (~> 1.10)
35
+ parser (>= 3.0.0.0)
36
+ rainbow (>= 2.2.2, < 4.0)
37
+ regexp_parser (>= 1.8, < 3.0)
38
+ rexml
39
+ rubocop-ast (>= 1.12.0, < 2.0)
40
+ ruby-progressbar (~> 1.7)
41
+ unicode-display_width (>= 1.4.0, < 3.0)
42
+ rubocop-ast (1.14.0)
43
+ parser (>= 3.0.1.1)
44
+ ruby-progressbar (1.11.0)
45
+ unicode-display_width (2.1.0)
46
+
47
+ PLATFORMS
48
+ x86_64-darwin-21
49
+
50
+ DEPENDENCIES
51
+ mobile-pesa!
52
+ rake (~> 13.0)
53
+ rspec (~> 3.0)
54
+ rubocop (~> 1.7)
55
+
56
+ BUNDLED WITH
57
+ 2.2.22
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Sylvance
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # Mobile::Pesa
2
+
3
+ Mpesa gem. This gem helps you carry out operations for Mpesa the easy way.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'mobile-pesa'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle install
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install mobile-pesa
20
+
21
+ ## Usage
22
+
23
+ TODO: Write usage instructions here
24
+
25
+ ## Development
26
+
27
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
28
+
29
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
30
+
31
+ ## Contributing
32
+
33
+ Bug reports and pull requests are welcome on GitHub at https://github.com/Sylvance/mobile-pesa.
34
+
35
+ ## License
36
+
37
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "mobile/pesa"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'net/http'
5
+ require 'openssl'
6
+ require 'ostruct'
7
+ require 'json'
8
+
9
+ module Mobile
10
+ module Pesa
11
+ class Authorization
12
+ def initialize; end
13
+
14
+ def self.call
15
+ url = URI("https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials")
16
+
17
+ http = Net::HTTP.new(url.host, url.port)
18
+ http.use_ssl = true
19
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
20
+
21
+ request = Net::HTTP::Get.new(url)
22
+ request.basic_auth(Mobile::Pesa.configuration.consumer_key, Mobile::Pesa.configuration.consumer_secret)
23
+
24
+ response = http.request(request)
25
+ parsed_body = JSON.parse(response.read_body)
26
+
27
+ if parsed_body.key?("errorCode")
28
+ error = OpenStruct.new(
29
+ error_code: parsed_body["errorCode"],
30
+ error_message: parsed_body["errorMessage"],
31
+ request_id: parsed_body["requestId"]
32
+ )
33
+ OpenStruct.new(result: nil, error: error)
34
+ else
35
+ result = OpenStruct.new(access_token: parsed_body["access_token"], expires_in: parsed_body["expires_in"])
36
+ OpenStruct.new(result: result, error: nil)
37
+ end
38
+ rescue JSON::ParserError => error
39
+ OpenStruct.new(result: nil, error: error)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'net/http'
5
+ require 'openssl'
6
+ require 'ostruct'
7
+ require 'json'
8
+ require 'base64'
9
+
10
+ module Mobile
11
+ module Pesa
12
+ class B2cPayment
13
+ attr_reader :amount, :phone_number, :short_code, :command_id, :remarks, :occasion
14
+
15
+ def self.call(amount:, phone_number:, short_code:, command_id:, remarks:, occasion:)
16
+ new(amount, phone_number, short_code, command_id, remarks, occasion).call
17
+ end
18
+
19
+ def initialize(amount, phone_number, short_code, command_id, remarks, occasion)
20
+ @amount = amount
21
+ @phone_number = phone_number
22
+ @short_code = short_code
23
+ @command_id = command_id
24
+ @remarks = remarks
25
+ @occasion = occasion
26
+ end
27
+
28
+ def call
29
+ url = URI("https://sandbox.safaricom.co.ke/mpesa/b2c/v1/paymentrequest")
30
+
31
+ http = Net::HTTP.new(url.host, url.port)
32
+ http.use_ssl = true
33
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
34
+
35
+ request = Net::HTTP::Post.new(url)
36
+ request["Content-Type"] = 'application/json'
37
+ request["Authorization"] = "Bearer #{token}"
38
+ request.body = JSON.dump(body)
39
+
40
+ response = http.request(request)
41
+ parsed_body = JSON.parse(response.read_body)
42
+
43
+ if parsed_body.key?("errorCode")
44
+ error = OpenStruct.new(
45
+ error_code: parsed_body["errorCode"],
46
+ error_message: parsed_body["errorMessage"],
47
+ request_id: parsed_body["requestId"]
48
+ )
49
+ OpenStruct.new(result: nil, error: error)
50
+ else
51
+ result = OpenStruct.new(
52
+ conversation_id: parsed_body["ConversationID"],
53
+ originator_conversation_id: parsed_body["OriginatorConversationID"],
54
+ response_code: parsed_body["ResponseCode"],
55
+ response_description: parsed_body["ResponseDescription"]
56
+ )
57
+ OpenStruct.new(result: result, error: nil)
58
+ end
59
+ rescue JSON::ParserError => error
60
+ OpenStruct.new(result: nil, error: error)
61
+ end
62
+
63
+ private
64
+
65
+ def token
66
+ Mobile::Pesa::Authorization.call.result.access_token
67
+ end
68
+
69
+ def body
70
+ {
71
+ "InitiatorName": "mobile-pesa Gem",
72
+ "SecurityCredential": security_credential,
73
+ "CommandID": command_id, # SalaryPayment, BusinessPayment, PromotionPayment
74
+ "Amount": amount,
75
+ "PartyA": short_code,
76
+ "PartyB": phone_number,
77
+ "Remarks": remarks,
78
+ "QueueTimeOutURL": Mobile::Pesa.configuration.queue_time_out_url,
79
+ "ResultURL": Mobile::Pesa.configuration.result_url,
80
+ "Occasion": occasion
81
+ }
82
+ end
83
+
84
+ def security_credential
85
+ cert = OpenSSL::X509::Certificate.new(file_data)
86
+ key = cert.public_key
87
+
88
+ Base64.strict_encode64(key.public_encrypt(password))
89
+ end
90
+
91
+ def file_data
92
+ file = File.open(cert_file_path)
93
+ data = file.read
94
+ file.close
95
+
96
+ data
97
+ end
98
+
99
+ def cert_file_path
100
+ File.join(File.dirname(__FILE__), file_path)
101
+ end
102
+
103
+ def file_path
104
+ return 'certificates/SandboxCertificate.cer' if Mobile::Pesa.configuration.enviroment == 'sandbox'
105
+ return 'certificates/ProductionCertificate.cer' if Mobile::Pesa.configuration.enviroment == 'production'
106
+ end
107
+
108
+ def password
109
+ Base64.strict_encode64("#{short_code}#{Mobile::Pesa.configuration.pass_key}#{timestamp}")
110
+ end
111
+
112
+ def timestamp
113
+ Time.now.strftime('%Y%m%d%H%M%S').to_i
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,38 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIGkzCCBXugAwIBAgIKXfBp5gAAAD+hNjANBgkqhkiG9w0BAQsFADBbMRMwEQYK
3
+ CZImiZPyLGQBGRYDbmV0MRkwFwYKCZImiZPyLGQBGRYJc2FmYXJpY29tMSkwJwYD
4
+ VQQDEyBTYWZhcmljb20gSW50ZXJuYWwgSXNzdWluZyBDQSAwMjAeFw0xNzA0MjUx
5
+ NjA3MjRaFw0xODAzMjExMzIwMTNaMIGNMQswCQYDVQQGEwJLRTEQMA4GA1UECBMH
6
+ TmFpcm9iaTEQMA4GA1UEBxMHTmFpcm9iaTEaMBgGA1UEChMRU2FmYXJpY29tIExp
7
+ bWl0ZWQxEzARBgNVBAsTClRlY2hub2xvZ3kxKTAnBgNVBAMTIGFwaWdlZS5hcGlj
8
+ YWxsZXIuc2FmYXJpY29tLmNvLmtlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
9
+ CgKCAQEAoknIb5Tm1hxOVdFsOejAs6veAai32Zv442BLuOGkFKUeCUM2s0K8XEsU
10
+ t6BP25rQGNlTCTEqfdtRrym6bt5k0fTDscf0yMCoYzaxTh1mejg8rPO6bD8MJB0c
11
+ FWRUeLEyWjMeEPsYVSJFv7T58IdAn7/RhkrpBl1dT7SmIZfNVkIlD35+Cxgab+u7
12
+ +c7dHh6mWguEEoE3NbV7Xjl60zbD/Buvmu6i9EYz+27jNVPI6pRXHvp+ajIzTSsi
13
+ eD8Ztz1eoC9mphErasAGpMbR1sba9bM6hjw4tyTWnJDz7RdQQmnsW1NfFdYdK0qD
14
+ RKUX7SG6rQkBqVhndFve4SDFRq6wvQIDAQABo4IDJDCCAyAwHQYDVR0OBBYEFG2w
15
+ ycrgEBPFzPUZVjh8KoJ3EpuyMB8GA1UdIwQYMBaAFOsy1E9+YJo6mCBjug1evuh5
16
+ TtUkMIIBOwYDVR0fBIIBMjCCAS4wggEqoIIBJqCCASKGgdZsZGFwOi8vL0NOPVNh
17
+ ZmFyaWNvbSUyMEludGVybmFsJTIwSXNzdWluZyUyMENBJTIwMDIsQ049U1ZEVDNJ
18
+ U1NDQTAxLENOPUNEUCxDTj1QdWJsaWMlMjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2
19
+ aWNlcyxDTj1Db25maWd1cmF0aW9uLERDPXNhZmFyaWNvbSxEQz1uZXQ/Y2VydGlm
20
+ aWNhdGVSZXZvY2F0aW9uTGlzdD9iYXNlP29iamVjdENsYXNzPWNSTERpc3RyaWJ1
21
+ dGlvblBvaW50hkdodHRwOi8vY3JsLnNhZmFyaWNvbS5jby5rZS9TYWZhcmljb20l
22
+ MjBJbnRlcm5hbCUyMElzc3VpbmclMjBDQSUyMDAyLmNybDCCAQkGCCsGAQUFBwEB
23
+ BIH8MIH5MIHJBggrBgEFBQcwAoaBvGxkYXA6Ly8vQ049U2FmYXJpY29tJTIwSW50
24
+ ZXJuYWwlMjBJc3N1aW5nJTIwQ0ElMjAwMixDTj1BSUEsQ049UHVibGljJTIwS2V5
25
+ JTIwU2VydmljZXMsQ049U2VydmljZXMsQ049Q29uZmlndXJhdGlvbixEQz1zYWZh
26
+ cmljb20sREM9bmV0P2NBQ2VydGlmaWNhdGU/YmFzZT9vYmplY3RDbGFzcz1jZXJ0
27
+ aWZpY2F0aW9uQXV0aG9yaXR5MCsGCCsGAQUFBzABhh9odHRwOi8vY3JsLnNhZmFy
28
+ aWNvbS5jby5rZS9vY3NwMAsGA1UdDwQEAwIFoDA9BgkrBgEEAYI3FQcEMDAuBiYr
29
+ BgEEAYI3FQiHz4xWhMLEA4XphTaE3tENhqCICGeGwcdsg7m5awIBZAIBDDAdBgNV
30
+ HSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwJwYJKwYBBAGCNxUKBBowGDAKBggr
31
+ BgEFBQcDAjAKBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOCAQEAC/hWx7KTwSYr
32
+ x2SOyyHNLTRmCnCJmqxA/Q+IzpW1mGtw4Sb/8jdsoWrDiYLxoKGkgkvmQmB2J3zU
33
+ ngzJIM2EeU921vbjLqX9sLWStZbNC2Udk5HEecdpe1AN/ltIoE09ntglUNINyCmf
34
+ zChs2maF0Rd/y5hGnMM9bX9ub0sqrkzL3ihfmv4vkXNxYR8k246ZZ8tjQEVsKehE
35
+ dqAmj8WYkYdWIHQlkKFP9ba0RJv7aBKb8/KP+qZ5hJip0I5Ey6JJ3wlEWRWUYUKh
36
+ gYoPHrJ92ToadnFCCpOlLKWc0xVxANofy6fqreOVboPO0qTAYpoXakmgeRNLUiar
37
+ 0ah6M/q/KA==
38
+ -----END CERTIFICATE-----
@@ -0,0 +1,35 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIGKzCCBROgAwIBAgIQDL7NH8cxSdUpl0ihH0A1wTANBgkqhkiG9w0BAQsFADBN
3
+ MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E
4
+ aWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMTgwODI3MDAwMDAwWhcN
5
+ MTkwNDA0MTIwMDAwWjBuMQswCQYDVQQGEwJLRTEQMA4GA1UEBxMHTmFpcm9iaTEW
6
+ MBQGA1UEChMNU2FmYXJpY29tIFBMQzETMBEGA1UECxMKRGlnaXRhbCBJVDEgMB4G
7
+ A1UEAxMXc2FuZGJveC5zYWZhcmljb20uY28ua2UwggEiMA0GCSqGSIb3DQEBAQUA
8
+ A4IBDwAwggEKAoIBAQC78yeC/wLoZY6TJeqc4g/9eAKIpeCwEsjX09pD8ZxAGXqT
9
+ Oi7ssdIGJBPmJZNeEVyf8ocFhisCuLngJ9Z5e/AvH52PhrEFmVu2D03zSf4C+rhZ
10
+ ndEKP6G79pUAb/bemOliU9zM8xYYkpCRzPWUzk6zSDarg0ZDLw5FrtZj/VJ9YEDL
11
+ WGgAfwExEgSN3wjyUlJ2UwI3wqQXLka0VNFWoZxUH5j436gbSWRIL6NJUmrq8V8S
12
+ aTEPz3eJHj3NOToDu245c7VKdF/KExyZjRjD2p5I+Aip80TXzKlZj6DjMb3DlfXF
13
+ Hsnu0+1uJE701mvKX7BiscxKr8tCRphL63as4dqvAgMBAAGjggLkMIIC4DAfBgNV
14
+ HSMEGDAWgBQPgGEcgjFh1S8o541GOLQs4cbZ4jAdBgNVHQ4EFgQUzZmY7ZORLw9w
15
+ qRbAQN5m9lJ28qMwIgYDVR0RBBswGYIXc2FuZGJveC5zYWZhcmljb20uY28ua2Uw
16
+ DgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjBr
17
+ BgNVHR8EZDBiMC+gLaArhilodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vc3NjYS1z
18
+ aGEyLWc2LmNybDAvoC2gK4YpaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NzY2Et
19
+ c2hhMi1nNi5jcmwwTAYDVR0gBEUwQzA3BglghkgBhv1sAQEwKjAoBggrBgEFBQcC
20
+ ARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAIBgZngQwBAgIwfAYIKwYB
21
+ BQUHAQEEcDBuMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20w
22
+ RgYIKwYBBQUHMAKGOmh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2Vy
23
+ dFNIQTJTZWN1cmVTZXJ2ZXJDQS5jcnQwCQYDVR0TBAIwADCCAQUGCisGAQQB1nkC
24
+ BAIEgfYEgfMA8QB2AKS5CZC0GFgUh7sTosxncAo8NZgE+RvfuON3zQ7IDdwQAAAB
25
+ ZXs1FvEAAAQDAEcwRQIgBzVMkm7SNprjJ1GBqiXIc9rNzY+y7gt6s/O02oMkyFoC
26
+ IQDBuThGlpmUKpeZoHhK6HGwB4jDMIecmKaOcMS18R2jxwB3AId1v+dZfPiMQ5lf
27
+ vfNu/1aNR1Y2/0q1YMG06v9eoIMPAAABZXs1F8IAAAQDAEgwRgIhAIRq2XFiC+RS
28
+ uDCYq8ICJg0QafSV+e9BLpJnElEdaSjiAiEAyiiW4vxwv4cWcAXE6FAipctyUBs6
29
+ bE5QyaCnmNpoDiQwDQYJKoZIhvcNAQELBQADggEBAB0YoWve9Sxhb0PBS3Hc46Rf
30
+ a7H1jhHuwE+UyscSQsdJdk8uPAgDuKRZMvJPGEaCkNHm36NfcaXXFjPOl7LI1d1a
31
+ 9zqSP0xeZBI6cF0x96WuQGrI9/WR2tfxjmaUSp8a/aJ6n+tZA28eJZNPrIaMm+6j
32
+ gh7AkKnqcf+g8F/MvCCVdNAiVMdz6UpCscf6BRPHNZ5ifvChGh7aUKjrVLLuF4Ls
33
+ HE05qm6HNyV5eTa6wvcbc4ewguN1UDZvPWetSyfBk10Wbpor4znQ4TJ3Y9uCvsJH
34
+ 41ldblDvZZ2z4kB2UYQ7iBkPlJSxSOaFgW/GGDXq49sz/995xzhVITHxh2SdLkI=
35
+ -----END CERTIFICATE-----
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'net/http'
5
+ require 'openssl'
6
+ require 'ostruct'
7
+ require 'json'
8
+
9
+ module Mobile
10
+ module Pesa
11
+ class RegisterUrls
12
+ attr_reader :short_code, :response_type, :confirmation_url, :validation_url
13
+
14
+ def self.call(short_code:, response_type:, confirmation_url:, validation_url:)
15
+ new(short_code, response_type, confirmation_url, validation_url).call
16
+ end
17
+
18
+ def initialize(short_code, response_type, confirmation_url, validation_url)
19
+ @short_code = short_code
20
+ @response_type = response_type
21
+ @confirmation_url = confirmation_url
22
+ @validation_url = validation_url
23
+ end
24
+
25
+ def call
26
+ url = URI("https://sandbox.safaricom.co.ke/mpesa/c2b/v1/registerurl")
27
+
28
+ http = Net::HTTP.new(url.host, url.port)
29
+ http.use_ssl = true
30
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
31
+
32
+ request = Net::HTTP::Post.new(url)
33
+ request["Content-Type"] = 'application/json'
34
+ request["Authorization"] = "Bearer #{token}"
35
+ request.body = JSON.dump(body)
36
+
37
+ response = http.request(request)
38
+ parsed_body = JSON.parse(response.read_body)
39
+
40
+ if parsed_body.key?("errorCode")
41
+ error = OpenStruct.new(
42
+ error_code: parsed_body["errorCode"],
43
+ error_message: parsed_body["errorMessage"],
44
+ request_id: parsed_body["requestId"]
45
+ )
46
+ OpenStruct.new(result: nil, error: error)
47
+ else
48
+ result = OpenStruct.new(
49
+ originator_converstion_id: parsed_body["OriginatorConverstionID"],
50
+ response_code: parsed_body["ResponseCode"],
51
+ response_description: parsed_body["ResponseDescription"]
52
+ )
53
+ OpenStruct.new(result: result, error: nil)
54
+ end
55
+ rescue JSON::ParserError => error
56
+ OpenStruct.new(result: nil, error: error)
57
+ end
58
+
59
+ private
60
+
61
+ def token
62
+ Mobile::Pesa::Authorization.call.result.access_token
63
+ end
64
+
65
+ def body
66
+ {
67
+ "ShortCode": short_code,
68
+ "ResponseType": response_type, # Cancelled Completed
69
+ "ConfirmationURL": confirmation_url,
70
+ "ValidationURL": validation_url
71
+ }
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'net/http'
5
+ require 'openssl'
6
+ require 'ostruct'
7
+ require 'json'
8
+ require 'base64'
9
+
10
+ module Mobile
11
+ module Pesa
12
+ class Reversal
13
+ attr_reader :amount, :transaction_id, :short_code, :remarks, :occasion
14
+
15
+ def self.call(amount:, transaction_id:, short_code:, remarks:, occasion:)
16
+ new(amount, transaction_id, short_code, remarks, occasion).call
17
+ end
18
+
19
+ def initialize(amount, transaction_id, short_code, remarks, occasion)
20
+ @amount = amount
21
+ @transaction_id = transaction_id
22
+ @short_code = short_code
23
+ @remarks = remarks
24
+ @occasion = occasion
25
+ end
26
+
27
+ def call
28
+ url = URI("https://sandbox.safaricom.co.ke/mpesa/reversal/v1/request")
29
+
30
+ http = Net::HTTP.new(url.host, url.port)
31
+ http.use_ssl = true
32
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
33
+
34
+ request = Net::HTTP::Post.new(url)
35
+ request["Content-Type"] = 'application/json'
36
+ request["Authorization"] = "Bearer #{token}"
37
+ request.body = JSON.dump(body)
38
+
39
+ response = http.request(request)
40
+ parsed_body = JSON.parse(response.read_body)
41
+
42
+ if parsed_body.key?("errorCode")
43
+ error = OpenStruct.new(
44
+ error_code: parsed_body["errorCode"],
45
+ error_message: parsed_body["errorMessage"],
46
+ request_id: parsed_body["requestId"]
47
+ )
48
+ OpenStruct.new(result: nil, error: error)
49
+ else
50
+ result = OpenStruct.new(
51
+ originator_converstion_id: parsed_body["OriginatorConverstionID"],
52
+ response_code: parsed_body["ResponseCode"],
53
+ response_description: parsed_body["ResponseDescription"]
54
+ )
55
+ OpenStruct.new(result: result, error: nil)
56
+ end
57
+ rescue JSON::ParserError => error
58
+ OpenStruct.new(result: nil, error: error)
59
+ end
60
+
61
+ private
62
+
63
+ def token
64
+ Mobile::Pesa::Authorization.call.result.access_token
65
+ end
66
+
67
+ def body
68
+ {
69
+ "Initiator": "mobile-pesa Gem",
70
+ "SecurityCredential": security_credential,
71
+ "CommandID": "TransactionReversal",
72
+ "TransactionID": transaction_id,
73
+ "Amount": amount,
74
+ "ReceiverParty": short_code,
75
+ "RecieverIdentifierType": "4",
76
+ "Remarks": remarks,
77
+ "QueueTimeOutURL": Mobile::Pesa.configuration.queue_time_out_url,
78
+ "ResultURL": Mobile::Pesa.configuration.result_url,
79
+ "Occasion": occasion
80
+ }
81
+ end
82
+
83
+ def security_credential
84
+ cert = OpenSSL::X509::Certificate.new(file_data)
85
+ key = cert.public_key
86
+
87
+ Base64.strict_encode64(key.public_encrypt(password))
88
+ end
89
+
90
+ def file_data
91
+ file = File.open(cert_file_path)
92
+ data = file.read
93
+ file.close
94
+
95
+ data
96
+ end
97
+
98
+ def cert_file_path
99
+ File.join(File.dirname(__FILE__), file_path)
100
+ end
101
+
102
+ def file_path
103
+ return 'certificates/SandboxCertificate.cer' if Mobile::Pesa.configuration.enviroment == 'sandbox'
104
+ return 'certificates/ProductionCertificate.cer' if Mobile::Pesa.configuration.enviroment == 'production'
105
+ end
106
+
107
+ def password
108
+ Base64.strict_encode64("#{short_code}#{Mobile::Pesa.configuration.pass_key}#{timestamp}")
109
+ end
110
+
111
+ def timestamp
112
+ Time.now.strftime('%Y%m%d%H%M%S').to_i
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'net/http'
5
+ require 'openssl'
6
+ require 'ostruct'
7
+ require 'json'
8
+
9
+ module Mobile
10
+ module Pesa
11
+ class SimulateC2bViaPaybillNumber
12
+ attr_reader :amount, :phone_number, :account_number, :pay_bill_number
13
+
14
+ def self.call(amount:, phone_number:, account_number:, pay_bill_number:)
15
+ new(amount, phone_number, account_number, pay_bill_number).call
16
+ end
17
+
18
+ def initialize(amount, phone_number, account_number, pay_bill_number)
19
+ @amount = amount
20
+ @phone_number = phone_number
21
+ @account_number = account_number
22
+ @pay_bill_number = pay_bill_number
23
+ end
24
+
25
+ def call
26
+ url = URI("https://sandbox.safaricom.co.ke/mpesa/c2b/v1/simulate")
27
+
28
+ http = Net::HTTP.new(url.host, url.port)
29
+ http.use_ssl = true
30
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
31
+
32
+ request = Net::HTTP::Post.new(url)
33
+ request["Content-Type"] = 'application/json'
34
+ request["Authorization"] = "Bearer #{token}"
35
+ request.body = JSON.dump(body)
36
+
37
+ response = http.request(request)
38
+ parsed_body = JSON.parse(response.read_body)
39
+
40
+ if parsed_body.key?("errorCode")
41
+ error = OpenStruct.new(
42
+ error_code: parsed_body["errorCode"],
43
+ error_message: parsed_body["errorMessage"],
44
+ request_id: parsed_body["requestId"]
45
+ )
46
+ OpenStruct.new(result: nil, error: error)
47
+ else
48
+ result = OpenStruct.new(
49
+ merchant_request_id: parsed_body["MerchantRequestID"],
50
+ checkout_request_id: parsed_body["CheckoutRequestID"],
51
+ response_code: parsed_body["ResponseCode"],
52
+ response_description: parsed_body["ResponseDescription"],
53
+ customer_message: parsed_body["CustomerMessage"]
54
+ )
55
+ OpenStruct.new(result: result, error: nil)
56
+ end
57
+ rescue JSON::ParserError => error
58
+ OpenStruct.new(result: nil, error: error)
59
+ end
60
+
61
+ private
62
+
63
+ def token
64
+ Mobile::Pesa::Authorization.call.result.access_token
65
+ end
66
+
67
+ def body
68
+ {
69
+ "ShortCode": pay_bill_number,
70
+ "CommandID": "CustomerPayBillOnline",
71
+ "Amount": amount,
72
+ "Msisdn": phone_number,
73
+ "BillRefNumber": account_number
74
+ }
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'net/http'
5
+ require 'openssl'
6
+ require 'ostruct'
7
+ require 'json'
8
+
9
+ module Mobile
10
+ module Pesa
11
+ class SimulateC2bViaTillNumber
12
+ attr_reader :amount, :phone_number, :till_number
13
+
14
+ def self.call(amount:, phone_number:, till_number:)
15
+ new(amount, phone_number, till_number).call
16
+ end
17
+
18
+ def initialize(amount, phone_number, till_number)
19
+ @amount = amount
20
+ @phone_number = phone_number
21
+ @till_number = till_number
22
+ end
23
+
24
+ def call
25
+ url = URI("https://sandbox.safaricom.co.ke/mpesa/c2b/v1/simulate")
26
+
27
+ http = Net::HTTP.new(url.host, url.port)
28
+ http.use_ssl = true
29
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
30
+
31
+ request = Net::HTTP::Post.new(url)
32
+ request["Content-Type"] = 'application/json'
33
+ request["Authorization"] = "Bearer #{token}"
34
+ request.body = JSON.dump(body)
35
+
36
+ response = http.request(request)
37
+ parsed_body = JSON.parse(response.read_body)
38
+
39
+ if parsed_body.key?("errorCode")
40
+ error = OpenStruct.new(
41
+ error_code: parsed_body["errorCode"],
42
+ error_message: parsed_body["errorMessage"],
43
+ request_id: parsed_body["requestId"]
44
+ )
45
+ OpenStruct.new(result: nil, error: error)
46
+ else
47
+ result = OpenStruct.new(
48
+ merchant_request_id: parsed_body["MerchantRequestID"],
49
+ checkout_request_id: parsed_body["CheckoutRequestID"],
50
+ response_code: parsed_body["ResponseCode"],
51
+ response_description: parsed_body["ResponseDescription"],
52
+ customer_message: parsed_body["CustomerMessage"]
53
+ )
54
+ OpenStruct.new(result: result, error: nil)
55
+ end
56
+ rescue JSON::ParserError => error
57
+ OpenStruct.new(result: nil, error: error)
58
+ end
59
+
60
+ private
61
+
62
+ def token
63
+ Mobile::Pesa::Authorization.call.result.access_token
64
+ end
65
+
66
+ def body
67
+ {
68
+ "ShortCode": till_number,
69
+ "CommandID": "CustomerBuyGoodsOnline",
70
+ "Amount": amount,
71
+ "Msisdn": phone_number
72
+ }
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'net/http'
5
+ require 'openssl'
6
+ require 'ostruct'
7
+ require 'json'
8
+ require 'base64'
9
+
10
+ module Mobile
11
+ module Pesa
12
+ class StkPushStatus
13
+ attr_reader :checkout_request_id, :short_code
14
+
15
+ def self.call(checkout_request_id:, short_code:)
16
+ new(checkout_request_id, short_code).call
17
+ end
18
+
19
+ def initialize(checkout_request_id, short_code)
20
+ @checkout_request_id = checkout_request_id
21
+ @short_code = short_code
22
+ end
23
+
24
+ def call
25
+ url = URI("https://sandbox.safaricom.co.ke/mpesa/stkpushquery/v1/query")
26
+
27
+ http = Net::HTTP.new(url.host, url.port)
28
+ http.use_ssl = true
29
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
30
+
31
+ request = Net::HTTP::Post.new(url)
32
+ request["Content-Type"] = 'application/json'
33
+ request["Authorization"] = "Bearer #{token}"
34
+ request.body = JSON.dump(body)
35
+
36
+ response = http.request(request)
37
+ parsed_body = JSON.parse(response.read_body)
38
+
39
+ if parsed_body.key?("errorCode")
40
+ error = OpenStruct.new(
41
+ error_code: parsed_body["errorCode"],
42
+ error_message: parsed_body["errorMessage"],
43
+ request_id: parsed_body["requestId"]
44
+ )
45
+ OpenStruct.new(result: nil, error: error)
46
+ else
47
+ result = OpenStruct.new(
48
+ merchant_request_id: parsed_body["MerchantRequestID"],
49
+ checkout_request_id: parsed_body["CheckoutRequestID"],
50
+ response_code: parsed_body["ResponseCode"],
51
+ response_description: parsed_body["ResponseDescription"],
52
+ result_desc: parsed_body["ResultDesc"],
53
+ result_code: parsed_body["ResultCode"]
54
+ )
55
+ OpenStruct.new(result: result, error: nil)
56
+ end
57
+ rescue JSON::ParserError => error
58
+ OpenStruct.new(result: nil, error: error)
59
+ end
60
+
61
+ private
62
+
63
+ def token
64
+ Mobile::Pesa::Authorization.call.result.access_token
65
+ end
66
+
67
+ def body
68
+ {
69
+ "BusinessShortCode": short_code,
70
+ "Password": password,
71
+ "Timestamp": timestamp.to_s,
72
+ "CheckoutRequestID": checkout_request_id
73
+ }
74
+ end
75
+
76
+ def password
77
+ Base64.strict_encode64("#{short_code}#{Mobile::Pesa.configuration.pass_key}#{timestamp}")
78
+ end
79
+
80
+ def timestamp
81
+ Time.now.strftime('%Y%m%d%H%M%S').to_i
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'net/http'
5
+ require 'openssl'
6
+ require 'ostruct'
7
+ require 'json'
8
+ require 'base64'
9
+
10
+ module Mobile
11
+ module Pesa
12
+ class StkPushViaPaybillNumber
13
+ attr_reader :amount, :phone_number, :account_number, :pay_bill_number
14
+
15
+ def self.call(amount:, phone_number:, account_number:, pay_bill_number:)
16
+ new(amount, phone_number, account_number, pay_bill_number).call
17
+ end
18
+
19
+ def initialize(amount, phone_number, account_number, pay_bill_number)
20
+ @amount = amount
21
+ @phone_number = phone_number
22
+ @account_number = account_number
23
+ @pay_bill_number = pay_bill_number
24
+ end
25
+
26
+ def call
27
+ url = URI("https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest")
28
+
29
+ http = Net::HTTP.new(url.host, url.port)
30
+ http.use_ssl = true
31
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
32
+
33
+ request = Net::HTTP::Post.new(url)
34
+ request["Content-Type"] = 'application/json'
35
+ request["Authorization"] = "Bearer #{token}"
36
+ request.body = JSON.dump(body)
37
+
38
+ response = http.request(request)
39
+ parsed_body = JSON.parse(response.read_body)
40
+
41
+ if parsed_body.key?("errorCode")
42
+ error = OpenStruct.new(
43
+ error_code: parsed_body["errorCode"],
44
+ error_message: parsed_body["errorMessage"],
45
+ request_id: parsed_body["requestId"]
46
+ )
47
+ OpenStruct.new(result: nil, error: error)
48
+ else
49
+ result = OpenStruct.new(
50
+ merchant_request_id: parsed_body["MerchantRequestID"],
51
+ checkout_request_id: parsed_body["CheckoutRequestID"],
52
+ response_code: parsed_body["ResponseCode"],
53
+ response_description: parsed_body["ResponseDescription"],
54
+ customer_message: parsed_body["CustomerMessage"]
55
+ )
56
+ OpenStruct.new(result: result, error: nil)
57
+ end
58
+ rescue JSON::ParserError => error
59
+ OpenStruct.new(result: nil, error: error)
60
+ end
61
+
62
+ private
63
+
64
+ def token
65
+ Mobile::Pesa::Authorization.call.result.access_token
66
+ end
67
+
68
+ def body
69
+ {
70
+ "BusinessShortCode": pay_bill_number,
71
+ "Password": password,
72
+ "Timestamp": timestamp.to_s,
73
+ "TransactionType": "CustomerPayBillOnline",
74
+ "Amount": amount,
75
+ "PartyA": phone_number,
76
+ "PartyB": pay_bill_number,
77
+ "PhoneNumber": phone_number,
78
+ "CallBackURL": Mobile::Pesa.configuration.callback_url,
79
+ "AccountReference": account_number,
80
+ "TransactionDesc": "Payment of X"
81
+ }
82
+ end
83
+
84
+ def password
85
+ Base64.strict_encode64("#{pay_bill_number}#{Mobile::Pesa.configuration.pass_key}#{timestamp}")
86
+ end
87
+
88
+ def timestamp
89
+ Time.now.strftime('%Y%m%d%H%M%S').to_i
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'net/http'
5
+ require 'openssl'
6
+ require 'ostruct'
7
+ require 'json'
8
+ require 'base64'
9
+
10
+ module Mobile
11
+ module Pesa
12
+ class StkPushViaTillNumber
13
+ attr_reader :amount, :phone_number, :till_number
14
+
15
+ def self.call(amount:, phone_number:, till_number:)
16
+ new(amount, phone_number, till_number).call
17
+ end
18
+
19
+ def initialize(amount, phone_number, till_number)
20
+ @amount = amount
21
+ @phone_number = phone_number
22
+ @till_number = till_number
23
+ end
24
+
25
+ def call
26
+ url = URI("https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest")
27
+
28
+ http = Net::HTTP.new(url.host, url.port)
29
+ http.use_ssl = true
30
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
31
+
32
+ request = Net::HTTP::Post.new(url)
33
+ request["Content-Type"] = 'application/json'
34
+ request["Authorization"] = "Bearer #{token}"
35
+ request.body = JSON.dump(body)
36
+
37
+ response = http.request(request)
38
+ parsed_body = JSON.parse(response.read_body)
39
+
40
+ if parsed_body.key?("errorCode")
41
+ error = OpenStruct.new(
42
+ error_code: parsed_body["errorCode"],
43
+ error_message: parsed_body["errorMessage"],
44
+ request_id: parsed_body["requestId"]
45
+ )
46
+ OpenStruct.new(result: nil, error: error)
47
+ else
48
+ result = OpenStruct.new(
49
+ merchant_request_id: parsed_body["MerchantRequestID"],
50
+ checkout_request_id: parsed_body["CheckoutRequestID"],
51
+ response_code: parsed_body["ResponseCode"],
52
+ response_description: parsed_body["ResponseDescription"],
53
+ customer_message: parsed_body["CustomerMessage"]
54
+ )
55
+ OpenStruct.new(result: result, error: nil)
56
+ end
57
+ rescue JSON::ParserError => error
58
+ OpenStruct.new(result: nil, error: error)
59
+ end
60
+
61
+ private
62
+
63
+ def token
64
+ Mobile::Pesa::Authorization.call.result.access_token
65
+ end
66
+
67
+ def body
68
+ {
69
+ "BusinessShortCode": till_number,
70
+ "Password": password,
71
+ "Timestamp": timestamp.to_s,
72
+ "TransactionType": "CustomerBuyGoodsOnline",
73
+ "Amount": amount,
74
+ "PartyA": phone_number,
75
+ "PartyB": till_number,
76
+ "PhoneNumber": phone_number,
77
+ "CallBackURL": Mobile::Pesa.configuration.callback_url,
78
+ "TransactionDesc": Mobile::Pesa.configuration.default_description
79
+ }
80
+ end
81
+
82
+ def password
83
+ Base64.strict_encode64("#{till_number}#{Mobile::Pesa.configuration.pass_key}#{timestamp}")
84
+ end
85
+
86
+ def timestamp
87
+ Time.now.strftime('%Y%m%d%H%M%S').to_i
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mobile
4
+ module Pesa
5
+ VERSION = "0.1.1"
6
+ end
7
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "pesa/authorization"
4
+ require_relative "pesa/b2c_payment"
5
+ require_relative "pesa/register_urls"
6
+ require_relative "pesa/reversal"
7
+ require_relative "pesa/simulate_c2b_via_paybill_number"
8
+ require_relative "pesa/simulate_c2b_via_till_number"
9
+ require_relative "pesa/stk_push_status"
10
+ require_relative "pesa/stk_push_via_paybill_number"
11
+ require_relative "pesa/stk_push_via_till_number"
12
+ require_relative "pesa/version"
13
+ require 'ostruct'
14
+
15
+ module Mobile
16
+ module Pesa
17
+ class Error < StandardError; end
18
+
19
+ def self.configuration
20
+ @configuration ||= OpenStruct.new(
21
+ consumer_key: nil,
22
+ consumer_secret: nil,
23
+ pass_key: nil,
24
+ short_code: nil,
25
+ response_type: nil,
26
+ callback_url: nil,
27
+ result_url: nil,
28
+ queue_time_out_url: nil,
29
+ default_description: nil,
30
+ enviroment: nil
31
+ )
32
+ end
33
+
34
+ def self.configure
35
+ yield(configuration)
36
+ end
37
+
38
+ def to_recursive_ostruct(hash)
39
+ result = hash.each_with_object({}) do |(key, val), memo|
40
+ memo[key] = val.is_a?(Hash) ? to_recursive_ostruct(val) : val
41
+ end
42
+
43
+ OpenStruct.new(result)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/mobile/pesa/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "mobile-pesa"
7
+ spec.version = Mobile::Pesa::VERSION
8
+ spec.authors = ["Sylvance"]
9
+ spec.email = ["9350722+Sylvance@users.noreply.github.com"]
10
+
11
+ spec.summary = "Mpesa gem."
12
+ spec.description = "This gem helps you carry out operations for Mpesa the easy way."
13
+ spec.homepage = "https://github.com/Sylvance/mobile-pesa"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.4.0"
16
+
17
+ spec.metadata["allowed_push_host"] = "https://rubygems.org/"
18
+
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = "https://github.com/Sylvance/mobile-pesa"
21
+ spec.metadata["changelog_uri"] = "https://github.com/Sylvance/mobile-pesa/CHANGELOG.md"
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
27
+ end
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ # Uncomment to register a new dependency of your gem
33
+ spec.add_dependency "colorize"
34
+
35
+ # For more information and examples about making a new gem, checkout our
36
+ # guide at: https://bundler.io/guides/creating_gem.html
37
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mobile-pesa
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Sylvance
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-12-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: colorize
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: This gem helps you carry out operations for Mpesa the easy way.
28
+ email:
29
+ - 9350722+Sylvance@users.noreply.github.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".github/workflows/main.yml"
35
+ - ".gitignore"
36
+ - ".rspec"
37
+ - ".rubocop.yml"
38
+ - CHANGELOG.md
39
+ - Gemfile
40
+ - Gemfile.lock
41
+ - LICENSE.txt
42
+ - README.md
43
+ - Rakefile
44
+ - bin/console
45
+ - bin/setup
46
+ - lib/mobile/pesa.rb
47
+ - lib/mobile/pesa/authorization.rb
48
+ - lib/mobile/pesa/b2c_payment.rb
49
+ - lib/mobile/pesa/certificates/ProductionCertificate.cer
50
+ - lib/mobile/pesa/certificates/SandboxCertificate.cer
51
+ - lib/mobile/pesa/register_urls.rb
52
+ - lib/mobile/pesa/reversal.rb
53
+ - lib/mobile/pesa/simulate_c2b_via_paybill_number.rb
54
+ - lib/mobile/pesa/simulate_c2b_via_till_number.rb
55
+ - lib/mobile/pesa/stk_push_status.rb
56
+ - lib/mobile/pesa/stk_push_via_paybill_number.rb
57
+ - lib/mobile/pesa/stk_push_via_till_number.rb
58
+ - lib/mobile/pesa/version.rb
59
+ - mobile-pesa.gemspec
60
+ homepage: https://github.com/Sylvance/mobile-pesa
61
+ licenses:
62
+ - MIT
63
+ metadata:
64
+ allowed_push_host: https://rubygems.org/
65
+ homepage_uri: https://github.com/Sylvance/mobile-pesa
66
+ source_code_uri: https://github.com/Sylvance/mobile-pesa
67
+ changelog_uri: https://github.com/Sylvance/mobile-pesa/CHANGELOG.md
68
+ post_install_message:
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: 2.4.0
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ requirements: []
83
+ rubygems_version: 3.2.22
84
+ signing_key:
85
+ specification_version: 4
86
+ summary: Mpesa gem.
87
+ test_files: []