mpesa_stk 1.3 → 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.
@@ -1,20 +1,50 @@
1
- require "mpesa_stk/access_token"
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'
2
27
 
3
28
  module MpesaStk
29
+ # Initiates STK Push payment for multiple applications with custom credentials
4
30
  class Push
5
31
  class << self
6
32
  def pay_bill(amount, phone_number, hash = {})
7
- new(amount, phone_number, "CustomerPayBillOnline", nil, hash["business_short_code"], hash["callback_url"], hash["business_passkey"], hash["key"], hash["secret"]).push_payment
33
+ new(amount, phone_number, 'CustomerPayBillOnline', nil, hash['business_short_code'], hash['callback_url'],
34
+ hash['business_passkey'], hash['key'], hash['secret']).push_payment
8
35
  end
9
36
 
10
37
  def buy_goods(amount, phone_number, hash = {})
11
- new(amount, phone_number, "CustomerBuyGoodsOnline", hash["till_number"], hash["business_short_code"], hash["callback_url"], hash["business_passkey"], hash["key"], hash["secret"]).push_payment
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
12
40
  end
13
41
  end
14
42
 
15
- attr_reader :token, :amount, :phone_number, :till_number, :business_short_code, :callback_url, :business_passkey, :transaction_type
43
+ attr_reader :token, :amount, :phone_number, :till_number, :business_short_code, :callback_url, :business_passkey,
44
+ :transaction_type
16
45
 
17
- def initialize(amount, phone_number, transaction_type, till_number = nil, business_short_code = nil, callback_url = nil, business_passkey = nil, key = nil, secret = nil)
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)
18
48
  @token = MpesaStk::AccessToken.call(key, secret)
19
49
  @transaction_type = transaction_type
20
50
  @till_number = till_number
@@ -27,19 +57,22 @@ module MpesaStk
27
57
 
28
58
  def push_payment
29
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
+
30
63
  JSON.parse(response.body)
31
64
  end
32
65
 
33
66
  private
34
67
 
35
68
  def url
36
- "#{ENV['base_url']}#{ENV['process_request_url']}"
69
+ "#{ENV.fetch('base_url', nil)}#{ENV.fetch('process_request_url', nil)}"
37
70
  end
38
71
 
39
72
  def headers
40
73
  {
41
- "Authorization" => "Bearer #{token}",
42
- "Content-Type" => "application/json"
74
+ 'Authorization' => "Bearer #{token}",
75
+ 'Content-Type' => 'application/json'
43
76
  }
44
77
  end
45
78
 
