mpesa_stk 2.0.0 → 3.0.0

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.
data/Rakefile CHANGED
File without changes
data/SECURITY.md CHANGED
File without changes
data/bin/index.jpeg CHANGED
File without changes
@@ -25,6 +25,7 @@
25
25
  require 'base64'
26
26
  require 'json'
27
27
  require 'redis'
28
+ require 'mpesa_stk/config'
28
29
 
29
30
  module MpesaStk
30
31
  # Handles OAuth access token generation, caching, and refreshing for M-Pesa APIs
@@ -36,8 +37,8 @@ module MpesaStk
36
37
  end
37
38
 
38
39
  def initialize(key = nil, secret = nil)
39
- @key = key.nil? ? ENV.fetch('key', nil) : key
40
- @secret = secret.nil? ? ENV.fetch('secret', nil) : secret
40
+ @key = key.nil? ? Config.fetch('key') : key
41
+ @secret = secret.nil? ? Config.fetch('secret') : secret
41
42
  begin
42
43
  @redis = Redis.new
43
44
  rescue Redis::CannotConnectError, Redis::ConnectionError => e
@@ -100,7 +101,7 @@ module MpesaStk
100
101
  private
101
102
 
102
103
  def url
103
- "#{ENV.fetch('base_url', nil)}#{ENV.fetch('token_generator_url', nil)}"
104
+ "#{Config.fetch('base_url')}#{Config.fetch('token_generator_url')}"
104
105
  end
105
106
 
106
107
  def headers
@@ -1,142 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright (c) 2018 mboya
4
- #
5
- # MIT License
6
- #
7
- # Permission is hereby granted, free of charge, to any person obtaining a copy
8
- # of this software and associated documentation files (the "Software"), to deal
9
- # in the Software without restriction, including without limitation the rights
10
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
- # copies of the Software, and to permit persons to whom the Software is
12
- # furnished to do so, subject to the following conditions:
13
- #
14
- # The above copyright notice and this permission notice shall be included in
15
- # all copies or substantial portions of the Software.
16
- #
17
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
- # THE SOFTWARE.
24
-
25
- require 'mpesa_stk/access_token'
3
+ require 'mpesa_stk/client'
26
4
 
27
5
  module MpesaStk
28
- # Query account balance for PayBill or Till Number
29
- class AccountBalance
6
+ # Query account balance for a PayBill or Till number.
7
+ class AccountBalance < Client
30
8
  class << self
31
- def query(hash = {})
32
- new(hash).query_balance
9
+ def call(**options)
10
+ new(**options).query_balance
33
11
  end
34
12
  end
35
13
 
36
- attr_reader :token, :initiator, :security_credential, :party_a, :identifier_type, :result_url, :queue_timeout_url
37
-
38
- def initialize(hash = {})
39
- @token = MpesaStk::AccessToken.call(hash['key'], hash['secret'])
40
- @initiator = hash['initiator'] || ENV.fetch('initiator', nil)
41
- @security_credential = hash['security_credential'] || ENV.fetch('security_credential', nil)
42
- @party_a = hash['party_a'] || ENV.fetch('business_short_code', nil)
43
- @identifier_type = hash['identifier_type'] || '4'
44
- @result_url = hash['result_url'] || ENV.fetch('result_url', nil)
45
- @queue_timeout_url = hash['queue_timeout_url'] || ENV.fetch('queue_timeout_url', nil)
46
- end
47
-
48
14
  def query_balance
