mpesa_stk 1.2.1.1 → 2.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.
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
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'
26
+
27
+ module MpesaStk
28
+ # Customer to Business API - register URLs and simulate C2B payments
29
+ class C2B
30
+ class << self
31
+ def register_url(hash = {})
32
+ new(hash).register
33
+ end
34
+
35
+ def simulate(amount, phone_number, hash = {})
36
+ new(hash).simulate_payment(amount, phone_number)
37
+ end
38
+ end
39
+
40
+ attr_reader :token, :short_code, :response_type, :confirmation_url, :validation_url, :command_id, :bill_ref_number
41
+
42
+ def initialize(hash = {})
43
+ @token = MpesaStk::AccessToken.call(hash['key'], hash['secret'])
44
+ @short_code = hash['short_code'] || ENV.fetch('business_short_code', nil)
45
+ @response_type = hash['response_type'] || 'Completed'
46
+ @confirmation_url = hash['confirmation_url'] || ENV.fetch('confirmation_url', nil)
47
+ @validation_url = hash['validation_url']
48
+ @command_id = hash['command_id'] || 'CustomerPayBillOnline'
49
+ @bill_ref_number = hash['bill_ref_number'] || ''
50
+ end
51
+
52
+ def register
53
+ response = HTTParty.post(register_url_endpoint, headers: headers, body: register_body)
54
+
55
+ raise StandardError, "Failed to register C2B URL: #{response.code} - #{response.body}" unless response.success?
56
+
57
+ JSON.parse(response.body)
58
+ end
59
+
60
+ def simulate_payment(amount, phone_number)
61
+ response = HTTParty.post(simulate_url_endpoint, headers: headers, body: simulate_body(amount, phone_number))
62
+
63
+ raise StandardError, "Failed to simulate C2B payment: #{response.code} - #{response.body}" unless response.success?
64
+
65
+ JSON.parse(response.body)
66
+ end
67
+
68
+ private
69
+
70
+ def register_url_endpoint
71
+ "#{ENV.fetch('base_url', nil)}#{ENV.fetch('c2b_register_url', nil)}"
72
+ end
73
+
74
+ def simulate_url_endpoint
75
+ "#{ENV.fetch('base_url', nil)}#{ENV.fetch('c2b_simulate_url', nil)}"
76
+ end
77
+
78
+ def headers
79
+ {
80
+ 'Authorization' => "Bearer #{token}",
81
+ 'Content-Type' => 'application/json'
82
+ }
83
+ end
84
+
85
+ def register_body
86
+ body_hash = {
87
+ ShortCode: get_short_code,
88
+ ResponseType: response_type,
89
+ ConfirmationURL: get_confirmation_url
90
+ }
91
+ body_hash[:ValidationURL] = validation_url if validation_url
92
+ body_hash.to_json
93
+ end
94
+
95
+ def simulate_body(amount, phone_number)
96
+ {
97
+ ShortCode: get_short_code,
98
+ CommandID: command_id,
99
+ Amount: amount.to_s,
100
+ Msisdn: phone_number,
101
+ BillRefNumber: bill_ref_number
102
+ }.to_json
103
+ end
104
+
105
+ def get_short_code
106
+ if short_code.nil? || short_code.eql?('')
107
+ raise ArgumentError, 'Short Code is not defined' if ENV['business_short_code'].nil? || ENV['business_short_code'].eql?('')
108
+
109
+ ENV.fetch('business_short_code', nil)
110
+
111
+ else
112
+ short_code
113
+ end
114
+ end
115
+
116
+ def get_confirmation_url
117
+ if confirmation_url.nil? || confirmation_url.eql?('')
118
+ raise ArgumentError, 'Confirmation URL is not defined' if ENV['confirmation_url'].nil? || ENV['confirmation_url'].eql?('')
119
+
120
+ ENV.fetch('confirmation_url', nil)
121
+
122
+ else
123
+ confirmation_url
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
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'
26
+
27
+ module MpesaStk
28
+ # IMSI/SWAP Operations - query IMSI and SIM swap information for fraud prevention
29
+ class IMSI
30
+ class << self
31
+ def check_ati(customer_number, hash = {}, version: 'v1')
32
+ new(hash).check_ati(customer_number, version)
33
+ end
34
+ end
35
+
36
+ attr_reader :token
37
+
38
+ def initialize(hash = {})
39
+ @token = MpesaStk::AccessToken.call(hash['key'], hash['secret'])
40
+ end
41
+
42
+ def check_ati(customer_number, version = 'v1')
43
+ endpoint = version == 'v2' ? '/imsi/v2/checkATI' : '/imsi/v1/checkATI'
44
+ response = HTTParty.post(url(endpoint), headers: headers, body: body(customer_number))
45
+
46
+ raise StandardError, "Failed to check ATI: #{response.code} - #{response.body}" unless response.success?
47
+
48
+ JSON.parse(response.body)
49
+ end
50
+
51
+ private
52
+
53
+ def url(endpoint)
54
+ "#{ENV.fetch('base_url', nil)}#{endpoint}"
55
+ end
56
+
57
+ def headers
58
+ {
59
+ 'Authorization' => "Bearer #{token}",
60
+ 'Content-Type' => 'application/json'
61
+ }
62
+ end
63
+
64
+ def body(customer_number)
65
+ {
66
+ customerNumber: customer_number
67
+ }.to_json
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
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'
26
+ require 'securerandom'
27
+
28
+ module MpesaStk
29
+ # IoT SIM Management & Messaging - manage SIM cards and send/receive messages for IoT devices
30
+ class IoT
31
+ class << self
32
+ def sims(hash = {})
33
+ new(hash)
34
+ end
35
+
36
+ def messaging(hash = {})
37
+ new(hash)
38
+ end
39
+ end
40
+
41
+ attr_reader :token, :api_key, :vpn_group, :username
42
+
43
+ def initialize(hash = {})
44
+ @token = MpesaStk::AccessToken.call(hash['key'], hash['secret'])
45
+ @api_key = hash['api_key'] || ENV['iot_api_key'] || 'Yl4S3KEcr173mbeUdYdjf147IuG3rJ824ArMkP6Z'
46
+ @vpn_group = hash['vpn_group'] || ENV['vpn_group'] || ''
47
+ @username = hash['username'] || ENV['username'] || ''
48
+ end
49
+
50
+ # SIM Operations
51
+ def get_all_sims(start_at_index: 0, page_size: 10)
52
+ post_request('/allsims', {
53
+ vpnGroup: [vpn_group],
54
+ startAtIndex: start_at_index.to_s,
55
+ pageSize: page_size.to_s,
56
+ username: username
57
+ })
58
+ end
59
+
60
+ def query_lifecycle_status(msisdn)
61
+ post_request('/queryLifeCycleStatus', {
62
+ msisdn: msisdn,
63
+ vpnGroup: vpn_group,
64
+ username: username
65
+ })
66
+ end
67
+
68
+ def query_customer_info(msisdn)
69
+ post_request('/querycustomerinfo', {
70
+ msisdn: msisdn,
71
+ vpnGroup: vpn_group,
72
+ username: username
73
+ })
74
+ end
75
+
76
+ def sim_activation(msisdn)
77
+ post_request('/simactivation', {
78
+ msisdn: msisdn,
79
+ vpnGroup: vpn_group,
80
+ username: username
81
+ })
82
+ end
83
+
84
+ def get_activation_trends(start_date:, stop_date:)
85
+ post_request('/getactivationtrends', {
86
+ vpnGroup: vpn_group,
87
+ startDate: start_date,
88
+ stopDate: stop_date,
89
+ username: username
90
+ })
91
+ end
92
+
93
+ def rename_asset(msisdn, asset_name)
94
+ post_request('/renameasset', {
95
+ msisdn: msisdn,
96
+ vpnGroup: vpn_group,
97
+ username: username,
98
+ assetName: asset_name
99
+ })
100
+ end
101
+
102
+ def get_location_info(msisdn)
103
+ post_request('/getlocationinfo', {
104
+ msisdn: msisdn,
105
+ vpnGroup: vpn_group,
106
+ username: username
107
+ })
108
+ end
109
+
110
+ def suspend_unsuspend_sub(msisdn, product, operation)
111
+ post_request('/suspend_unsuspend_sub', {
112
+ msisdn: msisdn,
113
+ username: username,
114
+ vpnGroup: vpn_group,
115
+ product: product,
116
+ operation: operation
117
+ })
118
+ end
119
+
120
+ # Messaging Operations
121
+ def get_all_messages(page_no: 1, page_size: 10)
122
+ get_request("/getallmessages?pageNo=#{page_no}&pageSize=#{page_size}", {
123
+ vpnGroup: vpn_group
124
+ })
125
+ end
126
+
127
+ def search_messages(search_value, page_no: 1, page_size: 5)
128
+ get_request("/searchmessages?pageNo=#{page_no}&pageSize=#{page_size}", {
129
+ searchValue: search_value,
130
+ vpnGroup: vpn_group,
131
+ username: username
132
+ })
133
+ end
134
+
135
+ def filter_messages(start_date:, end_date:, status: '', page_no: 1, page_size: 10)
136
+ get_request("/filtermessages?pageNo=#{page_no}&pageSize=#{page_size}", {
137
+ startDate: start_date,
138
+ endDate: end_date,
139
+ status: status,
140
+ vpnGroup: vpn_group,
141
+ username: username
142
+ })
143
+ end
144
+
145
+ def send_single_message(msisdn, message)
146
+ post_request('/sendsinglemessage', {
147
+ msisdn: msisdn,
148
+ message: message,
149
+ vpnGroup: vpn_group,
150
+ username: username
151
+ })
152
+ end
153
+
154
+ def delete_message(message_id)
155
+ post_request('/deletemessage', {
156
+ id: message_id,
157
+ vpnGroup: vpn_group,
158
+ username: username
159
+ })
160
+ end
161
+
162
+ def delete_message_thread(msisdn)
163
+ post_request('/deleteMessageThread', {
164
+ msisdn: msisdn,
165
+ vpnGroup: vpn_group,
166
+ username: username
167
+ })
168
+ end
169
+
170
+ private
171
+
172
+ def base_url
173
+ "#{ENV.fetch('base_url', nil)}#{ENV.fetch('iot_base_url', nil)}"
174
+ end
175
+
176
+ def headers(msisdn: nil)
177
+ headers_hash = {
178
+ 'Authorization' => "Bearer #{token}",
179
+ 'Content-Type' => 'application/json',
180
+ 'x-correlation-conversationid' => SecureRandom.uuid,
181
+ 'x-source-system' => 'web-portal',
182
+ 'x-api-key' => api_key,
183
+ 'X-App' => 'web-portal',
184
+ 'X-MessageID' => SecureRandom.uuid
185
+ }
186
+ headers_hash['X-MSISDN'] = msisdn if msisdn
187
+ headers_hash
188
+ end
189
+
190
+ def post_request(endpoint, body)
191
+ response = HTTParty.post("#{base_url}#{endpoint}", headers: headers(msisdn: body[:msisdn]), body: body.to_json)
192
+
193
+ raise StandardError, "Failed IoT request: #{response.code} - #{response.body}" unless response.success?
194
+
195
+ JSON.parse(response.body)
196
+ end
197
+
198
+ def get_request(endpoint, body)
199
+ response = HTTParty.post("#{base_url}#{endpoint}", headers: headers, body: body.to_json)
200
+
201
+ raise StandardError, "Failed IoT request: #{response.code} - #{response.body}" unless response.success?
202
+
203
+ JSON.parse(response.body)
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
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'
26
+
27
+ module MpesaStk
28
+ # Pull Transactions API - register URLs and query historical transaction data
29
+ class PullTransactions
30
+ class << self
31
+ def register(hash = {})
32
+ new(hash).register_url
33
+ end
34
+
35
+ def query(start_date, end_date, hash = {})
36
+ new(hash).query_transactions(start_date, end_date)
37
+ end
38
+ end
39
+
40
+ attr_reader :token, :short_code, :request_type, :nominated_number, :callback_url, :offset_value
41
+
42
+ def initialize(hash = {})
43
+ @token = MpesaStk::AccessToken.call(hash['key'], hash['secret'])
44
+ @short_code = hash['short_code'] || ENV.fetch('business_short_code', nil)
45
+ @request_type = hash['request_type'] || ''
46
+ @nominated_number = hash['nominated_number'] || ''
47
+ @callback_url = hash['callback_url'] || ENV.fetch('callback_url', nil)
48
+ @offset_value = hash['offset_value'] || '0'
49
+ end
50
+
51
+ def register_url
52
+ response = HTTParty.post(register_endpoint, headers: headers, body: register_body)
53
+
54
+ unless response.success?
55
+ raise StandardError, "Failed to register pull transactions URL: #{response.code} - #{response.body}"
56
+ end
57
+
58
+ JSON.parse(response.body)
59
+ end
60
+
61
+ def query_transactions(start_date, end_date)
62
+ response = HTTParty.post(query_endpoint, headers: headers, body: query_body(start_date, end_date))
63
+
64
+ raise StandardError, "Failed to query pull transactions: #{response.code} - #{response.body}" unless response.success?
65
+
66
+ JSON.parse(response.body)
67
+ end
68
+
69
+ private
70
+
71
+ def register_endpoint
72
+ "#{ENV.fetch('base_url', nil)}#{ENV.fetch('pull_transactions_register_url', nil)}"
73
+ end
74
+
75
+ def query_endpoint
76
+ "#{ENV.fetch('base_url', nil)}#{ENV.fetch('pull_transactions_query_url', nil)}"
77
+ end
78
+
79
+ def headers
80
+ {
81
+ 'Authorization' => "Bearer #{token}",
82
+ 'Content-Type' => 'application/json'
83
+ }
84
+ end
85
+
86
+ def register_body
87
+ {
88
+ ShortCode: get_short_code,
89
+ RequestType: request_type,
90
+ NominatedNumber: nominated_number,
91
+ CallBackURL: get_callback_url
92
+ }.to_json
93
+ end
94
+
95
+ def query_body(start_date, end_date)
96
+ {
97
+ ShortCode: get_short_code,
98
+ StartDate: start_date,
99
+ EndDate: end_date,
100
+ OffSetValue: offset_value
101
+ }.to_json
102
+ end
103
+
104
+ def get_short_code
105
+ if short_code.nil? || short_code.eql?('')
106
+ raise ArgumentError, 'Short Code is not defined' if ENV['business_short_code'].nil? || ENV['business_short_code'].eql?('')
107
+
108
+ ENV.fetch('business_short_code', nil)
109
+
110
+ else
111
+ short_code
112
+ end
113
+ end
114
+
115
+ def get_callback_url
116
+ if callback_url.nil? || callback_url.eql?('')
117
+ raise ArgumentError, 'Callback URL is not defined' if ENV['callback_url'].nil? || ENV['callback_url'].eql?('')
118
+
119
+ ENV.fetch('callback_url', nil)
120
+
121
+ else
122
+ callback_url
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
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 'date'
26
+ require 'mpesa_stk/access_token'
27
+
28
+ module MpesaStk
29
+ # Initiates STK Push payment for multiple applications with custom credentials
30
+ class Push
31
+ class << self
32
+ def pay_bill(amount, phone_number, hash = {})
33
+ new(amount, phone_number, 'CustomerPayBillOnline', nil, hash['business_short_code'], hash['callback_url'],
34
+ hash['business_passkey'], hash['key'], hash['secret']).push_payment
35
+ end
36
+
37
+ def buy_goods(amount, phone_number, hash = {})
38
+ new(amount, phone_number, 'CustomerBuyGoodsOnline', hash['till_number'], hash['business_short_code'],
39
+ hash['callback_url'], hash['business_passkey'], hash['key'], hash['secret']).push_payment
40
+ end
41
+ end
42
+
43
+ attr_reader :token, :amount, :phone_number, :till_number, :business_short_code, :callback_url, :business_passkey,
44
+ :transaction_type
45
+
46
+ def initialize(amount, phone_number, transaction_type, till_number = nil, business_short_code = nil,
47
+ callback_url = nil, business_passkey = nil, key = nil, secret = nil)
48
+ @token = MpesaStk::AccessToken.call(key, secret)
49
+ @transaction_type = transaction_type
50
+ @till_number = till_number
51
+ @business_short_code = business_short_code
52
+ @callback_url = callback_url
53
+ @business_passkey = business_passkey
54
+ @amount = amount
55
+ @phone_number = phone_number
56
+ end
57
+
58
+ def push_payment
59
+ response = HTTParty.post(url, headers: headers, body: body)
60
+
61
+ raise StandardError, "Failed to push payment: #{response.code} - #{response.body}" unless response.success?
62
+
63
+ JSON.parse(response.body)
64
+ end
65
+
66
+ private
67
+
68
+ def url
69
+ "#{ENV.fetch('base_url', nil)}#{ENV.fetch('process_request_url', nil)}"
70
+ end
71
+
72
+ def headers
73
+ {
74
+ 'Authorization' => "Bearer #{token}",
75
+ 'Content-Type' => 'application/json'
76
+ }
77
+ end
78
+
79
+ def body
80
+ {
81
+ BusinessShortCode: get_business_short_code,
82
+ Password: generate_password,
83
+ Timestamp: timestamp.to_s,
84
+ TransactionType: transaction_type,
85
+ Amount: amount.to_s,
86
+ PartyA: phone_number.to_s,
87
+ PartyB: get_till_number,
88
+ PhoneNumber: phone_number.to_s,
89
+ CallBackURL: get_callback_url,
90
+ AccountReference: generate_bill_reference_number(5),
91
+ TransactionDesc: generate_bill_reference_number(5)
92
+ }.to_json
93
+ end
94
+
95
+ def generate_bill_reference_number(number)
96
+ charset = Array('A'..'Z') + Array('a'..'z')
97
+ Array.new(number) { charset.sample }.join
98
+ end
99
+
100
+ def timestamp
101
+ DateTime.now.strftime('%Y%m%d%H%M%S').to_i
102
+ end
103
+
104
+ # shortcode
105
+ # passkey
106
+ # timestamp
107
+ def generate_password
108
+ key = "#{get_business_short_code}#{get_business_passkey}#{timestamp}"
109
+ Base64.encode64(key).split("\n").join
110
+ end
111
+
112
+ def get_business_short_code
113
+ if business_short_code.nil? || business_short_code.eql?('')
114
+ if ENV['business_short_code'].nil? || ENV['business_short_code'].eql?('')
115
+ raise ArgumentError, 'Business Short Code is not defined'
116
+ end
117
+
118
+ ENV.fetch('business_short_code', nil)
119
+
120
+ else
121
+ business_short_code
122
+ end
123
+ end
124
+
125
+ def get_business_passkey
126
+ if business_passkey.nil? || business_passkey.eql?('')
127
+ raise ArgumentError, 'Business Passkey is not defined' if ENV['business_passkey'].nil? || ENV['business_passkey'].eql?('')
128
+
129
+ ENV.fetch('business_passkey', nil)
130
+
131
+ else
132
+ business_passkey
133
+ end
134
+ end
135
+
136
+ def get_callback_url
137
+ if callback_url.nil? || callback_url.eql?('')
138
+ raise ArgumentError, 'Callback URL is not defined' if ENV['callback_url'].nil? || ENV['callback_url'].eql?('')
139
+
140
+ ENV.fetch('callback_url', nil)
141
+
142
+ else
143
+ callback_url
144
+ end
145
+ end
146
+
147
+ def get_till_number
148
+ if transaction_type.eql?('CustomerPayBillOnline')
149
+ get_business_short_code
150
+ elsif till_number.nil?
151
+ raise ArgumentError, 'Till number is not defined' if ENV['till_number'].nil? || ENV['till_number'].eql?('')
152
+
153
+ ENV.fetch('till_number', nil)
154
+
155
+ else
156
+ till_number
157
+ end
158
+ end
159
+ end
160
+ end