@@ -47,12 +80,12 @@ module MpesaStk
47
80
  {
48
81
  BusinessShortCode: get_business_short_code,
49
82
  Password: generate_password,
50
- Timestamp: "#{timestamp}",
83
+ Timestamp: timestamp.to_s,
51
84
  TransactionType: transaction_type,
52
- Amount: "#{amount}",
53
- PartyA: "#{phone_number}",
85
+ Amount: amount.to_s,
86
+ PartyA: phone_number.to_s,
54
87
  PartyB: get_till_number,
55
- PhoneNumber: "#{phone_number}",
88
+ PhoneNumber: phone_number.to_s,
56
89
  CallBackURL: get_callback_url,
57
90
  AccountReference: generate_bill_reference_number(5),
58
91
  TransactionDesc: generate_bill_reference_number(5)
@@ -65,7 +98,7 @@ module MpesaStk
65
98
  end
66
99
 
67
100
  def timestamp
68
- DateTime.now.strftime("%Y%m%d%H%M%S").to_i
101
+ DateTime.now.strftime('%Y%m%d%H%M%S').to_i
69
102
  end
70
103
 
71
104
  # shortcode
@@ -77,55 +110,51 @@ module MpesaStk
77
110
  end
78
111
 
79
112
  def get_business_short_code
80
- if business_short_code.nil? || business_short_code.eql?("")
81
- if ENV['business_short_code'].nil? || ENV['business_short_code'].eql?("")
82
- raise Exception.new "Business Short Code is not defined"
83
- else
84
- ENV['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'
85
116
  end
117
+
118
+ ENV.fetch('business_short_code', nil)
119
+
86
120
  else
87
121
  business_short_code
88
122
  end
89
123
  end
90
124
 
91
125
  def get_business_passkey
92
- if business_passkey.nil? || business_passkey.eql?("")
93
- if ENV['business_passkey'].nil? || ENV['business_passkey'].eql?("")
94
- raise Exception.new "Business Passkey is not defined"
95
- else
96
- ENV['business_passkey']
97
- end
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
+
98
131
  else
99
132
  business_passkey
100
133
  end
101
134
  end
102
135
 
103
136
  def get_callback_url
104
- if callback_url.nil? || callback_url.eql?("")
105
- if ENV['callback_url'].nil? || ENV['callback_url'].eql?("")
106
- raise Exception.new "Callback URL is not defined"
107
- else
108
- ENV['callback_url']
109
- end
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
+
110
142
  else
111
143
  callback_url
112
144
  end
113
145
  end
114
146
 
115
147
  def get_till_number
116
- if transaction_type.eql?("CustomerPayBillOnline")
148
+ if transaction_type.eql?('CustomerPayBillOnline')
117
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
+
118
155
  else
119
- if till_number.nil?
120
- if ENV['till_number'].nil? || ENV['till_number'].eql?("")
121
- raise Exception.new "Till number is not defined"
122
- else
123
- ENV['till_number']
124
- end
125
- else
126
- till_number
127
- end
156
+ till_number
128
157
  end
129
158
  end
130
159
  end
131
- end
160
+ end
@@ -1,6 +1,32 @@
1
- require "mpesa_stk/access_token"
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'
2
27
 
3
28
  module MpesaStk
29
+ # Initiates STK Push payment for a single application using ENV variables
4
30
  class PushPayment
5
31
  class << self
6
32
  def call(amount, phone_number)
@@ -18,33 +44,36 @@ module MpesaStk
18
44
 
19
45
  def push_payment
20
46
  response = HTTParty.post(url, headers: headers, body: body)
47
+
48
+ raise StandardError, "Failed to push payment: #{response.code} - #{response.body}" unless response.success?
49
+
21
50
  JSON.parse(response.body)
22
51
  end
23
52
 
24
53
  private
25
54
 
26
55
  def url
27
- "#{ENV['base_url']}#{ENV['process_request_url']}"
56
+ "#{ENV.fetch('base_url', nil)}#{ENV.fetch('process_request_url', nil)}"
28
57
  end
29
58
 
30
59
  def headers
31
60
  {
32
- "Authorization" => "Bearer #{token}",
33
- "Content-Type" => "application/json"
61
+ 'Authorization' => "Bearer #{token}",
62
+ 'Content-Type' => 'application/json'
34
63
  }
35
64
  end
36
65
 
37
66
  def body
38
67
  {
39
- BusinessShortCode: "#{ENV['business_short_code']}",
68
+ BusinessShortCode: ENV.fetch('business_short_code', nil).to_s,
40
69
  Password: generate_password,
41
- Timestamp: "#{timestamp}",
42
- TransactionType: "CustomerPayBillOnline",
43
- Amount: "#{amount}",
44
- PartyA: "#{phone_number}",
45
- PartyB: "#{ENV['business_short_code']}",
46
- PhoneNumber: "#{phone_number}",
47
- CallBackURL: "#{ENV['callback_url']}",
70
+ Timestamp: timestamp.to_s,
71
+ TransactionType: 'CustomerPayBillOnline',
72
+ Amount: amount.to_s,
73
+ PartyA: phone_number.to_s,
74
+ PartyB: ENV.fetch('business_short_code', nil).to_s,
75
+ PhoneNumber: phone_number.to_s,
76
+ CallBackURL: ENV.fetch('callback_url', nil).to_s,
48
77
  AccountReference: generate_bill_reference_number(5),
49
78
  TransactionDesc: generate_bill_reference_number(5)
50
79
  }.to_json
@@ -56,16 +85,15 @@ module MpesaStk
56
85
  end
57
86
 
58
87
  def timestamp
59
- DateTime.now.strftime("%Y%m%d%H%M%S").to_i
88
+ DateTime.now.strftime('%Y%m%d%H%M%S').to_i
60
89
  end
61
90
 
62
91
  # shortcode
63
92
  # passkey
64
93
  # timestamp
65
94
  def generate_password
66
- key = "#{ENV['business_short_code']}#{ENV['business_passkey']}#{timestamp}"
95
+ key = "#{ENV.fetch('business_short_code', nil)}#{ENV.fetch('business_passkey', nil)}#{timestamp}"
67
96
  Base64.encode64(key).split("\n").join
68
97
  end
69
-
70
98
  end
71
- end
99
+ end
@@ -0,0 +1,118 @@
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
+ # M-Pesa Ratiba (Standing Orders) - create recurring payments
29
+ class Ratiba
30
+ class << self
31
+ def create_standing_order(hash = {})
32
+ new(hash).create
33
+ end
34
+ end
35
+
36
+ attr_reader :token, :standing_order_name, :business_short_code, :transaction_type, :amount, :party_a,
37
+ :receiver_party_identifier_type, :callback_url, :account_reference, :transaction_desc,
38
+ :frequency, :start_date, :end_date
39
+
40
+ def initialize(hash = {})
41
+ @token = MpesaStk::AccessToken.call(hash['key'], hash['secret'])
42
+ @standing_order_name = hash['standing_order_name'] || 'Standing Order'
43
+ @business_short_code = hash['business_short_code'] || ENV.fetch('business_short_code', nil)
44
+ @transaction_type = hash['transaction_type'] || 'Standing Order Customer Pay Bill'
45
+ @amount = hash['amount']
46
+ @party_a = hash['party_a']
47
+ @receiver_party_identifier_type = hash['receiver_party_identifier_type'] || '4'
48
+ @callback_url = hash['callback_url'] || ENV.fetch('callback_url', nil)
49
+ @account_reference = hash['account_reference'] || ''
50
+ @transaction_desc = hash['transaction_desc'] || ''
51
+ @frequency = hash['frequency'] || '3'
52
+ @start_date = hash['start_date']
53
+ @end_date = hash['end_date']
54
+ end
55
+
56
+ def create
57
+ response = HTTParty.post(url, headers: headers, body: body)
58
+
59
+ raise StandardError, "Failed to create standing order: #{response.code} - #{response.body}" unless response.success?
60
+
61
+ JSON.parse(response.body)
62
+ end
63
+
64
+ private
65
+
66
+ def url
67
+ "#{ENV.fetch('base_url', nil)}#{ENV.fetch('ratiba_url', nil)}"
68
+ end
69
+
70
+ def headers
71
+ {
72
+ 'Authorization' => "Bearer #{token}",
73
+ 'Content-Type' => 'application/json'
74
+ }
75
+ end
76
+
77
+ def body
78
+ {
79
+ StandingOrderName: standing_order_name,
80
+ BusinessShortCode: get_business_short_code,
81
+ TransactionType: transaction_type,
82
+ Amount: amount.to_s,
83
+ PartyA: party_a,
84
+ ReceiverPartyIdentifierType: receiver_party_identifier_type,
85
+ CallBackURL: get_callback_url,
86
+ AccountReference: account_reference,
87
+ TransactionDesc: transaction_desc,
88
+ Frequency: frequency,
89
+ StartDate: start_date,
90
+ EndDate: end_date
91
+ }.to_json
92
+ end
93
+
94
+ def get_business_short_code
95
+ if business_short_code.nil? || business_short_code.eql?('')
96
+ if ENV['business_short_code'].nil? || ENV['business_short_code'].eql?('')
97
+ raise ArgumentError, 'Business Short Code is not defined'
98
+ end
99
+
100
+ ENV.fetch('business_short_code', nil)
101
+
102
+ else
103
+ business_short_code
104
+ end
105
+ end
106
+
107
+ def get_callback_url
108
+ if callback_url.nil? || callback_url.eql?('')
109
+ raise ArgumentError, 'Callback URL is not defined' if ENV['callback_url'].nil? || ENV['callback_url'].eql?('')
110
+
111
+ ENV.fetch('callback_url', nil)
112
+
113
+ else
114
+ callback_url
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,147 @@
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
+ # Reverse M-Pesa transactions (full or partial)
29
+ class Reversal
30
+ class << self
31
+ def reverse(transaction_id, amount, hash = {})
32
+ new(transaction_id, amount, hash).reverse_transaction
33
+ end
34
+ end
35
+
36
+ attr_reader :token, :transaction_id, :amount, :initiator, :security_credential, :receiver_party,
37
+ :receiver_identifier_type, :result_url, :queue_timeout_url
38
+
39
+ def initialize(transaction_id, amount, hash = {})
40
+ @token = MpesaStk::AccessToken.call(hash['key'], hash['secret'])
41
+ @transaction_id = transaction_id
42
+ @amount = amount
43
+ @initiator = hash['initiator'] || ENV.fetch('initiator', nil)
44
+ @security_credential = hash['security_credential'] || ENV.fetch('security_credential', nil)
45
+ @receiver_party = hash['receiver_party'] || ENV.fetch('business_short_code', nil)
46
+ @receiver_identifier_type = hash['receiver_identifier_type'] || '4'
47
+ @result_url = hash['result_url'] || ENV.fetch('result_url', nil)
48
+ @queue_timeout_url = hash['queue_timeout_url'] || ENV.fetch('queue_timeout_url', nil)
49
+ end
50
+
51
+ def reverse_transaction
52
+ response = HTTParty.post(url, headers: headers, body: body)
53
+
54
+ raise StandardError, "Failed to reverse transaction: #{response.code} - #{response.body}" unless response.success?
55
+
56
+ JSON.parse(response.body)
57
+ end
58
+
59
+ private
60
+
61
+ def url
62
+ "#{ENV.fetch('base_url', nil)}#{ENV.fetch('reversal_url', nil)}"
63
+ end
64
+
65
+ def headers
66
+ {
67
+ 'Authorization' => "Bearer #{token}",
68
+ 'Content-Type' => 'application/json'
69
+ }
70
+ end
71
+
72
+ def body
73
+ {
74
+ Initiator: get_initiator,
75
+ SecurityCredential: get_security_credential,
76
+ CommandID: 'TransactionReversal',
77
+ TransactionID: transaction_id,
78
+ Amount: amount.to_s,
79
+ ReceiverParty: get_receiver_party,
80
+ RecieverIdentifierType: receiver_identifier_type,
81
+ ResultURL: get_result_url,
82
+ QueueTimeOutURL: get_queue_timeout_url
83
+ }.to_json
84
+ end
85
+
86
+ def get_initiator
87
+ if initiator.nil? || initiator.eql?('')
88
+ raise ArgumentError, 'Initiator is not defined' if ENV['initiator'].nil? || ENV['initiator'].eql?('')
89
+
90
+ ENV.fetch('initiator', nil)
91
+
92
+ else
93
+ initiator
94
+ end
95
+ end
96
+
97
+ def get_security_credential
98
+ if security_credential.nil? || security_credential.eql?('')
99
+ if ENV['security_credential'].nil? || ENV['security_credential'].eql?('')
100
+ raise ArgumentError, 'Security Credential is not defined'
101
+ end
102
+
103
+ ENV.fetch('security_credential', nil)
104
+
105
+ else
106
+ security_credential
107
+ end
108
+ end
109
+
110
+ def get_receiver_party
111
+ if receiver_party.nil? || receiver_party.eql?('')
112
+ if ENV['business_short_code'].nil? || ENV['business_short_code'].eql?('')
113
+ raise ArgumentError, 'Receiver Party (Business Short Code) is not defined'
114
+ end
115
+
116
+ ENV.fetch('business_short_code', nil)
117
+
118
+ else
119
+ receiver_party
120
+ end
121
+ end
122
+
123
+ def get_result_url
124
+ if result_url.nil? || result_url.eql?('')
125
+ raise ArgumentError, 'Result URL is not defined' if ENV['result_url'].nil? || ENV['result_url'].eql?('')
126
+
127
+ ENV.fetch('result_url', nil)
128
+
129
+ else
130
+ result_url
131
+ end
132
+ end
133
+
134
+ def get_queue_timeout_url
135
+ if queue_timeout_url.nil? || queue_timeout_url.eql?('')
136
+ if ENV['queue_timeout_url'].nil? || ENV['queue_timeout_url'].eql?('')
137
+ raise ArgumentError, 'Queue Timeout URL is not defined'
138
+ end
139
+
140
+ ENV.fetch('queue_timeout_url', nil)
141
+
142
+ else
143
+ queue_timeout_url
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,109 @@
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
+ # Query STK Push transaction status using CheckoutRequestID
30
+ class StkPushQuery
31
+ class << self
32
+ def query(checkout_request_id, hash = {})
33
+ new(checkout_request_id, hash).query_status
34
+ end
35
+ end
36
+
37
+ attr_reader :token, :checkout_request_id, :business_short_code, :business_passkey
38
+
39
+ def initialize(checkout_request_id, hash = {})
40
+ @token = MpesaStk::AccessToken.call(hash['key'], hash['secret'])
41
+ @checkout_request_id = checkout_request_id
42
+ @business_short_code = hash['business_short_code'] || ENV.fetch('business_short_code', nil)
43
+ @business_passkey = hash['business_passkey'] || ENV.fetch('business_passkey', nil)
44
+ end
45
+
46
+ def query_status
47
+ response = HTTParty.post(url, headers: headers, body: body)
48
+
49
+ raise StandardError, "Failed to query STK push status: #{response.code} - #{response.body}" unless response.success?
50
+
51
+ JSON.parse(response.body)
52
+ end
53
+
54
+ private
55
+
56
+ def url
57
+ "#{ENV.fetch('base_url', nil)}#{ENV.fetch('stk_push_query_url', nil)}"
58
+ end
59
+
60
+ def headers
61
+ {
62
+ 'Authorization' => "Bearer #{token}",
63
+ 'Content-Type' => 'application/json'
64
+ }
65
+ end
66
+
67
+ def body
68
+ {
69
+ BusinessShortCode: get_business_short_code,
70
+ Password: generate_password,
71
+ Timestamp: timestamp.to_s,
72
+ CheckoutRequestID: checkout_request_id
73
+ }.to_json
74
+ end
75
+
76
+ def timestamp
77
+ DateTime.now.strftime('%Y%m%d%H%M%S').to_i
78
+ end
79
+
80
+ def generate_password
81
+ key = "#{get_business_short_code}#{get_business_passkey}#{timestamp}"
82
+ Base64.encode64(key).split("\n").join
83
+ end
84
+
85
+ def get_business_short_code
86
+ if business_short_code.nil? || business_short_code.eql?('')
87
+ if ENV['business_short_code'].nil? || ENV['business_short_code'].eql?('')
88
+ raise ArgumentError, 'Business Short Code is not defined'
89
+ end
90
+
91
+ ENV.fetch('business_short_code', nil)
92
+
93
+ else
94
+ business_short_code
95
+ end
96
+ end
97
+
98
+ def get_business_passkey
99
+ if business_passkey.nil? || business_passkey.eql?('')
100
+ raise ArgumentError, 'Business Passkey is not defined' if ENV['business_passkey'].nil? || ENV['business_passkey'].eql?('')
101
+
102
+ ENV.fetch('business_passkey', nil)
103
+
104
+ else
105
+ business_passkey
106
+ end
107
+ end
108
+ end
109
+ end