49
- response = HTTParty.post(url, headers: headers, body: body)
50
-
51
- raise StandardError, "Failed to query account balance: #{response.code} - #{response.body}" unless response.success?
52
-
53
- JSON.parse(response.body)
54
- end
55
-
56
- private
57
-
58
- def url
59
- "#{ENV.fetch('base_url', nil)}#{ENV.fetch('account_balance_url', nil)}"
60
- end
61
-
62
- def headers
63
- {
64
- 'Authorization' => "Bearer #{token}",
65
- 'Content-Type' => 'application/json'
66
- }
67
- end
68
-
69
- def body
70
- {
71
- Initiator: get_initiator,
72
- SecurityCredential: get_security_credential,
73
- CommandID: 'AccountBalance',
74
- PartyA: get_party_a,
75
- IdentifierType: identifier_type,
76
- ResultURL: get_result_url,
77
- QueueTimeOutURL: get_queue_timeout_url
78
- }.to_json
79
- end
80
-
81
- def get_initiator
82
- if initiator.nil? || initiator.eql?('')
83
- raise ArgumentError, 'Initiator is not defined' if ENV['initiator'].nil? || ENV['initiator'].eql?('')
84
-
85
- ENV.fetch('initiator', nil)
86
-
87
- else
88
- initiator
89
- end
90
- end
91
-
92
- def get_security_credential
93
- if security_credential.nil? || security_credential.eql?('')
94
- if ENV['security_credential'].nil? || ENV['security_credential'].eql?('')
95
- raise ArgumentError, 'Security Credential is not defined'
96
- end
97
-
98
- ENV.fetch('security_credential', nil)
99
-
100
- else
101
- security_credential
102
- end
103
- end
104
-
105
- def get_party_a
106
- if party_a.nil? || party_a.eql?('')
107
- if ENV['business_short_code'].nil? || ENV['business_short_code'].eql?('')
108
- raise ArgumentError, 'PartyA (Business Short Code) is not defined'
109
- end
110
-
111
- ENV.fetch('business_short_code', nil)
112
-
113
- else
114
- party_a
115
- end
116
- end
117
-
118
- def get_result_url
119
- if result_url.nil? || result_url.eql?('')
120
- raise ArgumentError, 'Result URL is not defined' if ENV['result_url'].nil? || ENV['result_url'].eql?('')
121
-
122
- ENV.fetch('result_url', nil)
123
-
124
- else
125
- result_url
126
- end
127
- end
128
-
129
- def get_queue_timeout_url
130
- if queue_timeout_url.nil? || queue_timeout_url.eql?('')
131
- if ENV['queue_timeout_url'].nil? || ENV['queue_timeout_url'].eql?('')
132
- raise ArgumentError, 'Queue Timeout URL is not defined'
133
- end
134
-
135
- ENV.fetch('queue_timeout_url', nil)
136
-
137
- else
138
- queue_timeout_url
139
- end
15
+ post(
16
+ 'account_balance_url',
17
+ {
18
+ Initiator: option('initiator'),
19
+ SecurityCredential: option('security_credential'),
20
+ CommandID: 'AccountBalance',
21
+ PartyA: option('business_short_code', :party_a),
22
+ IdentifierType: @options.fetch(:identifier_type, '4'),
23
+ ResultURL: option('result_url'),
24
+ QueueTimeOutURL: option('queue_timeout_url')
25
+ },
26
+ error_message: 'Failed to query account balance'
27
+ )
140
28
  end
141
29
  end
142
30
  end
data/lib/mpesa_stk/b2b.rb CHANGED
@@ -1,153 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright (c) 2018 mboya
4
- #
5
- # MIT License
6
- #
7
- # Permission is hereby granted, free of charge, to any person obtaining a copy
8
- # of this software and associated documentation files (the "Software"), to deal
9
- # in the Software without restriction, including without limitation the rights
10
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
- # copies of the Software, and to permit persons to whom the Software is
12
- # furnished to do so, subject to the following conditions:
13
- #
14
- # The above copyright notice and this permission notice shall be included in
15
- # all copies or substantial portions of the Software.
16
- #
17
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
- # THE SOFTWARE.
24
-
25
- require 'mpesa_stk/access_token'
3
+ require 'mpesa_stk/client'
26
4
 
27
5
  module MpesaStk
28
- # Business to Business payment API - send money between businesses
29
- class B2B
6
+ # Business-to-business payments via Daraja B2B API.
7
+ class B2B < Client
30
8
  class << self
