pesapal 1.5.6 → 1.6.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.
- checksums.yaml +4 -4
- data/.hound.yml +3 -0
- data/.rubocop.yml +15 -0
- data/.travis.yml +2 -1
- data/CHANGELOG.md +9 -0
- data/CONTRIBUTING.md +29 -0
- data/lib/generators/pesapal/install_generator.rb +8 -7
- data/lib/pesapal.rb +0 -1
- data/lib/pesapal/helper/details.rb +18 -14
- data/lib/pesapal/helper/post.rb +17 -17
- data/lib/pesapal/helper/status.rb +7 -7
- data/lib/pesapal/merchant.rb +28 -28
- data/lib/pesapal/oauth.rb +8 -9
- data/lib/pesapal/railtie.rb +7 -5
- data/lib/pesapal/version.rb +1 -1
- data/pesapal.gemspec +7 -4
- data/spec/pesapal_merchant_spec.rb +142 -182
- data/spec/spec_helper.rb +1 -0
- metadata +34 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8af57eb900f2972e33b0415d47f6dd829bf1e292
|
4
|
+
data.tar.gz: 73ea9770d4c51b81fcc92bff9dfaaac46f1779c3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5594d62643eec3357558ea3f133735be428c682e58e9f3040aaa2196e5aa758999b62e124e9956af5f0b42c96b681fbf0fc71b79077db24a49f222dacd5abe2a
|
7
|
+
data.tar.gz: dfe9baaca9c4488dcaa6e4744975e65937ebc013152033fca9912059e8dbbdca7ccfd038ea7acbf81116a3defbe93dc5ca35391471964b8f4a0c1c50a2923b5f
|
data/.hound.yml
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
AllCops:
|
2
|
+
Include:
|
3
|
+
- '**/*.gemspec'
|
4
|
+
- '**/Gemfile'
|
5
|
+
- '**/Rakefile'
|
6
|
+
Exclude:
|
7
|
+
- 'vendor/**/*'
|
8
|
+
Metrics/LineLength:
|
9
|
+
Enabled: false
|
10
|
+
Metrics/ClassLength:
|
11
|
+
CountComments: false
|
12
|
+
Max: 150
|
13
|
+
Metrics/MethodLength:
|
14
|
+
CountComments: false
|
15
|
+
Max: 20
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,15 @@
|
|
1
1
|
Changelog
|
2
2
|
=========
|
3
3
|
|
4
|
+
v1.6.0
|
5
|
+
------
|
6
|
+
|
7
|
+
* Enforce minimum required Ruby version, gem is tested for Ruby '>= 1.9.3'
|
8
|
+
* Add Rubocop as a development dependency and Hound to the workflow since we will use them to detect style guide violations
|
9
|
+
* Code cleanup here and there
|
10
|
+
* Avoid using set_ on method name, this changes the API slightly since we can't use set_env which was public
|
11
|
+
|
12
|
+
|
4
13
|
v1.5.6
|
5
14
|
------
|
6
15
|
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
CONTRIBUTING
|
2
|
+
============
|
3
|
+
|
4
|
+
Submitting a Pull Request
|
5
|
+
-------------------------
|
6
|
+
|
7
|
+
1. [Fork the repository][fork].
|
8
|
+
2. [Create a topic branch][branch] (`git checkout -b BRANCH_NAME`).
|
9
|
+
3. [Install bundler][bundler].
|
10
|
+
4. Check that tests pass with `rspec spec`.
|
11
|
+
5. Write a failing test to capture existing bug or lack of feature.
|
12
|
+
6. Run `rspec spec` to verify that test fails.
|
13
|
+
7. Implement your feature or bug fix.
|
14
|
+
8. Ensure tests pass.
|
15
|
+
9. If it's a new feature or a bug fix, please add an entry to the CHANGELOG file.
|
16
|
+
10. Check code style violations using [Rubocop][rubocop].
|
17
|
+
11. Add a commit (`git commit -am 'AWESOME COMMIT MESSAGE'`).
|
18
|
+
12. Push your changes to the branch (`git push origin BRANCH_NAME`).
|
19
|
+
13. [Submit a pull request.][pr]
|
20
|
+
14. You will get some feedback and may need to push additional commits
|
21
|
+
with more fixes to the same branch; this will update your pull request
|
22
|
+
automatically.
|
23
|
+
|
24
|
+
[branch]: http://git-scm.com/book/en/Git-Branching-Branching-Workflows#Topic-Branches
|
25
|
+
[bundler]: http://bundler.io
|
26
|
+
[fork]: https://help.github.com/articles/fork-a-repo/
|
27
|
+
[pr]: https://help.github.com/articles/using-pull-requests
|
28
|
+
[rubocop]: https://github.com/bbatsov/rubocop
|
29
|
+
|
@@ -1,19 +1,20 @@
|
|
1
|
+
# Since generators are not specific to this project, it's preferable you refer
|
2
|
+
# to the official documentation on the Rails site here;
|
3
|
+
# http://guides.rubyonrails.org/generators.html
|
1
4
|
module Pesapal
|
2
|
-
# Since generators are not specific to this project, it's preferable you refer
|
3
|
-
# to the official documentation on the Rails site here;
|
4
|
-
# http://guides.rubyonrails.org/generators.html
|
5
5
|
module Generators
|
6
|
+
# Install pesapal generator
|
6
7
|
class InstallGenerator < Rails::Generators::Base
|
7
|
-
source_root File.expand_path(
|
8
|
+
source_root File.expand_path('../../templates', __FILE__)
|
8
9
|
|
9
|
-
desc
|
10
|
+
desc 'Creates a Pesapal config file to your application.'
|
10
11
|
|
11
12
|
def copy_config
|
12
|
-
copy_file
|
13
|
+
copy_file 'pesapal.yml', 'config/pesapal.yml'
|
13
14
|
end
|
14
15
|
|
15
16
|
def show_readme
|
16
|
-
readme
|
17
|
+
readme 'README.md' if behavior == :invoke
|
17
18
|
end
|
18
19
|
end
|
19
20
|
end
|
data/lib/pesapal.rb
CHANGED
@@ -4,9 +4,11 @@ module Pesapal
|
|
4
4
|
# Contains helper methods relating to any queries for payment details. See
|
5
5
|
# {Pesapal::Merchant#query_payment_details} source.
|
6
6
|
module Details
|
7
|
-
# Prepares parameters to be used during the QueryPaymentDetails oAuth 1.0
|
7
|
+
# Prepares parameters to be used during the QueryPaymentDetails oAuth 1.0
|
8
|
+
# call.
|
8
9
|
#
|
9
|
-
# The QueryPaymentDetails oAuth 1.0 call requires the following
|
10
|
+
# The QueryPaymentDetails oAuth 1.0 call requires the following
|
11
|
+
# parameters;
|
10
12
|
#
|
11
13
|
# 1. `oauth_consumer_key` - your Pesapal consumer key sent to you via email or obtained from the dashboard
|
12
14
|
# 2. `oauth_nonce` - a random string, uniquely generated for each request. See [section 8 of the oAuth 1.0 spec][3]
|
@@ -17,10 +19,11 @@ module Pesapal
|
|
17
19
|
# 7. `pesapal_merchant_reference` - the transaction merchant reference (same as `merchant_reference` defined below)
|
18
20
|
# 8. `pesapal_transaction_tracking_id` - the transaction tracking id (same as `transaction_tracking_id` defined below)
|
19
21
|
#
|
20
|
-
# This method generates all the above **except** the `oauth_signature`
|
21
|
-
# is generated later by {Pesapal::Oauth.generate_oauth_signature}
|
22
|
-
# generation of this `oauth_signature` requires these parameters as
|
23
|
-
# anyway. See [section 9.2.1 of the oAuth 1.0 spec][1] for more
|
22
|
+
# This method generates all the above **except** the `oauth_signature`
|
23
|
+
# which is generated later by {Pesapal::Oauth.generate_oauth_signature}
|
24
|
+
# since generation of this `oauth_signature` requires these parameters as
|
25
|
+
# inputs anyway. See [section 9.2.1 of the oAuth 1.0 spec][1] for more
|
26
|
+
# details.
|
24
27
|
#
|
25
28
|
# [1]: http://oauth.net/core/1.0/#anchor16
|
26
29
|
# [2]: http://oauth.net/core/1.0/#signing_process
|
@@ -36,16 +39,17 @@ module Pesapal
|
|
36
39
|
# @param transaction_tracking_id [String] the unique id assigned by Pesapal
|
37
40
|
# to the transaction after it's posted
|
38
41
|
#
|
39
|
-
# @return [Hash] parameters to be used in generating the oAuth 1.0 URL
|
42
|
+
# @return [Hash] parameters to be used in generating the oAuth 1.0 URL
|
43
|
+
# query parameters and the `oauth_signature` itself.
|
40
44
|
def self.set_parameters(consumer_key, merchant_reference, transaction_tracking_id)
|
41
45
|
timestamp = Time.now.to_i.to_s
|
42
|
-
{ :
|
43
|
-
:
|
44
|
-
:
|
45
|
-
:
|
46
|
-
:
|
47
|
-
:
|
48
|
-
:
|
46
|
+
{ oauth_consumer_key: consumer_key,
|
47
|
+
oauth_nonce: "#{timestamp}" + Pesapal::Oauth.generate_nonce(12),
|
48
|
+
oauth_signature_method: 'HMAC-SHA1',
|
49
|
+
oauth_timestamp: "#{timestamp}",
|
50
|
+
oauth_version: '1.0',
|
51
|
+
pesapal_merchant_reference: merchant_reference,
|
52
|
+
pesapal_transaction_tracking_id: transaction_tracking_id
|
49
53
|
}
|
50
54
|
end
|
51
55
|
end
|
data/lib/pesapal/helper/post.rb
CHANGED
@@ -82,15 +82,15 @@ module Pesapal
|
|
82
82
|
#
|
83
83
|
# ```
|
84
84
|
# order_details = {
|
85
|
-
# :
|
86
|
-
# :
|
87
|
-
# :
|
88
|
-
# :
|
89
|
-
# :
|
90
|
-
# :
|
91
|
-
# :
|
92
|
-
# :
|
93
|
-
# :
|
85
|
+
# amount: 1000,
|
86
|
+
# description: 'this is the transaction description',
|
87
|
+
# type: 'MERCHANT',
|
88
|
+
# reference: '808-707-606',
|
89
|
+
# first_name: 'Swaleh',
|
90
|
+
# last_name: 'Mdoe',
|
91
|
+
# email: 'user@example.com',
|
92
|
+
# phonenumber: '+254722222222',
|
93
|
+
# currency: 'KES'
|
94
94
|
# }
|
95
95
|
# ```
|
96
96
|
#
|
@@ -124,7 +124,7 @@ module Pesapal
|
|
124
124
|
|
125
125
|
# Encode the XML
|
126
126
|
encoder = HTMLEntities.new(:xhtml1)
|
127
|
-
|
127
|
+
encoder.encode post_xml
|
128
128
|
end
|
129
129
|
|
130
130
|
# Prepares parameters to be used during the PostPesapalDirectOrderV4 oAuth 1.0 call.
|
@@ -163,13 +163,13 @@ module Pesapal
|
|
163
163
|
# @return [Hash] parameters to be used in generating the oAuth 1.0 URL query parameters and the `oauth_signature` itself.
|
164
164
|
def self.set_parameters(callback_url, consumer_key, post_xml)
|
165
165
|
timestamp = Time.now.to_i.to_s
|
166
|
-
{ :
|
167
|
-
:
|
168
|
-
:
|
169
|
-
:
|
170
|
-
:
|
171
|
-
:
|
172
|
-
:
|
166
|
+
{ oauth_callback: callback_url,
|
167
|
+
oauth_consumer_key: consumer_key,
|
168
|
+
oauth_nonce: "#{timestamp}" + Pesapal::Oauth.generate_nonce(12),
|
169
|
+
oauth_signature_method: 'HMAC-SHA1',
|
170
|
+
oauth_timestamp: "#{timestamp}",
|
171
|
+
oauth_version: '1.0',
|
172
|
+
pesapal_request_data: post_xml
|
173
173
|
}
|
174
174
|
end
|
175
175
|
end
|
@@ -39,15 +39,15 @@ module Pesapal
|
|
39
39
|
# @return [Hash] parameters to be used in generating the oAuth 1.0 URL query parameters and the `oauth_signature` itself.
|
40
40
|
def self.set_parameters(consumer_key, merchant_reference, transaction_tracking_id = nil)
|
41
41
|
timestamp = Time.now.to_i.to_s
|
42
|
-
params = { :
|
43
|
-
:
|
44
|
-
:
|
45
|
-
:
|
46
|
-
:
|
47
|
-
:
|
42
|
+
params = { oauth_consumer_key: consumer_key,
|
43
|
+
oauth_nonce: "#{timestamp}" + Pesapal::Oauth.generate_nonce(12),
|
44
|
+
oauth_signature_method: 'HMAC-SHA1',
|
45
|
+
oauth_timestamp: "#{timestamp}",
|
46
|
+
oauth_version: '1.0',
|
47
|
+
pesapal_merchant_reference: merchant_reference
|
48
48
|
}
|
49
49
|
params[:pesapal_transaction_tracking_id] = transaction_tracking_id unless transaction_tracking_id.nil?
|
50
|
-
|
50
|
+
params
|
51
51
|
end
|
52
52
|
end
|
53
53
|
end
|
data/lib/pesapal/merchant.rb
CHANGED
@@ -140,17 +140,17 @@ module Pesapal
|
|
140
140
|
# }
|
141
141
|
# ```
|
142
142
|
#
|
143
|
-
# @note You can change the environment at runtime using {#
|
143
|
+
# @note You can change the environment at runtime using {#change_env}
|
144
144
|
#
|
145
145
|
# @param env [Symbol] the environment we want to use i.e. `:development` or
|
146
146
|
# `:production`. Leaving it blank sets environment intelligently to
|
147
147
|
# `Rails.env` (if Rails) or `:development` (if non-Rails).
|
148
148
|
def initialize(env = false)
|
149
|
-
|
149
|
+
change_env env
|
150
150
|
if defined?(Rails)
|
151
|
-
|
151
|
+
configure Rails.application.config.pesapal_credentials
|
152
152
|
else
|
153
|
-
|
153
|
+
configure
|
154
154
|
end
|
155
155
|
end
|
156
156
|
|
@@ -159,7 +159,7 @@ module Pesapal
|
|
159
159
|
# PesaPal will present the user with a page which contains the available
|
160
160
|
# payment options and will redirect to your site to the _callback url_ once
|
161
161
|
# the user has completed the payment process. A tracking id will be returned
|
162
|
-
# as a query parameter
|
162
|
+
# as a query parameter - this can be used subsequently to track the payment
|
163
163
|
# status on Pesapal for the transaction later on.
|
164
164
|
#
|
165
165
|
# Generating the URL is a 3-step process:
|
@@ -190,7 +190,7 @@ module Pesapal
|
|
190
190
|
@params = Pesapal::Helper::Post.set_parameters(@config[:callback_url], @config[:consumer_key], @post_xml)
|
191
191
|
|
192
192
|
# generate oauth signature and add signature to the request parameters
|
193
|
-
@params[:oauth_signature] = Pesapal::Oauth
|
193
|
+
@params[:oauth_signature] = Pesapal::Oauth.generate_oauth_signature('GET', @api_endpoints[:postpesapaldirectorderv4], @params, @config[:consumer_secret], @token_secret)
|
194
194
|
|
195
195
|
# change params (with signature) to a query string
|
196
196
|
query_string = Pesapal::Oauth.generate_encoded_params_query_string @params
|
@@ -239,7 +239,7 @@ module Pesapal
|
|
239
239
|
@params = Pesapal::Helper::Details.set_parameters(@config[:consumer_key], merchant_reference, transaction_tracking_id)
|
240
240
|
|
241
241
|
# generate oauth signature and add signature to the request parameters
|
242
|
-
@params[:oauth_signature] = Pesapal::Oauth.generate_oauth_signature(
|
242
|
+
@params[:oauth_signature] = Pesapal::Oauth.generate_oauth_signature('GET', @api_endpoints[:querypaymentdetails], @params, @config[:consumer_secret], @token_secret)
|
243
243
|
|
244
244
|
# change params (with signature) to a query string
|
245
245
|
query_string = Pesapal::Oauth.generate_encoded_params_query_string @params
|
@@ -255,10 +255,10 @@ module Pesapal
|
|
255
255
|
response = CGI.parse response.body
|
256
256
|
response = response['pesapal_response_data'][0].split(',')
|
257
257
|
|
258
|
-
{ :
|
259
|
-
:
|
260
|
-
:
|
261
|
-
:
|
258
|
+
{ method: response[1],
|
259
|
+
status: response[2],
|
260
|
+
merchant_reference: response[3],
|
261
|
+
transaction_tracking_id: response[0]
|
262
262
|
}
|
263
263
|
end
|
264
264
|
|
@@ -294,7 +294,7 @@ module Pesapal
|
|
294
294
|
@params = Pesapal::Helper::Status.set_parameters(@config[:consumer_key], merchant_reference, transaction_tracking_id)
|
295
295
|
|
296
296
|
# generate oauth signature and add signature to the request parameters
|
297
|
-
@params[:oauth_signature] = Pesapal::Oauth.generate_oauth_signature(
|
297
|
+
@params[:oauth_signature] = Pesapal::Oauth.generate_oauth_signature('GET', @api_endpoints[:querypaymentstatus], @params, @config[:consumer_secret], @token_secret)
|
298
298
|
|
299
299
|
# change params (with signature) to a query string
|
300
300
|
query_string = Pesapal::Oauth.generate_encoded_params_query_string @params
|
@@ -344,7 +344,7 @@ module Pesapal
|
|
344
344
|
#
|
345
345
|
# @return [Hash] contains Pesapal endpoints appropriate for the set
|
346
346
|
# environment
|
347
|
-
def
|
347
|
+
def change_env(env = false)
|
348
348
|
env = env.to_s.downcase
|
349
349
|
if env == 'production'
|
350
350
|
@env = 'production'
|
@@ -352,7 +352,7 @@ module Pesapal
|
|
352
352
|
@env = 'development'
|
353
353
|
@env = Rails.env if defined?(Rails)
|
354
354
|
end
|
355
|
-
|
355
|
+
assign_endpoints
|
356
356
|
end
|
357
357
|
|
358
358
|
# Generates the appropriate IPN response depending on the status of the
|
@@ -381,8 +381,8 @@ module Pesapal
|
|
381
381
|
# @note It's up to you to send the response back to Pesapal by providing the
|
382
382
|
# `:response` back to the IPN. The hard part is done.
|
383
383
|
#
|
384
|
-
# @param notification_type [String] the IPN notification type,
|
385
|
-
# to CHANGE
|
384
|
+
# @param notification_type [String] the IPN notification type, overridden
|
385
|
+
# and set to CHANGE internally. Left here for extensibility.
|
386
386
|
#
|
387
387
|
# @param merchant_reference [String] the unique id generated for the
|
388
388
|
# transaction by your application before posting the order
|
@@ -393,12 +393,13 @@ module Pesapal
|
|
393
393
|
# @return [Hash] contains the status and IPN response that should be sent
|
394
394
|
# back to Pesapal
|
395
395
|
def ipn_listener(notification_type, merchant_reference, transaction_tracking_id)
|
396
|
+
notification_type = 'CHANGE'
|
396
397
|
status = query_payment_status(merchant_reference, transaction_tracking_id)
|
397
|
-
output = { :
|
398
|
+
output = { status: status, response: nil }
|
398
399
|
|
399
400
|
case status
|
400
|
-
when 'COMPLETED' then output[:response] = "pesapal_notification_type
|
401
|
-
when 'FAILED' then output[:response] = "pesapal_notification_type
|
401
|
+
when 'COMPLETED' then output[:response] = "pesapal_notification_type=#{notification_type}&pesapal_transaction_tracking_id=#{transaction_tracking_id}&pesapal_merchant_reference=#{merchant_reference}"
|
402
|
+
when 'FAILED' then output[:response] = "pesapal_notification_type=#{notification_type}&pesapal_transaction_tracking_id=#{transaction_tracking_id}&pesapal_merchant_reference=#{merchant_reference}"
|
402
403
|
end
|
403
404
|
|
404
405
|
output
|
@@ -406,8 +407,8 @@ module Pesapal
|
|
406
407
|
|
407
408
|
private
|
408
409
|
|
409
|
-
#
|
410
|
-
def
|
410
|
+
# Assign API endpoints depending on the environment.
|
411
|
+
def assign_endpoints
|
411
412
|
if @env == 'production'
|
412
413
|
@api_domain = 'https://www.pesapal.com'
|
413
414
|
else
|
@@ -422,13 +423,12 @@ module Pesapal
|
|
422
423
|
@api_endpoints
|
423
424
|
end
|
424
425
|
|
425
|
-
#
|
426
|
-
# remove unwanted data & uses default if nothing is input).
|
427
|
-
def
|
428
|
-
|
429
|
-
|
430
|
-
:
|
431
|
-
:consumer_secret => '<YOUR_CONSUMER_SECRET>'
|
426
|
+
# Configure credentials through hash that passed in (does a little
|
427
|
+
# processing to remove unwanted data & uses default if nothing is input).
|
428
|
+
def configure(consumer_details = {})
|
429
|
+
@config = { callback_url: 'http://0.0.0.0:3000/pesapal/callback',
|
430
|
+
consumer_key: '<YOUR_CONSUMER_KEY>',
|
431
|
+
consumer_secret: '<YOUR_CONSUMER_SECRET>'
|
432
432
|
}
|
433
433
|
|
434
434
|
valid_config_keys = @config.keys
|
data/lib/pesapal/oauth.rb
CHANGED
@@ -114,7 +114,6 @@ module Pesapal
|
|
114
114
|
#
|
115
115
|
# @return [String] valid signature base string.
|
116
116
|
def self.generate_signature_base_string(http_method, absolute_url, params)
|
117
|
-
|
118
117
|
# step 1: convert the http method to uppercase
|
119
118
|
http_method = http_method.upcase
|
120
119
|
|
@@ -156,11 +155,11 @@ module Pesapal
|
|
156
155
|
# > String MUST include the scheme, authority, and path, and MUST exclude
|
157
156
|
# > the query and fragment as defined by [RFC3986] section 3._
|
158
157
|
#
|
159
|
-
# > _If the absolute request URL is not available to the Service Provider
|
160
|
-
# > is always available to the Consumer), it can be constructed by
|
161
|
-
# > the scheme being used, the HTTP Host header, and the relative
|
162
|
-
# > request URL. If the Host header is not available, the Service
|
163
|
-
# > SHOULD use the host name communicated to the Consumer in the
|
158
|
+
# > _If the absolute request URL is not available to the Service Provider
|
159
|
+
# > (it is always available to the Consumer), it can be constructed by
|
160
|
+
# > combining the scheme being used, the HTTP Host header, and the relative
|
161
|
+
# > HTTP request URL. If the Host header is not available, the Service
|
162
|
+
# > Provider SHOULD use the host name communicated to the Consumer in the
|
164
163
|
# > documentation or other means._
|
165
164
|
#
|
166
165
|
# > _The Service Provider SHOULD document the form of URL used in the
|
@@ -184,7 +183,7 @@ module Pesapal
|
|
184
183
|
path = u.path
|
185
184
|
port = u.port
|
186
185
|
|
187
|
-
port = (scheme == 'http' && port != 80) || (scheme == 'https' && port != 443) ? ":#{port}" :
|
186
|
+
port = (scheme == 'http' && port != 80) || (scheme == 'https' && port != 443) ? ":#{port}" : ''
|
188
187
|
path = (path && path != '') ? path : '/'
|
189
188
|
|
190
189
|
"#{scheme}://#{host}#{port}#{path}"
|
@@ -209,8 +208,8 @@ module Pesapal
|
|
209
208
|
def self.parameter_encode(str)
|
210
209
|
# reserved character regexp, per section 5.1
|
211
210
|
reserved_characters = /[^a-zA-Z0-9\-\.\_\~]/
|
212
|
-
# Apparently we can't force_encoding on a frozen string since that would
|
213
|
-
# What we can do is work with a copy
|
211
|
+
# Apparently we can't force_encoding on a frozen string since that would
|
212
|
+
# modify it. What we can do is work with a copy
|
214
213
|
URI.escape(str.dup.to_s.force_encoding(Encoding::UTF_8), reserved_characters)
|
215
214
|
end
|
216
215
|
end
|
data/lib/pesapal/railtie.rb
CHANGED
@@ -9,14 +9,16 @@ module Pesapal
|
|
9
9
|
begin
|
10
10
|
config.pesapal_credentials = YAML.load(IO.read(path_to_yaml))[Rails.env]
|
11
11
|
rescue Errno::ENOENT
|
12
|
-
logger.info('YAML configuration file couldn\'t be found.')
|
12
|
+
logger.info('YAML configuration file couldn\'t be found.')
|
13
|
+
return
|
13
14
|
rescue Psych::SyntaxError
|
14
|
-
logger.info('YAML configuration file contains invalid syntax. Will use using defaults.')
|
15
|
+
logger.info('YAML configuration file contains invalid syntax. Will use using defaults.')
|
16
|
+
return
|
15
17
|
end
|
16
18
|
else
|
17
|
-
config.pesapal_credentials = { :
|
18
|
-
:
|
19
|
-
:
|
19
|
+
config.pesapal_credentials = { callback_url: 'http://0.0.0.0:3000/pesapal/callback',
|
20
|
+
consumer_key: '<YOUR_CONSUMER_KEY>',
|
21
|
+
consumer_secret: '<YOUR_CONSUMER_SECRET>'
|
20
22
|
}
|
21
23
|
end
|
22
24
|
end
|
data/lib/pesapal/version.rb
CHANGED
data/pesapal.gemspec
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
lib = File.expand_path('../lib', __FILE__)
|
2
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'english'
|
3
4
|
require 'pesapal/version'
|
4
5
|
|
5
6
|
Gem::Specification.new do |spec|
|
@@ -13,17 +14,19 @@ Gem::Specification.new do |spec|
|
|
13
14
|
spec.homepage = 'http://itsmrwave.github.io/pesapal-gem'
|
14
15
|
spec.license = 'MIT'
|
15
16
|
|
16
|
-
spec.files = `git ls-files`.split(
|
17
|
-
spec.executables = spec.files.grep(
|
18
|
-
spec.test_files = spec.files.grep(
|
17
|
+
spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
18
|
+
spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(/^(test|spec|features)\//)
|
19
20
|
spec.require_paths = ['lib']
|
20
21
|
|
21
|
-
spec.required_ruby_version = '>= 1.9.
|
22
|
+
spec.required_ruby_version = '>= 1.9.3'
|
22
23
|
|
23
24
|
spec.add_development_dependency 'bundler'
|
24
25
|
spec.add_development_dependency 'coveralls'
|
26
|
+
spec.add_development_dependency 'faker'
|
25
27
|
spec.add_development_dependency 'rake'
|
26
28
|
spec.add_development_dependency 'rspec'
|
29
|
+
spec.add_development_dependency 'rubocop'
|
27
30
|
spec.add_development_dependency 'webmock'
|
28
31
|
|
29
32
|
spec.add_runtime_dependency 'htmlentities'
|
@@ -2,401 +2,361 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Pesapal::Merchant do
|
4
4
|
|
5
|
+
let(:order_details) do
|
6
|
+
{ amount: 1000,
|
7
|
+
description: Faker::Lorem.sentence,
|
8
|
+
type: 'MERCHANT',
|
9
|
+
reference: '111-222-333',
|
10
|
+
first_name: Faker::Name.first_name,
|
11
|
+
last_name: Faker::Name.last_name,
|
12
|
+
email: Faker::Internet.email,
|
13
|
+
phonenumber: Faker::PhoneNumber.phone_number,
|
14
|
+
currency: 'KES' # ['USD', 'KES', 'UGX', 'TZA'].sample
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:default_credentials) do
|
19
|
+
{ callback_url: 'http://0.0.0.0:3000/pesapal/callback',
|
20
|
+
consumer_key: '<YOUR_CONSUMER_KEY>',
|
21
|
+
consumer_secret: '<YOUR_CONSUMER_SECRET>'
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
let(:demo_endpoints) do
|
26
|
+
{ postpesapaldirectorderv4: 'http://demo.pesapal.com/API/PostPesapalDirectOrderV4',
|
27
|
+
querypaymentstatus: 'http://demo.pesapal.com/API/QueryPaymentStatus',
|
28
|
+
querypaymentdetails: 'http://demo.pesapal.com/API/QueryPaymentDetails'
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
let(:production_endpoints) do
|
33
|
+
{ postpesapaldirectorderv4: 'https://www.pesapal.com/API/PostPesapalDirectOrderV4',
|
34
|
+
querypaymentstatus: 'https://www.pesapal.com/API/QueryPaymentStatus',
|
35
|
+
querypaymentdetails: 'https://www.pesapal.com/API/QueryPaymentDetails'
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
5
39
|
context 'when mode not specified' do
|
6
40
|
|
7
|
-
|
8
|
-
@pesapal = Pesapal::Merchant.new
|
9
|
-
end
|
41
|
+
let(:pesapal) { Pesapal::Merchant.new }
|
10
42
|
|
11
43
|
describe '#new' do
|
12
44
|
|
13
45
|
it 'is valid object' do
|
14
|
-
expect(
|
46
|
+
expect(pesapal).to be_an_instance_of(Pesapal::Merchant)
|
15
47
|
end
|
16
48
|
|
17
49
|
it 'sets default environment variable' do
|
18
|
-
expect(
|
50
|
+
expect(pesapal.send(:env)).to eq 'development'
|
19
51
|
end
|
20
52
|
|
21
53
|
it 'sets default credentials' do
|
22
|
-
expect(
|
54
|
+
expect(pesapal.config).to eq(default_credentials)
|
23
55
|
end
|
24
56
|
|
25
57
|
it 'sets default order details' do
|
26
|
-
expect(
|
58
|
+
expect(pesapal.order_details).to eq({})
|
27
59
|
end
|
28
60
|
end
|
29
61
|
|
30
|
-
describe '#
|
62
|
+
describe '#change_env' do
|
31
63
|
|
32
64
|
it 'sets default environment variable' do
|
33
|
-
|
34
|
-
expect(
|
65
|
+
pesapal.change_env
|
66
|
+
expect(pesapal.send(:env)).to eq 'development'
|
35
67
|
end
|
36
68
|
|
37
69
|
it 'sets endpoints for default environment' do
|
38
|
-
expect(
|
70
|
+
expect(pesapal.change_env).to eq(demo_endpoints)
|
39
71
|
end
|
40
72
|
end
|
41
73
|
|
42
74
|
describe '#generate_order_url' do
|
43
75
|
|
44
76
|
it 'generates iframe url string' do
|
45
|
-
|
46
|
-
|
47
|
-
:type => 'MERCHANT',
|
48
|
-
:reference => '111-222-333',
|
49
|
-
:first_name => 'Swaleh',
|
50
|
-
:last_name => 'Mdoe',
|
51
|
-
:email => 'test@example.com',
|
52
|
-
:phonenumber => '+254711000333',
|
53
|
-
:currency => 'KES'
|
54
|
-
}
|
55
|
-
expect(@pesapal.generate_order_url).to match /http:\/\/demo.pesapal.com\/API\/PostPesapalDirectOrderV4\?oauth_callback=.*oauth_consumer_key=.*oauth_nonce=.*oauth_signature=.*oauth_signature_method=HMAC-SHA1&oauth_timestamp.*oauth_version=1.0&pesapal_request_data=.*/
|
77
|
+
pesapal.order_details = order_details
|
78
|
+
expect(pesapal.generate_order_url).to match %r{http://demo.pesapal.com/API/PostPesapalDirectOrderV4\?oauth_callback=.*oauth_consumer_key=.*oauth_nonce=.*oauth_signature=.*oauth_signature_method=HMAC-SHA1&oauth_timestamp.*oauth_version=1.0&pesapal_request_data=.*}
|
56
79
|
end
|
57
80
|
end
|
58
81
|
|
59
82
|
describe '#query_payment_status' do
|
60
83
|
|
61
|
-
|
62
|
-
@request = stub_request(:get, /http:\/\/demo.pesapal.com\/API\/QueryPaymentStatus\?oauth_consumer_key=.*oauth_nonce=.*oauth_signature=.*oauth_signature_method=HMAC-SHA1&oauth_timestamp.*oauth_version=1.0&pesapal_merchant_reference=.*&pesapal_transaction_tracking_id=.*/)
|
63
|
-
end
|
84
|
+
let(:request) { stub_request(:get, %r{http://demo.pesapal.com/API/QueryPaymentStatus\?oauth_consumer_key=.*oauth_nonce=.*oauth_signature=.*oauth_signature_method=HMAC-SHA1&oauth_timestamp.*oauth_version=1.0&pesapal_merchant_reference=.*&pesapal_transaction_tracking_id=.*}) }
|
64
85
|
|
65
86
|
it 'gets pending payment status' do
|
66
|
-
|
67
|
-
expect(
|
87
|
+
request.to_return(status: 200, body: 'pesapal_response_data=PENDING')
|
88
|
+
expect(pesapal.query_payment_status('merchant_reference', 'transaction_tracking_id')).to eq('PENDING')
|
68
89
|
end
|
69
90
|
|
70
91
|
it 'gets completed payment status' do
|
71
|
-
|
72
|
-
expect(
|
92
|
+
request.to_return(status: 200, body: 'pesapal_response_data=COMPLETED')
|
93
|
+
expect(pesapal.query_payment_status('merchant_reference', 'transaction_tracking_id')).to eq('COMPLETED')
|
73
94
|
end
|
74
95
|
|
75
96
|
it 'gets failed payment status' do
|
76
|
-
|
77
|
-
expect(
|
97
|
+
request.to_return(status: 200, body: 'pesapal_response_data=FAILED')
|
98
|
+
expect(pesapal.query_payment_status('merchant_reference', 'transaction_tracking_id')).to eq('FAILED')
|
78
99
|
end
|
79
100
|
|
80
101
|
it 'gets invalid payment status' do
|
81
|
-
|
82
|
-
expect(
|
102
|
+
request.to_return(status: 200, body: 'pesapal_response_data=INVALID')
|
103
|
+
expect(pesapal.query_payment_status('merchant_reference', 'transaction_tracking_id')).to eq('INVALID')
|
83
104
|
end
|
84
105
|
end
|
85
106
|
|
86
107
|
describe '#query_payment_details' do
|
87
108
|
|
88
|
-
|
89
|
-
@request = stub_request(:get, /http:\/\/demo.pesapal.com\/API\/QueryPaymentDetails\?oauth_consumer_key=.*oauth_nonce=.*oauth_signature=.*oauth_signature_method=HMAC-SHA1&oauth_timestamp.*oauth_version=1.0&pesapal_merchant_reference=.*&pesapal_transaction_tracking_id=.*/)
|
90
|
-
end
|
109
|
+
let(:request) { stub_request(:get, %r{http://demo.pesapal.com/API/QueryPaymentDetails\?oauth_consumer_key=.*oauth_nonce=.*oauth_signature=.*oauth_signature_method=HMAC-SHA1&oauth_timestamp.*oauth_version=1.0&pesapal_merchant_reference=.*&pesapal_transaction_tracking_id=.*}) }
|
91
110
|
|
92
111
|
it 'gets pending payment details' do
|
93
|
-
|
94
|
-
expect(
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
112
|
+
request.to_return(status: 200, body: 'pesapal_response_data=transaction_tracking_id,payment_method,payment_status,merchant_reference')
|
113
|
+
expect(pesapal.query_payment_details('merchant_reference', 'transaction_tracking_id')).to eq(method: 'payment_method',
|
114
|
+
status: 'payment_status',
|
115
|
+
merchant_reference: 'merchant_reference',
|
116
|
+
transaction_tracking_id: 'transaction_tracking_id'
|
117
|
+
)
|
99
118
|
end
|
100
119
|
end
|
101
120
|
|
102
121
|
describe '#ipn_listener' do
|
103
122
|
|
104
|
-
|
105
|
-
@request = stub_request(:get, /http:\/\/demo.pesapal.com\/API\/QueryPaymentStatus\?oauth_consumer_key=.*oauth_nonce=.*oauth_signature=.*oauth_signature_method=HMAC-SHA1&oauth_timestamp.*oauth_version=1.0&pesapal_merchant_reference=.*&pesapal_transaction_tracking_id=.*/)
|
106
|
-
end
|
123
|
+
let(:request) { stub_request(:get, %r{http://demo.pesapal.com/API/QueryPaymentStatus\?oauth_consumer_key=.*oauth_nonce=.*oauth_signature=.*oauth_signature_method=HMAC-SHA1&oauth_timestamp.*oauth_version=1.0&pesapal_merchant_reference=.*&pesapal_transaction_tracking_id=.*}) }
|
107
124
|
|
108
125
|
it 'gets ipn response for pending status' do
|
109
|
-
|
110
|
-
expect(
|
126
|
+
request.to_return(status: 200, body: 'pesapal_response_data=PENDING')
|
127
|
+
expect(pesapal.ipn_listener('CHANGE', 'merchant_reference', 'transaction_tracking_id')).to eq(status: 'PENDING', response: nil)
|
111
128
|
end
|
112
129
|
|
113
130
|
it 'gets ipn response for completed status' do
|
114
|
-
|
115
|
-
expect(
|
131
|
+
request.to_return(status: 200, body: 'pesapal_response_data=COMPLETED')
|
132
|
+
expect(pesapal.ipn_listener('CHANGE', 'merchant_reference', 'transaction_tracking_id')).to eq(status: 'COMPLETED', response: 'pesapal_notification_type=CHANGE&pesapal_transaction_tracking_id=transaction_tracking_id&pesapal_merchant_reference=merchant_reference')
|
116
133
|
end
|
117
134
|
|
118
135
|
it 'gets ipn response for failed status' do
|
119
|
-
|
120
|
-
expect(
|
136
|
+
request.to_return(status: 200, body: 'pesapal_response_data=FAILED')
|
137
|
+
expect(pesapal.ipn_listener('CHANGE', 'merchant_reference', 'transaction_tracking_id')).to eq(status: 'FAILED', response: 'pesapal_notification_type=CHANGE&pesapal_transaction_tracking_id=transaction_tracking_id&pesapal_merchant_reference=merchant_reference')
|
121
138
|
end
|
122
139
|
|
123
140
|
it 'gets ipn response for invalid status' do
|
124
|
-
|
125
|
-
expect(
|
141
|
+
request.to_return(status: 200, body: 'pesapal_response_data=INVALID')
|
142
|
+
expect(pesapal.ipn_listener('CHANGE', 'merchant_reference', 'transaction_tracking_id')).to eq(status: 'INVALID', response: nil)
|
126
143
|
end
|
127
144
|
end
|
128
145
|
end
|
129
146
|
|
130
147
|
context 'when mode is specified as development' do
|
131
148
|
|
132
|
-
|
133
|
-
@pesapal = Pesapal::Merchant.new(:development)
|
134
|
-
end
|
149
|
+
let(:pesapal) { Pesapal::Merchant.new(:development) }
|
135
150
|
|
136
151
|
describe '#new(:development)' do
|
137
152
|
|
138
153
|
it 'is valid object' do
|
139
|
-
expect(
|
154
|
+
expect(pesapal).to be_an_instance_of(Pesapal::Merchant)
|
140
155
|
end
|
141
156
|
|
142
157
|
it 'sets environment variable' do
|
143
|
-
expect(
|
158
|
+
expect(pesapal.send(:env)).to eq 'development'
|
144
159
|
end
|
145
160
|
|
146
161
|
it 'sets credentials' do
|
147
|
-
expect(
|
162
|
+
expect(pesapal.config).to eq(default_credentials)
|
148
163
|
end
|
149
164
|
|
150
165
|
it 'sets order details' do
|
151
|
-
expect(
|
166
|
+
expect(pesapal.order_details).to eq({})
|
152
167
|
end
|
153
168
|
end
|
154
169
|
|
155
|
-
describe '#
|
170
|
+
describe '#change_env(:development)' do
|
156
171
|
|
157
172
|
it 'sets environment variable' do
|
158
|
-
|
159
|
-
expect(
|
173
|
+
pesapal.change_env :development
|
174
|
+
expect(pesapal.send(:env)).to eq 'development'
|
160
175
|
end
|
161
176
|
|
162
177
|
it 'sets endpoints for environment' do
|
163
|
-
expect(
|
178
|
+
expect(pesapal.change_env :development).to eq(demo_endpoints)
|
164
179
|
end
|
165
180
|
end
|
166
181
|
|
167
182
|
describe '#generate_order_url' do
|
168
183
|
|
169
184
|
it 'generates iframe url string' do
|
170
|
-
|
171
|
-
|
172
|
-
:type => 'MERCHANT',
|
173
|
-
:reference => '111-222-333',
|
174
|
-
:first_name => 'Swaleh',
|
175
|
-
:last_name => 'Mdoe',
|
176
|
-
:email => 'test@example.com',
|
177
|
-
:phonenumber => '+254711000333',
|
178
|
-
:currency => 'KES'
|
179
|
-
}
|
180
|
-
expect(@pesapal.generate_order_url).to match /http:\/\/demo.pesapal.com\/API\/PostPesapalDirectOrderV4\?oauth_callback=.*oauth_consumer_key=.*oauth_nonce=.*oauth_signature=.*oauth_signature_method=HMAC-SHA1&oauth_timestamp.*oauth_version=1.0&pesapal_request_data=.*/
|
185
|
+
pesapal.order_details = order_details
|
186
|
+
expect(pesapal.generate_order_url).to match %r{http://demo.pesapal.com/API/PostPesapalDirectOrderV4\?oauth_callback=.*oauth_consumer_key=.*oauth_nonce=.*oauth_signature=.*oauth_signature_method=HMAC-SHA1&oauth_timestamp.*oauth_version=1.0&pesapal_request_data=.*}
|
181
187
|
end
|
182
188
|
end
|
183
189
|
|
184
190
|
describe '#query_payment_status' do
|
185
191
|
|
186
|
-
|
187
|
-
@request = stub_request(:get, /http:\/\/demo.pesapal.com\/API\/QueryPaymentStatus\?oauth_consumer_key=.*oauth_nonce=.*oauth_signature=.*oauth_signature_method=HMAC-SHA1&oauth_timestamp.*oauth_version=1.0&pesapal_merchant_reference=.*&pesapal_transaction_tracking_id=.*/)
|
188
|
-
end
|
192
|
+
let(:request) { stub_request(:get, %r{http://demo.pesapal.com/API/QueryPaymentStatus\?oauth_consumer_key=.*oauth_nonce=.*oauth_signature=.*oauth_signature_method=HMAC-SHA1&oauth_timestamp.*oauth_version=1.0&pesapal_merchant_reference=.*&pesapal_transaction_tracking_id=.*}) }
|
189
193
|
|
190
194
|
it 'gets pending payment status' do
|
191
|
-
|
192
|
-
expect(
|
195
|
+
request.to_return(status: 200, body: 'pesapal_response_data=PENDING')
|
196
|
+
expect(pesapal.query_payment_status('merchant_reference', 'transaction_tracking_id')).to eq('PENDING')
|
193
197
|
end
|
194
198
|
|
195
199
|
it 'gets completed payment status' do
|
196
|
-
|
197
|
-
expect(
|
200
|
+
request.to_return(status: 200, body: 'pesapal_response_data=COMPLETED')
|
201
|
+
expect(pesapal.query_payment_status('merchant_reference', 'transaction_tracking_id')).to eq('COMPLETED')
|
198
202
|
end
|
199
203
|
|
200
204
|
it 'gets failed payment status' do
|
201
|
-
|
202
|
-
expect(
|
205
|
+
request.to_return(status: 200, body: 'pesapal_response_data=FAILED')
|
206
|
+
expect(pesapal.query_payment_status('merchant_reference', 'transaction_tracking_id')).to eq('FAILED')
|
203
207
|
end
|
204
208
|
|
205
209
|
it 'gets invalid payment status' do
|
206
|
-
|
207
|
-
expect(
|
210
|
+
request.to_return(status: 200, body: 'pesapal_response_data=INVALID')
|
211
|
+
expect(pesapal.query_payment_status('merchant_reference', 'transaction_tracking_id')).to eq('INVALID')
|
208
212
|
end
|
209
213
|
end
|
210
214
|
|
211
215
|
describe '#query_payment_details' do
|
212
216
|
|
213
|
-
|
214
|
-
@request = stub_request(:get, /http:\/\/demo.pesapal.com\/API\/QueryPaymentDetails\?oauth_consumer_key=.*oauth_nonce=.*oauth_signature=.*oauth_signature_method=HMAC-SHA1&oauth_timestamp.*oauth_version=1.0&pesapal_merchant_reference=.*&pesapal_transaction_tracking_id=.*/)
|
215
|
-
end
|
217
|
+
let(:request) { stub_request(:get, %r{http://demo.pesapal.com/API/QueryPaymentDetails\?oauth_consumer_key=.*oauth_nonce=.*oauth_signature=.*oauth_signature_method=HMAC-SHA1&oauth_timestamp.*oauth_version=1.0&pesapal_merchant_reference=.*&pesapal_transaction_tracking_id=.*}) }
|
216
218
|
|
217
219
|
it 'gets pending payment details' do
|
218
|
-
|
219
|
-
expect(
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
220
|
+
request.to_return(status: 200, body: 'pesapal_response_data=transaction_tracking_id,payment_method,payment_status,merchant_reference')
|
221
|
+
expect(pesapal.query_payment_details('merchant_reference', 'transaction_tracking_id')).to eq(method: 'payment_method',
|
222
|
+
status: 'payment_status',
|
223
|
+
merchant_reference: 'merchant_reference',
|
224
|
+
transaction_tracking_id: 'transaction_tracking_id'
|
225
|
+
)
|
224
226
|
end
|
225
227
|
end
|
226
228
|
|
227
229
|
describe '#ipn_listener' do
|
228
230
|
|
229
|
-
|
230
|
-
@request = stub_request(:get, /http:\/\/demo.pesapal.com\/API\/QueryPaymentStatus\?oauth_consumer_key=.*oauth_nonce=.*oauth_signature=.*oauth_signature_method=HMAC-SHA1&oauth_timestamp.*oauth_version=1.0&pesapal_merchant_reference=.*&pesapal_transaction_tracking_id=.*/)
|
231
|
-
end
|
231
|
+
let(:request) { stub_request(:get, %r{http://demo.pesapal.com/API/QueryPaymentStatus\?oauth_consumer_key=.*oauth_nonce=.*oauth_signature=.*oauth_signature_method=HMAC-SHA1&oauth_timestamp.*oauth_version=1.0&pesapal_merchant_reference=.*&pesapal_transaction_tracking_id=.*}) }
|
232
232
|
|
233
233
|
it 'gets ipn response for pending status' do
|
234
|
-
|
235
|
-
expect(
|
234
|
+
request.to_return(status: 200, body: 'pesapal_response_data=PENDING')
|
235
|
+
expect(pesapal.ipn_listener('CHANGE', 'merchant_reference', 'transaction_tracking_id')).to eq(status: 'PENDING', response: nil)
|
236
236
|
end
|
237
237
|
|
238
238
|
it 'gets ipn response for completed status' do
|
239
|
-
|
240
|
-
expect(
|
239
|
+
request.to_return(status: 200, body: 'pesapal_response_data=COMPLETED')
|
240
|
+
expect(pesapal.ipn_listener('CHANGE', 'merchant_reference', 'transaction_tracking_id')).to eq(status: 'COMPLETED', response: 'pesapal_notification_type=CHANGE&pesapal_transaction_tracking_id=transaction_tracking_id&pesapal_merchant_reference=merchant_reference')
|
241
241
|
end
|
242
242
|
|
243
243
|
it 'gets ipn response for failed status' do
|
244
|
-
|
245
|
-
expect(
|
244
|
+
request.to_return(status: 200, body: 'pesapal_response_data=FAILED')
|
245
|
+
expect(pesapal.ipn_listener('CHANGE', 'merchant_reference', 'transaction_tracking_id')).to eq(status: 'FAILED', response: 'pesapal_notification_type=CHANGE&pesapal_transaction_tracking_id=transaction_tracking_id&pesapal_merchant_reference=merchant_reference')
|
246
246
|
end
|
247
247
|
|
248
248
|
it 'gets ipn response for invalid status' do
|
249
|
-
|
250
|
-
expect(
|
249
|
+
request.to_return(status: 200, body: 'pesapal_response_data=INVALID')
|
250
|
+
expect(pesapal.ipn_listener('CHANGE', 'merchant_reference', 'transaction_tracking_id')).to eq(status: 'INVALID', response: nil)
|
251
251
|
end
|
252
252
|
end
|
253
253
|
end
|
254
254
|
|
255
255
|
context 'when mode is specified as production' do
|
256
256
|
|
257
|
-
|
258
|
-
@pesapal = Pesapal::Merchant.new(:production)
|
259
|
-
end
|
257
|
+
let(:pesapal) { Pesapal::Merchant.new(:production) }
|
260
258
|
|
261
259
|
describe '#new(:production)' do
|
262
260
|
|
263
261
|
it 'is valid object' do
|
264
|
-
expect(
|
262
|
+
expect(pesapal).to be_an_instance_of(Pesapal::Merchant)
|
265
263
|
end
|
266
264
|
|
267
265
|
it 'sets environment variable' do
|
268
|
-
expect(
|
266
|
+
expect(pesapal.send(:env)).to eq 'production'
|
269
267
|
end
|
270
268
|
|
271
269
|
it 'sets credentials' do
|
272
|
-
expect(
|
270
|
+
expect(pesapal.config).to eq(default_credentials)
|
273
271
|
end
|
274
272
|
|
275
273
|
it 'sets order details' do
|
276
|
-
expect(
|
274
|
+
expect(pesapal.order_details).to eq({})
|
277
275
|
end
|
278
276
|
end
|
279
277
|
|
280
|
-
describe '#
|
278
|
+
describe '#change_env(:production)' do
|
281
279
|
|
282
280
|
it 'sets environment variable' do
|
283
|
-
|
284
|
-
expect(
|
281
|
+
pesapal.change_env :production
|
282
|
+
expect(pesapal.send(:env)).to eq 'production'
|
285
283
|
end
|
286
284
|
|
287
285
|
it 'sets endpoints for environment' do
|
288
|
-
expect(
|
286
|
+
expect(pesapal.change_env :production).to eq(production_endpoints)
|
289
287
|
end
|
290
288
|
end
|
291
289
|
|
292
290
|
describe '#generate_order_url' do
|
293
291
|
|
294
292
|
it 'generates iframe url string' do
|
295
|
-
|
296
|
-
|
297
|
-
:type => 'MERCHANT',
|
298
|
-
:reference => '111-222-333',
|
299
|
-
:first_name => 'Swaleh',
|
300
|
-
:last_name => 'Mdoe',
|
301
|
-
:email => 'test@example.com',
|
302
|
-
:phonenumber => '+254711000333',
|
303
|
-
:currency => 'KES'
|
304
|
-
}
|
305
|
-
expect(@pesapal.generate_order_url).to match /https:\/\/www.pesapal.com\/API\/PostPesapalDirectOrderV4\?oauth_callback=.*oauth_consumer_key=.*oauth_nonce=.*oauth_signature=.*oauth_signature_method=HMAC-SHA1&oauth_timestamp.*oauth_version=1.0&pesapal_request_data=.*/
|
293
|
+
pesapal.order_details = order_details
|
294
|
+
expect(pesapal.generate_order_url).to match %r{https://www.pesapal.com/API/PostPesapalDirectOrderV4\?oauth_callback=.*oauth_consumer_key=.*oauth_nonce=.*oauth_signature=.*oauth_signature_method=HMAC-SHA1&oauth_timestamp.*oauth_version=1.0&pesapal_request_data=.*}
|
306
295
|
end
|
307
296
|
end
|
308
297
|
|
309
298
|
describe '#query_payment_status' do
|
310
299
|
|
311
|
-
|
312
|
-
@request = stub_request(:get, /https:\/\/www.pesapal.com\/API\/QueryPaymentStatus\?oauth_consumer_key=.*oauth_nonce=.*oauth_signature=.*oauth_signature_method=HMAC-SHA1&oauth_timestamp.*oauth_version=1.0&pesapal_merchant_reference=.*&pesapal_transaction_tracking_id=.*/)
|
313
|
-
end
|
300
|
+
let(:request) { stub_request(:get, %r{https://www.pesapal.com/API/QueryPaymentStatus\?oauth_consumer_key=.*oauth_nonce=.*oauth_signature=.*oauth_signature_method=HMAC-SHA1&oauth_timestamp.*oauth_version=1.0&pesapal_merchant_reference=.*&pesapal_transaction_tracking_id=.*}) }
|
314
301
|
|
315
302
|
it 'gets pending payment status' do
|
316
|
-
|
317
|
-
expect(
|
303
|
+
request.to_return(status: 200, body: 'pesapal_response_data=PENDING')
|
304
|
+
expect(pesapal.query_payment_status('merchant_reference', 'transaction_tracking_id')).to eq('PENDING')
|
318
305
|
end
|
319
306
|
|
320
307
|
it 'gets completed payment status' do
|
321
|
-
|
322
|
-
expect(
|
308
|
+
request.to_return(status: 200, body: 'pesapal_response_data=COMPLETED')
|
309
|
+
expect(pesapal.query_payment_status('merchant_reference', 'transaction_tracking_id')).to eq('COMPLETED')
|
323
310
|
end
|
324
311
|
|
325
312
|
it 'gets failed payment status' do
|
326
|
-
|
327
|
-
expect(
|
313
|
+
request.to_return(status: 200, body: 'pesapal_response_data=FAILED')
|
314
|
+
expect(pesapal.query_payment_status('merchant_reference', 'transaction_tracking_id')).to eq('FAILED')
|
328
315
|
end
|
329
316
|
|
330
317
|
it 'gets invalid payment status' do
|
331
|
-
|
332
|
-
expect(
|
318
|
+
request.to_return(status: 200, body: 'pesapal_response_data=INVALID')
|
319
|
+
expect(pesapal.query_payment_status('merchant_reference', 'transaction_tracking_id')).to eq('INVALID')
|
333
320
|
end
|
334
321
|
end
|
335
322
|
|
336
323
|
describe '#query_payment_details' do
|
337
324
|
|
338
|
-
|
339
|
-
@request = stub_request(:get, /https:\/\/www.pesapal.com\/API\/QueryPaymentDetails\?oauth_consumer_key=.*oauth_nonce=.*oauth_signature=.*oauth_signature_method=HMAC-SHA1&oauth_timestamp.*oauth_version=1.0&pesapal_merchant_reference=.*&pesapal_transaction_tracking_id=.*/)
|
340
|
-
end
|
325
|
+
let(:request) { stub_request(:get, %r{https://www.pesapal.com/API/QueryPaymentDetails\?oauth_consumer_key=.*oauth_nonce=.*oauth_signature=.*oauth_signature_method=HMAC-SHA1&oauth_timestamp.*oauth_version=1.0&pesapal_merchant_reference=.*&pesapal_transaction_tracking_id=.*}) }
|
341
326
|
|
342
327
|
it 'gets pending payment details' do
|
343
|
-
|
344
|
-
expect(
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
328
|
+
request.to_return(status: 200, body: 'pesapal_response_data=transaction_tracking_id,payment_method,payment_status,merchant_reference')
|
329
|
+
expect(pesapal.query_payment_details('merchant_reference', 'transaction_tracking_id')).to eq(method: 'payment_method',
|
330
|
+
status: 'payment_status',
|
331
|
+
merchant_reference: 'merchant_reference',
|
332
|
+
transaction_tracking_id: 'transaction_tracking_id'
|
333
|
+
)
|
349
334
|
end
|
350
335
|
end
|
351
336
|
|
352
337
|
describe '#ipn_listener' do
|
353
338
|
|
354
|
-
|
355
|
-
@request = stub_request(:get, /https:\/\/www.pesapal.com\/API\/QueryPaymentStatus\?oauth_consumer_key=.*oauth_nonce=.*oauth_signature=.*oauth_signature_method=HMAC-SHA1&oauth_timestamp.*oauth_version=1.0&pesapal_merchant_reference=.*&pesapal_transaction_tracking_id=.*/)
|
356
|
-
end
|
339
|
+
let(:request) { stub_request(:get, %r{https://www.pesapal.com/API/QueryPaymentStatus\?oauth_consumer_key=.*oauth_nonce=.*oauth_signature=.*oauth_signature_method=HMAC-SHA1&oauth_timestamp.*oauth_version=1.0&pesapal_merchant_reference=.*&pesapal_transaction_tracking_id=.*}) }
|
357
340
|
|
358
341
|
it 'gets ipn response for pending status' do
|
359
|
-
|
360
|
-
expect(
|
342
|
+
request.to_return(status: 200, body: 'pesapal_response_data=PENDING')
|
343
|
+
expect(pesapal.ipn_listener('CHANGE', 'merchant_reference', 'transaction_tracking_id')).to eq(status: 'PENDING', response: nil)
|
361
344
|
end
|
362
345
|
|
363
346
|
it 'gets ipn response for completed status' do
|
364
|
-
|
365
|
-
expect(
|
347
|
+
request.to_return(status: 200, body: 'pesapal_response_data=COMPLETED')
|
348
|
+
expect(pesapal.ipn_listener('CHANGE', 'merchant_reference', 'transaction_tracking_id')).to eq(status: 'COMPLETED', response: 'pesapal_notification_type=CHANGE&pesapal_transaction_tracking_id=transaction_tracking_id&pesapal_merchant_reference=merchant_reference')
|
366
349
|
end
|
367
350
|
|
368
351
|
it 'gets ipn response for failed status' do
|
369
|
-
|
370
|
-
expect(
|
352
|
+
request.to_return(status: 200, body: 'pesapal_response_data=FAILED')
|
353
|
+
expect(pesapal.ipn_listener('CHANGE', 'merchant_reference', 'transaction_tracking_id')).to eq(status: 'FAILED', response: 'pesapal_notification_type=CHANGE&pesapal_transaction_tracking_id=transaction_tracking_id&pesapal_merchant_reference=merchant_reference')
|
371
354
|
end
|
372
355
|
|
373
356
|
it 'gets ipn response for invalid status' do
|
374
|
-
|
375
|
-
expect(
|
357
|
+
request.to_return(status: 200, body: 'pesapal_response_data=INVALID')
|
358
|
+
expect(pesapal.ipn_listener('CHANGE', 'merchant_reference', 'transaction_tracking_id')).to eq(status: 'INVALID', response: nil)
|
376
359
|
end
|
377
360
|
end
|
378
361
|
end
|
379
|
-
|
380
|
-
private
|
381
|
-
|
382
|
-
def default_credentials
|
383
|
-
{ :callback_url => 'http://0.0.0.0:3000/pesapal/callback',
|
384
|
-
:consumer_key => '<YOUR_CONSUMER_KEY>',
|
385
|
-
:consumer_secret => '<YOUR_CONSUMER_SECRET>'
|
386
|
-
}
|
387
|
-
end
|
388
|
-
|
389
|
-
def demo_endpoints
|
390
|
-
{ :postpesapaldirectorderv4 => 'http://demo.pesapal.com/API/PostPesapalDirectOrderV4',
|
391
|
-
:querypaymentstatus=>'http://demo.pesapal.com/API/QueryPaymentStatus',
|
392
|
-
:querypaymentdetails=>'http://demo.pesapal.com/API/QueryPaymentDetails'
|
393
|
-
}
|
394
|
-
end
|
395
|
-
|
396
|
-
def production_endpoints
|
397
|
-
{ :postpesapaldirectorderv4 => 'https://www.pesapal.com/API/PostPesapalDirectOrderV4',
|
398
|
-
:querypaymentstatus=>'https://www.pesapal.com/API/QueryPaymentStatus',
|
399
|
-
:querypaymentdetails=>'https://www.pesapal.com/API/QueryPaymentDetails'
|
400
|
-
}
|
401
|
-
end
|
402
362
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pesapal
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Job King'ori Maina
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-10-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - '>='
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: faker
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: rake
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,6 +80,20 @@ dependencies:
|
|
66
80
|
- - '>='
|
67
81
|
- !ruby/object:Gem::Version
|
68
82
|
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
69
97
|
- !ruby/object:Gem::Dependency
|
70
98
|
name: webmock
|
71
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -103,10 +131,13 @@ extra_rdoc_files: []
|
|
103
131
|
files:
|
104
132
|
- .coveralls.yml
|
105
133
|
- .gitignore
|
134
|
+
- .hound.yml
|
106
135
|
- .rspec
|
136
|
+
- .rubocop.yml
|
107
137
|
- .travis.yml
|
108
138
|
- .yardopts
|
109
139
|
- CHANGELOG.md
|
140
|
+
- CONTRIBUTING.md
|
110
141
|
- Gemfile
|
111
142
|
- LICENSE.md
|
112
143
|
- README.md
|
@@ -137,7 +168,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
137
168
|
requirements:
|
138
169
|
- - '>='
|
139
170
|
- !ruby/object:Gem::Version
|
140
|
-
version: 1.9.
|
171
|
+
version: 1.9.3
|
141
172
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
142
173
|
requirements:
|
143
174
|
- - '>='
|