31
- def pay(amount, receiver_party, hash = {})
32
- new(amount, receiver_party, hash).send_payment
9
+ def call(amount, receiver_party, **options)
10
+ new(amount, receiver_party, **options).send_payment
33
11
  end
34
12
  end
35
13
 
36
- attr_reader :token, :amount, :receiver_party, :initiator, :security_credential, :command_id,
37
- :sender_identifier_type, :receiver_identifier_type, :party_a, :account_reference,
38
- :result_url, :queue_timeout_url
14
+ attr_reader :amount, :receiver_party
39
15
 
40
- def initialize(amount, receiver_party, hash = {})
41
- @token = MpesaStk::AccessToken.call(hash['key'], hash['secret'])
16
+ def initialize(amount, receiver_party, **options)
17
+ super(**options)
42
18
  @amount = amount
43
19
  @receiver_party = receiver_party
44
- @initiator = hash['initiator'] || ENV.fetch('initiator', nil)
45
- @security_credential = hash['security_credential'] || ENV.fetch('security_credential', nil)
46
- @command_id = hash['command_id'] || 'BusinessPayBill'
47
- @sender_identifier_type = hash['sender_identifier_type'] || '4'
48
- @receiver_identifier_type = hash['receiver_identifier_type'] || '4'
49
- @party_a = hash['party_a'] || ENV.fetch('business_short_code', nil)
50
- @account_reference = hash['account_reference'] || ''
51
- @result_url = hash['result_url'] || ENV.fetch('result_url', nil)
52
- @queue_timeout_url = hash['queue_timeout_url'] || ENV.fetch('queue_timeout_url', nil)
53
20
  end
54
21
 
55
22
  def send_payment
56
- response = HTTParty.post(url, headers: headers, body: body)
57
-
58
- raise StandardError, "Failed to send B2B payment: #{response.code} - #{response.body}" unless response.success?
59
-
60
- JSON.parse(response.body)
23
+ post('b2b_url', b2b_payload, error_message: 'Failed to send B2B payment')
61
24
  end
62
25
 
63
26
  private
64
27
 
65
- def url
66
- "#{ENV.fetch('base_url', nil)}#{ENV.fetch('b2b_url', nil)}"
67
- end
68
-
69
- def headers
70
- {
71
- 'Authorization' => "Bearer #{token}",
72
- 'Content-Type' => 'application/json'
73
- }
74
- end
75
-
76
- def body
28
+ def b2b_payload
77
29
  {
78
- Initiator: get_initiator,
79
- SecurityCredential: get_security_credential,
80
- CommandID: command_id,
81
- SenderIdentifierType: sender_identifier_type,
82
- RecieverIdentifierType: receiver_identifier_type,
30
+ Initiator: option('initiator'),
31
+ SecurityCredential: option('security_credential'),
32
+ CommandID: @options.fetch(:command_id, 'BusinessPayBill'),
33
+ SenderIdentifierType: @options.fetch(:sender_identifier_type, '4'),
34
+ RecieverIdentifierType: @options.fetch(:receiver_identifier_type, '4'),
83
35
  Amount: amount.to_s,
84
- PartyA: get_party_a,
36
+ PartyA: option('business_short_code', :party_a),
85
37
  PartyB: receiver_party,
86
- AccountReference: account_reference,
87
- QueueTimeOutURL: get_queue_timeout_url,
88
- ResultURL: get_result_url
89
- }.to_json
90
- end
91
-
92
- def get_initiator
93
- if initiator.nil? || initiator.eql?('')
94
- raise ArgumentError, 'Initiator is not defined' if ENV['initiator'].nil? || ENV['initiator'].eql?('')
95
-
96
- ENV.fetch('initiator', nil)
97
-
98
- else
99
- initiator
100
- end
101
- end
102
-
103
- def get_security_credential
104
- if security_credential.nil? || security_credential.eql?('')
105
- if ENV['security_credential'].nil? || ENV['security_credential'].eql?('')
106
- raise ArgumentError, 'Security Credential is not defined'
107
- end
108
-
109
- ENV.fetch('security_credential', nil)
110
-
111
- else
112
- security_credential
113
- end
114
- end
115
-
116
- def get_party_a
117
- if party_a.nil? || party_a.eql?('')
118
- if ENV['business_short_code'].nil? || ENV['business_short_code'].eql?('')
119
- raise ArgumentError, 'PartyA (Business Short Code) is not defined'
120
- end
121
-
122
- ENV.fetch('business_short_code', nil)
123
-
124
- else
125
- party_a
126
- end
127
- end
128
-
129
- def get_result_url
130
- if result_url.nil? || result_url.eql?('')
131
- raise ArgumentError, 'Result URL is not defined' if ENV['result_url'].nil? || ENV['result_url'].eql?('')
132
-
133
- ENV.fetch('result_url', nil)
134
-
135
- else
136
- result_url
137
- end
138
- end
139
-
140
- def get_queue_timeout_url
141
- if queue_timeout_url.nil? || queue_timeout_url.eql?('')
142
- if ENV['queue_timeout_url'].nil? || ENV['queue_timeout_url'].eql?('')
143
- raise ArgumentError, 'Queue Timeout URL is not defined'
144
- end
145
-
146
- ENV.fetch('queue_timeout_url', nil)
147
-
148
- else
149
- queue_timeout_url
150
- end
38
+ AccountReference: @options.fetch(:account_reference, ''),
39
+ QueueTimeOutURL: option('queue_timeout_url'),
40
+ ResultURL: option('result_url')
41
+ }
151
42
  end
152
43
  end
153
44
  end
data/lib/mpesa_stk/b2c.rb CHANGED
@@ -1,151 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright (c) 2018 mboya
4
- #
5
- # MIT License
6
- #
7
- # Permission is hereby granted, free of charge, to any person obtaining a copy
8
- # of this software and associated documentation files (the "Software"), to deal
9
- # in the Software without restriction, including without limitation the rights
10
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
- # copies of the Software, and to permit persons to whom the Software is
12
- # furnished to do so, subject to the following conditions:
13
- #
14
- # The above copyright notice and this permission notice shall be included in
15
- # all copies or substantial portions of the Software.
16
- #
17
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
- # THE SOFTWARE.
24
-
25
- require 'mpesa_stk/access_token'
3
+ require 'mpesa_stk/client'
26
4
 
27
5
  module MpesaStk
28
- # Business to Customer payment API - send money from business to customers
29
- class B2C
6
+ # Business-to-customer payments (salary, promotions, etc.).
7
+ class B2C < Client
30
8
  class << self
31
- def pay(amount, phone_number, hash = {})
32
- new(amount, phone_number, hash).send_payment
9
+ def call(amount, phone_number, **options)
10
+ new(amount, phone_number, **options).send_payment
33
11
  end
34
12
  end
35
13
 
36
- attr_reader :token, :amount, :phone_number, :initiator_name, :security_credential, :command_id, :party_a, :remarks,
37
- :result_url, :queue_timeout_url, :occasion
14
+ attr_reader :amount, :phone_number
38
15
 
39
- def initialize(amount, phone_number, hash = {})
40
- @token = MpesaStk::AccessToken.call(hash['key'], hash['secret'])
16
+ def initialize(amount, phone_number, **options)
17
+ super(**options)
41
18
  @amount = amount
42
19
  @phone_number = phone_number
43
- @initiator_name = hash['initiator_name'] || ENV.fetch('initiator_name', nil)
44
- @security_credential = hash['security_credential'] || ENV.fetch('security_credential', nil)
45
- @command_id = hash['command_id'] || 'BusinessPayment'
46
- @party_a = hash['party_a'] || ENV.fetch('business_short_code', nil)
47
- @remarks = hash['remarks'] || 'Payment'
48
- @result_url = hash['result_url'] || ENV.fetch('result_url', nil)
49
- @queue_timeout_url = hash['queue_timeout_url'] || ENV.fetch('queue_timeout_url', nil)
50
- @occasion = hash['occasion']
51
20
  end
52
21
 
53
22
  def send_payment
54
- response = HTTParty.post(url, headers: headers, body: body)
55
-
56
- raise StandardError, "Failed to send B2C payment: #{response.code} - #{response.body}" unless response.success?
57
-
58
- JSON.parse(response.body)
59
- end
60
-
61
- private
62
-
63
- def url
64
- "#{ENV.fetch('base_url', nil)}#{ENV.fetch('b2c_url', nil)}"
65
- end
66
-
67
- def headers
68
- {
69
- 'Authorization' => "Bearer #{token}",
70
- 'Content-Type' => 'application/json'
71
- }
72
- end
73
-
74
- def body
75
- body_hash = {
76
- InitiatorName: get_initiator_name,
77
- SecurityCredential: get_security_credential,
78
- CommandID: command_id,
23
+ body = {
24
+ InitiatorName: option('initiator_name'),
25
+ SecurityCredential: option('security_credential'),
26
+ CommandID: @options.fetch(:command_id, 'BusinessPayment'),
79
27
  Amount: amount.to_s,
80
- PartyA: get_party_a,
28
+ PartyA: option('business_short_code', :party_a),
81
29
  PartyB: phone_number,
82
- Remarks: remarks,
83
- QueueTimeOutURL: get_queue_timeout_url,
84
- ResultURL: get_result_url
30
+ Remarks: @options.fetch(:remarks, 'Payment'),
31
+ QueueTimeOutURL: option('queue_timeout_url'),
32
+ ResultURL: option('result_url')
85
33
  }
86
- body_hash[:Occasion] = occasion if occasion
87
- body_hash.to_json
88
- end
89
-
90
- def get_initiator_name
91
- if initiator_name.nil? || initiator_name.eql?('')
92
- raise ArgumentError, 'Initiator Name is not defined' if ENV['initiator_name'].nil? || ENV['initiator_name'].eql?('')
93
-
94
- ENV.fetch('initiator_name', nil)
95
-
96
- else
97
- initiator_name
98
- end
99
- end
100
-
101
- def get_security_credential
102
- if security_credential.nil? || security_credential.eql?('')
103
- if ENV['security_credential'].nil? || ENV['security_credential'].eql?('')
104
- raise ArgumentError, 'Security Credential is not defined'
105
- end
34
+ body[:Occasion] = @options[:occasion] if @options[:occasion]
106
35
 
107
- ENV.fetch('security_credential', nil)
108
-
109
- else
110
- security_credential
111
- end
112
- end
113
-
114
- def get_party_a
115
- if party_a.nil? || party_a.eql?('')
116
- if ENV['business_short_code'].nil? || ENV['business_short_code'].eql?('')
117
- raise ArgumentError, 'PartyA (Business Short Code) is not defined'
118
- end
119
-
120
- ENV.fetch('business_short_code', nil)
121
-
122
- else
123
- party_a
124
- end
125
- end
126
-
127
- def get_result_url
128
- if result_url.nil? || result_url.eql?('')
129
- raise ArgumentError, 'Result URL is not defined' if ENV['result_url'].nil? || ENV['result_url'].eql?('')
130
-
131
- ENV.fetch('result_url', nil)
132
-
133
- else
134
- result_url
135
- end
136
- end
137
-
138
- def get_queue_timeout_url
139
- if queue_timeout_url.nil? || queue_timeout_url.eql?('')
140
- if ENV['queue_timeout_url'].nil? || ENV['queue_timeout_url'].eql?('')
141
- raise ArgumentError, 'Queue Timeout URL is not defined'
142
- end
143
-
144
- ENV.fetch('queue_timeout_url', nil)
145
-
146
- else
147
- queue_timeout_url
148
- end
36
+ post('b2c_url', body, error_message: 'Failed to send B2C payment')
149
37
  end
150
38
  end
151
39
  end