pesapal 1.5.4 → 1.5.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +12 -6
- data/.yardopts +12 -0
- data/CHANGELOG.md +14 -0
- data/{LICENSE.txt → LICENSE.md} +5 -2
- data/README.md +26 -250
- data/Rakefile +1 -1
- data/lib/generators/pesapal/install_generator.rb +3 -0
- data/lib/pesapal.rb +3 -3
- data/lib/pesapal/helper/details.rb +53 -0
- data/lib/pesapal/helper/post.rb +177 -0
- data/lib/pesapal/helper/status.rb +54 -0
- data/lib/pesapal/merchant.rb +398 -143
- data/lib/pesapal/oauth.rb +161 -122
- data/lib/pesapal/railtie.rb +4 -4
- data/lib/pesapal/version.rb +6 -1
- data/pesapal.gemspec +5 -3
- data/spec/pesapal_merchant_spec.rb +258 -6
- data/spec/spec_helper.rb +8 -0
- metadata +55 -35
- data/Gemfile.lock +0 -29
- data/lib/pesapal/merchant/details.rb +0 -24
- data/lib/pesapal/merchant/post.rb +0 -51
- data/lib/pesapal/merchant/status.rb +0 -29
@@ -0,0 +1,177 @@
|
|
1
|
+
module Pesapal
|
2
|
+
# Pesapal helper modules.
|
3
|
+
module Helper
|
4
|
+
# Contains helper methods relating to posting orders. See
|
5
|
+
# {Pesapal::Merchant#generate_order_url} source.
|
6
|
+
#
|
7
|
+
# _For your information;_ the schema below may be used to validate the XML
|
8
|
+
# required by Pesapal. You don't have to do this since
|
9
|
+
# {Pesapal::Helper::Post.generate_post_xml} pretty much does it for you ... but it's
|
10
|
+
# worth noting that the {Pesapal::Helper::Post.generate_post_xml} method only builds a
|
11
|
+
# very basic XML structure and is not as comprehensive as the one below. Maybe
|
12
|
+
# in the future we might need to add more functionality but as of now it's
|
13
|
+
# seems to meet most user's needs.
|
14
|
+
#
|
15
|
+
# ```xml
|
16
|
+
# <!--?xml version="1.0" encoding="utf-8" ?-->
|
17
|
+
# <xs:schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
18
|
+
# xmlns:xs="http://www.w3.org/2001/XMLSchema" attributeformdefault="unqualified" elementformdefault="qualified"
|
19
|
+
# targetnamespace="http://www.pesapal.com">
|
20
|
+
# <xsd:element name="PesapalDirectOrderInfo">
|
21
|
+
# <xsd:complextype>
|
22
|
+
# <xsd:sequence>
|
23
|
+
# <xsd:element name="LineItems" minoccurs="0" maxoccurs="1">
|
24
|
+
# <xsd:complextype>
|
25
|
+
# <xsd:sequence>
|
26
|
+
# <xsd:element minoccurs="1" maxoccurs="unbounded" name="LineItem">
|
27
|
+
# <xsd:complextype>
|
28
|
+
# <xsd:attribute name="UniqueId" type="xsd:string" use="required"></xsd:attribute>
|
29
|
+
# <xsd:attribute name="Particulars" type="xsd:string" use="required"></xsd:attribute>
|
30
|
+
# <xsd:attribute name="Quantity" type="xsd:unsignedInt" use="required"></xsd:attribute>
|
31
|
+
# <xsd:attribute name="UnitCost" type="xsd:decimal" use="required"></xsd:attribute>
|
32
|
+
# <xsd:attribute name="SubTotal" type="xsd:decimal" use="required"></xsd:attribute>
|
33
|
+
# </xsd:complextype>
|
34
|
+
# </xsd:element>
|
35
|
+
# </xsd:sequence>
|
36
|
+
# </xsd:complextype>
|
37
|
+
# </xsd:element>
|
38
|
+
# </xsd:sequence>
|
39
|
+
# <xsd:attribute name="Amount" type="xsd:decimal" use="required"></xsd:attribute>
|
40
|
+
# <xsd:attribute name="Currency" use="optional">
|
41
|
+
# <xsd:simpletype>
|
42
|
+
# <xsd:restriction base="xs:string">
|
43
|
+
# <xsd:pattern value="[A-Za-z][A-Za-z][A-Za-z]"></xsd:pattern>
|
44
|
+
# </xsd:restriction>
|
45
|
+
# </xsd:simpletype>
|
46
|
+
# </xsd:attribute>
|
47
|
+
# <xsd:attribute name="Description" type="xsd:string" use="required"></xsd:attribute>
|
48
|
+
# <xsd:attribute name="Type" use="required">
|
49
|
+
# <xsd:simpletype>
|
50
|
+
# <xsd:restriction base="xsd:string">
|
51
|
+
# <xsd:enumeration value="MERCHANT"></xsd:enumeration>
|
52
|
+
# <xsd:enumeration value="ORDER"></xsd:enumeration>
|
53
|
+
# </xsd:restriction>
|
54
|
+
# </xsd:simpletype>
|
55
|
+
# </xsd:attribute>
|
56
|
+
# <xsd:attribute name="Reference" type="xsd:string" use="required"></xsd:attribute>
|
57
|
+
# <xsd:attribute name="FirstName" type="xsd:string" use="optional"></xsd:attribute>
|
58
|
+
# <xsd:attribute name="LastName" type="xsd:string" use="optional"></xsd:attribute>
|
59
|
+
# <xsd:attribute name="Email" type="xsd:string" use="required"></xsd:attribute>
|
60
|
+
# <xsd:attribute name="PhoneNumber" type="xsd:string" use="optional"></xsd:attribute>
|
61
|
+
# <xsd:attribute name="AccountNumber" type="xsd:string" use="optional"></xsd:attribute>
|
62
|
+
# </xsd:complextype>
|
63
|
+
# </xsd:element>
|
64
|
+
# </xs:schema>
|
65
|
+
# ```
|
66
|
+
module Post
|
67
|
+
# Build XML formated order data to be submitted as part of the PostPesapalDirectOrderV4 oAuth 1.0 call.
|
68
|
+
#
|
69
|
+
# The expected order details are as follows;
|
70
|
+
#
|
71
|
+
# 1. `:amount` - the order amount
|
72
|
+
# 2. `:description` - a note about the order
|
73
|
+
# 3. `:type` - MERCHANT
|
74
|
+
# 4. `:reference` - the unique id generated for the transaction by your application before posting the order
|
75
|
+
# 5. `:first_name` - first name of the customer
|
76
|
+
# 6. `:last_name` - second name of the customer
|
77
|
+
# 7. `:email` - email of the customer
|
78
|
+
# 8. `:phonenumber` - phone number of the customer
|
79
|
+
# 9. `:currency` - ISO code for the currency
|
80
|
+
#
|
81
|
+
# It typically looks like this:
|
82
|
+
#
|
83
|
+
# ```
|
84
|
+
# order_details = {
|
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
|
+
# }
|
95
|
+
# ```
|
96
|
+
#
|
97
|
+
# See {Pesapal::Merchant#order_details}.
|
98
|
+
#
|
99
|
+
# @note Make sure **ALL** expected hash attributes are present, the method
|
100
|
+
# assumes they are and no checks are done to certify that this has been
|
101
|
+
# done nor are any fallbacks built in. Also the `:amount` should be a
|
102
|
+
# number, no commas, or else Pesapal will convert the comma to a period (.)
|
103
|
+
# which will result in the incorrect amount for the transaction.
|
104
|
+
#
|
105
|
+
# @param details [Hash] the order details, see above for details on it's contents.
|
106
|
+
#
|
107
|
+
# @return [String] encoded XML formated order data.
|
108
|
+
def self.generate_post_xml(details)
|
109
|
+
post_xml = ''
|
110
|
+
post_xml.concat '<?xml version="1.0" encoding="utf-8"?>'
|
111
|
+
post_xml.concat '<PesapalDirectOrderInfo '
|
112
|
+
post_xml.concat 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" '
|
113
|
+
post_xml.concat 'xmlns:xsd="http://www.w3.org/2001/XMLSchema" '
|
114
|
+
post_xml.concat "Amount=\"#{details[:amount]}\" "
|
115
|
+
post_xml.concat "Description=\"#{details[:description]}\" "
|
116
|
+
post_xml.concat "Type=\"#{details[:type]}\" "
|
117
|
+
post_xml.concat "Reference=\"#{details[:reference]}\" "
|
118
|
+
post_xml.concat "FirstName=\"#{details[:first_name]}\" "
|
119
|
+
post_xml.concat "LastName=\"#{details[:last_name]}\" "
|
120
|
+
post_xml.concat "Email=\"#{details[:email]}\" "
|
121
|
+
post_xml.concat "PhoneNumber=\"#{details[:phonenumber]}\" "
|
122
|
+
post_xml.concat "Currency=\"#{details[:currency]}\" "
|
123
|
+
post_xml.concat 'xmlns="http://www.pesapal.com" />'
|
124
|
+
|
125
|
+
# Encode the XML
|
126
|
+
encoder = HTMLEntities.new(:xhtml1)
|
127
|
+
post_xml = encoder.encode post_xml
|
128
|
+
end
|
129
|
+
|
130
|
+
# Prepares parameters to be used during the PostPesapalDirectOrderV4 oAuth 1.0 call.
|
131
|
+
#
|
132
|
+
# The PostPesapalDirectOrderV4 oAuth 1.0 call requires the following parameters;
|
133
|
+
#
|
134
|
+
# 1. `oauth_callback` - URL on your site where the users will be redirected to. See [section 6.2.3 of the oAuth 1.0 spec][5]
|
135
|
+
# 2. `oauth_consumer_key` - your Pesapal consumer key sent to you via email or obtained from the dashboard
|
136
|
+
# 3. `oauth_nonce` - a random string, uniquely generated for each request. See [section 8 of the oAuth 1.0 spec][3]
|
137
|
+
# 4. `oauth_signature` - the signature as defined in the oAuth 1.0 spec under [section 9 of the oAuth 1.0 spec][2]
|
138
|
+
# 5. `oauth_signature_method` - `HMAC-SHA1` (do not change). See [section 9.2 of the oAuth 1.0 spec][1]
|
139
|
+
# 6. `oauth_timestamp` - number of seconds since January 1, 1970 00:00:00 GMT, also known as Unix Time. See [section 8 of the oAuth 1.0 spec][3]
|
140
|
+
# 7. `oauth_version` - `1.0` (do not change)
|
141
|
+
# 8. `pesapal_request_data` - encoded XML formated order data (same as `post_xml` defined below).
|
142
|
+
#
|
143
|
+
# This method generates all the above **except** the `oauth_signature` which
|
144
|
+
# is generated later by {Pesapal::Oauth.generate_oauth_signature} since
|
145
|
+
# generation of this `oauth_signature` requires these parameters as inputs
|
146
|
+
# anyway. See [section 9.2.1 of the oAuth 1.0 spec][1] for more details.
|
147
|
+
#
|
148
|
+
# [1]: http://oauth.net/core/1.0/#anchor16
|
149
|
+
# [2]: http://oauth.net/core/1.0/#signing_process
|
150
|
+
# [3]: http://oauth.net/core/1.0/#nonce
|
151
|
+
# [4]: http://oauth.net/core/1.0/
|
152
|
+
# [5]: http://oauth.net/core/1.0/#auth_step2
|
153
|
+
#
|
154
|
+
# @param callback_url [String] URL to the redirect page. This is the page on
|
155
|
+
# your site where the users will be redirected to, after they have made the
|
156
|
+
# payment on PesaPal.
|
157
|
+
#
|
158
|
+
# @param consumer_key [String] your Pesapal consumer key sent to you via
|
159
|
+
# email or obtained from the dashboard
|
160
|
+
#
|
161
|
+
# @param post_xml [String] encoded XML formated order data. Generated by {generate_post_xml}
|
162
|
+
#
|
163
|
+
# @return [Hash] parameters to be used in generating the oAuth 1.0 URL query parameters and the `oauth_signature` itself.
|
164
|
+
def self.set_parameters(callback_url, consumer_key, post_xml)
|
165
|
+
timestamp = Time.now.to_i.to_s
|
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
|
+
}
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Pesapal
|
2
|
+
# Pesapal helper modules.
|
3
|
+
module Helper
|
4
|
+
# Contains helper methods relating to any queries for transaction payment
|
5
|
+
# status. See {Pesapal::Merchant#query_payment_status} source.
|
6
|
+
module Status
|
7
|
+
# Prepares parameters to be used during the QueryPaymentStatus and QueryPaymentStatusByMerchantRef oAuth 1.0 calls.
|
8
|
+
#
|
9
|
+
# The QueryPaymentStatus and QueryPaymentStatusByMerchantRef oAuth 1.0 calls require the following parameters;
|
10
|
+
#
|
11
|
+
# 1. `oauth_consumer_key` - your Pesapal consumer key sent to you via email or obtained from the dashboard
|
12
|
+
# 2. `oauth_nonce` - a random string, uniquely generated for each request. See [section 8 of the oAuth 1.0 spec][3]
|
13
|
+
# 3. `oauth_signature` - the signature as defined in the oAuth 1.0 spec under [section 9 of the oAuth 1.0 spec][2]
|
14
|
+
# 4. `oauth_signature_method` - `HMAC-SHA1` (do not change). See [section 9.2 of the oAuth 1.0 spec][1]
|
15
|
+
# 5. `oauth_timestamp` - number of seconds since January 1, 1970 00:00:00 GMT, also known as Unix Time. See [section 8 of the oAuth 1.0 spec][3]
|
16
|
+
# 6. `oauth_version` - `1.0` (do not change)
|
17
|
+
# 7. `pesapal_merchant_reference` - the transaction merchant reference (same as `merchant_reference` defined below)
|
18
|
+
# 8. `pesapal_transaction_tracking_id` - the transaction tracking id (same as `transaction_tracking_id` defined below)
|
19
|
+
#
|
20
|
+
# This method generates all the above **except** the `oauth_signature` which
|
21
|
+
# is generated later by {Pesapal::Oauth.generate_oauth_signature} since
|
22
|
+
# generation of this `oauth_signature` requires these parameters as inputs
|
23
|
+
# anyway. See [section 9.2.1 of the oAuth 1.0 spec][1] for more details.
|
24
|
+
#
|
25
|
+
# [1]: http://oauth.net/core/1.0/#anchor16
|
26
|
+
# [2]: http://oauth.net/core/1.0/#signing_process
|
27
|
+
# [3]: http://oauth.net/core/1.0/#nonce
|
28
|
+
# [4]: http://oauth.net/core/1.0/
|
29
|
+
#
|
30
|
+
# @param consumer_key [String] your Pesapal consumer key sent to you via
|
31
|
+
# email or obtained from the dashboard
|
32
|
+
#
|
33
|
+
# @param merchant_reference [String] the unique id generated for the
|
34
|
+
# transaction by your application before posting the order
|
35
|
+
#
|
36
|
+
# @param transaction_tracking_id [String] the unique id assigned by Pesapal
|
37
|
+
# to the transaction after it's posted
|
38
|
+
#
|
39
|
+
# @return [Hash] parameters to be used in generating the oAuth 1.0 URL query parameters and the `oauth_signature` itself.
|
40
|
+
def self.set_parameters(consumer_key, merchant_reference, transaction_tracking_id = nil)
|
41
|
+
timestamp = Time.now.to_i.to_s
|
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
|
+
}
|
49
|
+
params[:pesapal_transaction_tracking_id] = transaction_tracking_id unless transaction_tracking_id.nil?
|
50
|
+
return params
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/pesapal/merchant.rb
CHANGED
@@ -1,184 +1,439 @@
|
|
1
1
|
module Pesapal
|
2
|
-
|
2
|
+
# Pesapal Merchant object responsible for posting and handling transaction
|
3
|
+
# queries.
|
3
4
|
class Merchant
|
4
|
-
|
5
5
|
attr_accessor :config, :order_details
|
6
6
|
|
7
|
+
# Holds configuration details for the Pesapal object.
|
8
|
+
#
|
9
|
+
# 1. `:callback_url` - the page on your site that users will be redirected to, after they have made the payment on PesaPal
|
10
|
+
# 2. `:consumer_key` - your Pesapal consumer key sent to you via email or obtained from the dashboard
|
11
|
+
# 3. `:consumer_secret` - your Pesapal consumer secret sent to you via email or obtained from the dashboard
|
12
|
+
#
|
13
|
+
# It typically looks like this:
|
14
|
+
#
|
15
|
+
# ```
|
16
|
+
# { :callback_url => 'http://0.0.0.0:3000/pesapal/callback',
|
17
|
+
# :consumer_key => '<YOUR_CONSUMER_KEY>',
|
18
|
+
# :consumer_secret => '<YOUR_CONSUMER_SECRET>'
|
19
|
+
# }
|
20
|
+
# ```
|
21
|
+
#
|
22
|
+
# @return [Hash] the Pesapal config
|
7
23
|
def config
|
8
24
|
@config ||= {}
|
9
25
|
end
|
10
26
|
|
27
|
+
# Holds the order details for the transaction.
|
28
|
+
#
|
29
|
+
# 1. `:amount` - the order amount
|
30
|
+
# 2. `:description` - a note about the order
|
31
|
+
# 3. `:type` - MERCHANT
|
32
|
+
# 4. `:reference` - the unique id generated for the transaction by your application before posting the order
|
33
|
+
# 5. `:first_name` - first name of the customer
|
34
|
+
# 6. `:last_name` - second name of the customer
|
35
|
+
# 7. `:email` - email of the customer
|
36
|
+
# 8. `:phonenumber` - phone number of the customer
|
37
|
+
# 9. `:currency` - ISO code for the currency
|
38
|
+
#
|
39
|
+
# It typically looks like this:
|
40
|
+
#
|
41
|
+
# ```
|
42
|
+
# { :amount => 1000,
|
43
|
+
# :description => 'this is the transaction description',
|
44
|
+
# :type => 'MERCHANT',
|
45
|
+
# :reference => '808-707-606',
|
46
|
+
# :first_name => 'Swaleh',
|
47
|
+
# :last_name => 'Mdoe',
|
48
|
+
# :email => 'user@example.com',
|
49
|
+
# :phonenumber => '+254722222222',
|
50
|
+
# :currency => 'KES'
|
51
|
+
# }
|
52
|
+
# ```
|
53
|
+
#
|
54
|
+
# @note Make sure **ALL** expected hash attributes are present, the method
|
55
|
+
# assumes they are and no checks are done to certify that this has been
|
56
|
+
# done nor are any fallbacks built in. Also the `:amount` should be a
|
57
|
+
# number, no commas, or else Pesapal will convert the comma to a period (.)
|
58
|
+
# which will result in the incorrect amount for the transaction.
|
59
|
+
#
|
60
|
+
# @return [Hash] the order details
|
11
61
|
def order_details
|
12
62
|
@order_details ||= {}
|
13
63
|
end
|
14
64
|
|
15
65
|
private
|
16
66
|
|
17
|
-
|
18
|
-
@api_domain
|
19
|
-
end
|
20
|
-
|
21
|
-
def api_endpoints
|
22
|
-
@api_endpoints
|
23
|
-
end
|
24
|
-
|
25
|
-
def env
|
26
|
-
@env
|
27
|
-
end
|
67
|
+
attr_reader :api_domain, :api_endpoints, :env
|
28
68
|
|
29
|
-
|
30
|
-
|
31
|
-
|
69
|
+
def params
|
70
|
+
@params ||= nil
|
71
|
+
end
|
32
72
|
|
33
|
-
|
34
|
-
|
35
|
-
|
73
|
+
def post_xml
|
74
|
+
@post_xml ||= nil
|
75
|
+
end
|
36
76
|
|
37
|
-
|
38
|
-
|
39
|
-
|
77
|
+
def token_secret
|
78
|
+
@token_secret ||= nil
|
79
|
+
end
|
40
80
|
|
41
81
|
public
|
42
82
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
83
|
+
# Creates a new instance of {Pesapal::Merchant}.
|
84
|
+
#
|
85
|
+
# Initialize Pesapal object and choose the environment, there are two
|
86
|
+
# environments; `:development` and `:production`. They determine if the code
|
87
|
+
# will interact with the testing or the live Pesapal API. Like so ...
|
88
|
+
#
|
89
|
+
# ```ruby
|
90
|
+
# # Sets environment intelligently to 'Rails.env' (if Rails) or :development (if non-Rails)
|
91
|
+
# pesapal = Pesapal::Merchant.new
|
92
|
+
#
|
93
|
+
# # Sets environment to :development
|
94
|
+
# pesapal = Pesapal::Merchant.new(:development)
|
95
|
+
#
|
96
|
+
# # Sets environment to :production
|
97
|
+
# pesapal = Pesapal::Merchant.new(:production)
|
98
|
+
# ```
|
99
|
+
#
|
100
|
+
# A few things to note about the constructor as it behaves differently
|
101
|
+
# depending on the context within which it is called i.e. _Rails_ app vs
|
102
|
+
# _non-Rails_ app ...
|
103
|
+
#
|
104
|
+
# ### Case 1: Rails app
|
105
|
+
#
|
106
|
+
# The constructor attempts to set configuration details that should be
|
107
|
+
# available at runtime from `Rails.application.config.pesapal_credentials`.
|
108
|
+
# This contains values loaded at application start from a YAML file located
|
109
|
+
# at `config/pesapal.yml` which typically looks like this:
|
110
|
+
#
|
111
|
+
# ```yaml
|
112
|
+
# development:
|
113
|
+
# callback_url: 'http://0.0.0.0:3000/pesapal/callback'
|
114
|
+
# consumer_key: '<YOUR_DEV_CONSUMER_KEY>'
|
115
|
+
# consumer_secret: '<YOUR_DEV_CONSUMER_SECRET>'
|
116
|
+
#
|
117
|
+
# production:
|
118
|
+
# callback_url: 'http://1.2.3.4:3000/pesapal/callback'
|
119
|
+
# consumer_key: '<YOUR_PROD_CONSUMER_KEY>'
|
120
|
+
# consumer_secret: '<YOUR_PROD_CONSUMER_SECRET>'
|
121
|
+
# ```
|
122
|
+
#
|
123
|
+
# The appropriate credentials are picked and set to {#config} instance
|
124
|
+
# attribute depending on set environment. The setting of environment is
|
125
|
+
# explained above. It's worth nothing that if for some reason the YAML file
|
126
|
+
# could not be read, then it fallbacks to setting {#config} instance
|
127
|
+
# attribute with default values. The exact definition of default values is
|
128
|
+
# shown below.
|
129
|
+
#
|
130
|
+
# ### Case 2: Non-Rails app
|
131
|
+
#
|
132
|
+
# Since (and if) no predefined configuration files are available, the
|
133
|
+
# constructor sets the {#config} instance attribute up with default values
|
134
|
+
# as shown below:
|
135
|
+
#
|
136
|
+
# ```
|
137
|
+
# { :callback_url => 'http://0.0.0.0:3000/pesapal/callback',
|
138
|
+
# :consumer_key => '<YOUR_CONSUMER_KEY>',
|
139
|
+
# :consumer_secret => '<YOUR_CONSUMER_SECRET>'
|
140
|
+
# }
|
141
|
+
# ```
|
142
|
+
#
|
143
|
+
# @note You can change the environment at runtime using {#set_env}
|
144
|
+
#
|
145
|
+
# @param env [Symbol] the environment we want to use i.e. `:development` or
|
146
|
+
# `:production`. Leaving it blank sets environment intelligently to
|
147
|
+
# `Rails.env` (if Rails) or `:development` (if non-Rails).
|
148
|
+
def initialize(env = false)
|
149
|
+
set_env env
|
150
|
+
if defined?(Rails)
|
151
|
+
set_configuration Rails.application.config.pesapal_credentials
|
152
|
+
else
|
153
|
+
set_configuration
|
51
154
|
end
|
155
|
+
end
|
52
156
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
157
|
+
# Generate URL that's used to post a transaction to PesaPal.
|
158
|
+
#
|
159
|
+
# PesaPal will present the user with a page which contains the available
|
160
|
+
# payment options and will redirect to your site to the _callback url_ once
|
161
|
+
# the user has completed the payment process. A tracking id will be returned
|
162
|
+
# as a query parameter – this can be used subsequently to track the payment
|
163
|
+
# status on Pesapal for the transaction later on.
|
164
|
+
#
|
165
|
+
# Generating the URL is a 3-step process:
|
166
|
+
#
|
167
|
+
# 1. Initialize {Pesapal::Merchant}, making sure credentials are set. See {#initialize} for details.
|
168
|
+
# 2. Set the order details. See {#order_details} for details.
|
169
|
+
# 3. Call {#generate_order_url} on the object.
|
170
|
+
#
|
171
|
+
# Example:
|
172
|
+
#
|
173
|
+
# ```ruby
|
174
|
+
# # generate transaction url after step #1 & #2
|
175
|
+
# order_url = pesapal.generate_order_url
|
176
|
+
#
|
177
|
+
# # order_url now contains a string with the order url.
|
178
|
+
# # http://demo.pesapal.com/API/PostPesapalDirectOrderV4?oauth_callback=http%3A%2F%2F1.2.3.4%3A3000%2Fpesapal%2Fcallback&oauth_consumer_key=A9MXocJiHK1P4w0M%2F%2FYzxgIVMX557Jt4&oauth_nonce=13804335543pDXs4q3djsy&oauth_signature=BMmLR0AVInfoBI9D4C38YDA9eSM%3D&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1380433554&oauth_version=1.0&pesapal_request_data=%26lt%3B%3Fxml%20version%3D%26quot%3B1.0%26quot%3B%20encoding%3D%26quot%3Butf-8%26quot%3B%3F%26gt%3B%26lt%3BPesapalDirectOrderInfo%20xmlns%3Axsi%3D%26quot%3Bhttp%3A%2F%2Fwww.w3.org%2F2001%2FXMLSchema-instance%26quot%3B%20xmlns%3Axsd%3D%26quot%3Bhttp%3A%2F%2Fwww.w3.org%2F2001%2FXMLSchema%26quot%3B%20Amount%3D%26quot%3B1000%26quot%3B%20Description%3D%26quot%3Bthis%20is%20the%20transaction%20description%26quot%3B%20Type%3D%26quot%3BMERCHANT%26quot%3B%20Reference%3D%26quot%3B808%26quot%3B%20FirstName%3D%26quot%3BSwaleh%26quot%3B%20LastName%3D%26quot%3BMdoe%26quot%3B%20Email%3D%26quot%3Bj%40kingori.co%26quot%3B%20PhoneNumber%3D%26quot%3B%2B254722222222%26quot%3B%20xmlns%3D%26quot%3Bhttp%3A%2F%2Fwww.pesapal.com%26quot%3B%20%2F%26gt%3B
|
179
|
+
# ```
|
180
|
+
#
|
181
|
+
# @note You **MUST** set up your order details before you call this method on the object.
|
182
|
+
#
|
183
|
+
# @return [String] URL of the Pesapal post order form
|
184
|
+
def generate_order_url
|
185
|
+
# build xml with input data, the format is standard so no editing is
|
186
|
+
# required
|
187
|
+
@post_xml = Pesapal::Helper::Post.generate_post_xml @order_details
|
188
|
+
|
189
|
+
# initialize setting of @params (oauth_signature left empty)
|
190
|
+
@params = Pesapal::Helper::Post.set_parameters(@config[:callback_url], @config[:consumer_key], @post_xml)
|
191
|
+
|
192
|
+
# generate oauth signature and add signature to the request parameters
|
193
|
+
@params[:oauth_signature] = Pesapal::Oauth::generate_oauth_signature("GET", @api_endpoints[:postpesapaldirectorderv4], @params, @config[:consumer_secret], @token_secret)
|
194
|
+
|
195
|
+
# change params (with signature) to a query string
|
196
|
+
query_string = Pesapal::Oauth.generate_encoded_params_query_string @params
|
197
|
+
|
198
|
+
"#{@api_endpoints[:postpesapaldirectorderv4]}?#{query_string}"
|
199
|
+
end
|
68
200
|
|
69
|
-
|
201
|
+
# Same as {#query_payment_status}, but additional information is returned in
|
202
|
+
# a Hash.
|
203
|
+
#
|
204
|
+
# Call method on initialized {Pesapal::Merchant} object (see {#initialize}
|
205
|
+
# for details):
|
206
|
+
#
|
207
|
+
# ```ruby
|
208
|
+
# # pass in merchant reference and transaction id
|
209
|
+
# payment_details = pesapal.query_payment_details("<MERCHANT_REFERENCE>","<TRANSACTION_ID>")
|
210
|
+
# ```
|
211
|
+
#
|
212
|
+
# Response should contain the following:
|
213
|
+
#
|
214
|
+
# 1. `:method` - the payment method used by the user to make the payment
|
215
|
+
# 2. `:status` - one of `PENDING | COMPLETED | FAILED | INVALID`
|
216
|
+
# 3. `:merchant_reference` - this is the same as the parameter you sent when making the query
|
217
|
+
# 4. `:transaction_tracking_id` - this is the same as the parameter you sent when making the query
|
218
|
+
#
|
219
|
+
# Example:
|
220
|
+
#
|
221
|
+
# ```
|
222
|
+
# {
|
223
|
+
# :method => "<PAYMENT_METHOD>",
|
224
|
+
# :status => "<PAYMENT_STATUS>",
|
225
|
+
# :merchant_reference => "<MERCHANT_REFERENCE>",
|
226
|
+
# :transaction_tracking_id => "<TRANSACTION_ID>"
|
227
|
+
# }
|
228
|
+
# ```
|
229
|
+
#
|
230
|
+
# @param merchant_reference [String] the unique id generated for the
|
231
|
+
# transaction by your application before posting the order
|
232
|
+
#
|
233
|
+
# @param transaction_tracking_id [String] the unique id assigned by Pesapal
|
234
|
+
# to the transaction after it's posted
|
235
|
+
#
|
236
|
+
# @return [Hash] transaction payment details
|
237
|
+
def query_payment_details(merchant_reference, transaction_tracking_id)
|
238
|
+
# initialize setting of @params (oauth_signature left empty)
|
239
|
+
@params = Pesapal::Helper::Details.set_parameters(@config[:consumer_key], merchant_reference, transaction_tracking_id)
|
240
|
+
|
241
|
+
# generate oauth signature and add signature to the request parameters
|
242
|
+
@params[:oauth_signature] = Pesapal::Oauth.generate_oauth_signature("GET", @api_endpoints[:querypaymentdetails], @params, @config[:consumer_secret], @token_secret)
|
243
|
+
|
244
|
+
# change params (with signature) to a query string
|
245
|
+
query_string = Pesapal::Oauth.generate_encoded_params_query_string @params
|
246
|
+
|
247
|
+
# get status response
|
248
|
+
uri = URI.parse "#{@api_endpoints[:querypaymentdetails]}?#{query_string}"
|
249
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
250
|
+
if @env == 'production'
|
251
|
+
http.use_ssl = true
|
252
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
70
253
|
end
|
254
|
+
response = http.request(Net::HTTP::Get.new(uri.request_uri))
|
255
|
+
response = CGI.parse response.body
|
256
|
+
response = response['pesapal_response_data'][0].split(',')
|
257
|
+
|
258
|
+
{ :method => response[1],
|
259
|
+
:status => response[2],
|
260
|
+
:merchant_reference => response[3],
|
261
|
+
:transaction_tracking_id => response[0]
|
262
|
+
}
|
263
|
+
end
|
71
264
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
265
|
+
# Query the status of a transaction.
|
266
|
+
#
|
267
|
+
# When a transaction is posted to PesaPal, it may be in a `PENDING`,
|
268
|
+
# `COMPLETED` or `FAILED` state. If the transaction is `PENDING`, the
|
269
|
+
# payment may complete or fail at a later stage.
|
270
|
+
#
|
271
|
+
# ```ruby
|
272
|
+
# # option 1: using merchant reference only
|
273
|
+
# payment_status = pesapal.query_payment_status("<MERCHANT_REFERENCE>")
|
274
|
+
#
|
275
|
+
# # option 2: using merchant reference and transaction id (recommended, see note for reason why)
|
276
|
+
# payment_status = pesapal.query_payment_status("<MERCHANT_REFERENCE>","<TRANSACTION_ID>")
|
277
|
+
# ```
|
278
|
+
#
|
279
|
+
# @note If you don't ensure that the merchant reference is unique for each
|
280
|
+
# order on your system, you may get INVALID as the response. Because of
|
281
|
+
# this, it is recommended that you provide both the merchant reference and
|
282
|
+
# transaction tracking id as parameters to guarantee uniqueness.
|
283
|
+
#
|
284
|
+
# @param merchant_reference [String] the unique id generated for the
|
285
|
+
# transaction by your application before posting the order
|
286
|
+
#
|
287
|
+
# @param transaction_tracking_id [String] the unique id assigned by Pesapal
|
288
|
+
# to the transaction after it's posted
|
289
|
+
#
|
290
|
+
# @return [String] the status of the transaction. Possible values include
|
291
|
+
# PENDING | COMPLETED | FAILED | INVALID
|
292
|
+
def query_payment_status(merchant_reference, transaction_tracking_id = nil)
|
293
|
+
# initialize setting of @params (oauth_signature left empty)
|
294
|
+
@params = Pesapal::Helper::Status.set_parameters(@config[:consumer_key], merchant_reference, transaction_tracking_id)
|
295
|
+
|
296
|
+
# generate oauth signature and add signature to the request parameters
|
297
|
+
@params[:oauth_signature] = Pesapal::Oauth.generate_oauth_signature("GET", @api_endpoints[:querypaymentstatus], @params, @config[:consumer_secret], @token_secret)
|
298
|
+
|
299
|
+
# change params (with signature) to a query string
|
300
|
+
query_string = Pesapal::Oauth.generate_encoded_params_query_string @params
|
301
|
+
|
302
|
+
# get status response
|
303
|
+
uri = URI.parse "#{@api_endpoints[:querypaymentstatus]}?#{query_string}"
|
304
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
305
|
+
if @env == 'production'
|
306
|
+
http.use_ssl = true
|
307
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
99
308
|
end
|
309
|
+
response = http.request(Net::HTTP::Get.new(uri.request_uri))
|
310
|
+
response = CGI.parse response.body
|
311
|
+
response['pesapal_response_data'][0]
|
312
|
+
end
|
100
313
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
314
|
+
# Set the environment in use.
|
315
|
+
#
|
316
|
+
# Useful especially if you want to change the environment at runtime from
|
317
|
+
# what was set during initialization in the constructor. It also makes sure
|
318
|
+
# that we use the appropriate endpoints when making calls to Pesapal. See
|
319
|
+
# below:
|
320
|
+
#
|
321
|
+
# ```
|
322
|
+
# # endpoint values set if :development
|
323
|
+
# {
|
324
|
+
# :postpesapaldirectorderv4 => "http://demo.pesapal.com/API/PostPesapalDirectOrderV4",
|
325
|
+
# :querypaymentstatus => "http://demo.pesapal.com/API/QueryPaymentStatus",
|
326
|
+
# :querypaymentdetails => "http://demo.pesapal.com/API/QueryPaymentDetails"
|
327
|
+
# }
|
328
|
+
#
|
329
|
+
# # endpoint values set if :production
|
330
|
+
# {
|
331
|
+
# :postpesapaldirectorderv4 => "https://www.pesapal.com/API/PostPesapalDirectOrderV4",
|
332
|
+
# :querypaymentstatus => "https://www.pesapal.com/API/QueryPaymentStatus",
|
333
|
+
# :querypaymentdetails => "https://www.pesapal.com/API/QueryPaymentDetails"
|
334
|
+
# }
|
335
|
+
# ```
|
336
|
+
#
|
337
|
+
# @note For a Rails app, you'd expect that calling this would also flip the
|
338
|
+
# credentials if there was a YAML file containing both environment
|
339
|
+
# credentials but that's not the case. It could be something that we can
|
340
|
+
# add later.
|
341
|
+
#
|
342
|
+
# @param env [Symbol] the environment we want to use i.e. :development or
|
343
|
+
# :production
|
344
|
+
#
|
345
|
+
# @return [Hash] contains Pesapal endpoints appropriate for the set
|
346
|
+
# environment
|
347
|
+
def set_env(env = false)
|
348
|
+
env = env.to_s.downcase
|
349
|
+
if env == 'production'
|
350
|
+
@env = 'production'
|
351
|
+
else
|
352
|
+
@env = 'development'
|
353
|
+
@env = Rails.env if defined?(Rails)
|
123
354
|
end
|
355
|
+
set_endpoints
|
356
|
+
end
|
124
357
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
358
|
+
# Generates the appropriate IPN response depending on the status of the
|
359
|
+
# transaction.
|
360
|
+
#
|
361
|
+
# ```ruby
|
362
|
+
# # pass in the notification type, merchant reference and transaction id
|
363
|
+
# response_to_ipn = pesapal.ipn_listener("<NOTIFICATION_TYPE>", "<MERCHANT_REFERENCE>","<TRANSACTION_ID>")
|
364
|
+
# ```
|
365
|
+
#
|
366
|
+
# The variable, `response_to_ipn`, now holds a response as the one shown
|
367
|
+
# below. Using the status you can customise any actions (e.g. database
|
368
|
+
# inserts and updates).
|
369
|
+
#
|
370
|
+
# ```
|
371
|
+
# {
|
372
|
+
# :status => "<PAYMENT_STATUS>",
|
373
|
+
# :response => "<IPN_RESPONSE>"
|
374
|
+
# }
|
375
|
+
# ```
|
376
|
+
#
|
377
|
+
# _Ps: The response you send to PesaPal must be the same as what you
|
378
|
+
# received from PesaPal if successful, which the method generates for you
|
379
|
+
# and should be in `:response`._
|
380
|
+
#
|
381
|
+
# @note It's up to you to send the response back to Pesapal by providing the
|
382
|
+
# `:response` back to the IPN. The hard part is done.
|
383
|
+
#
|
384
|
+
# @param notification_type [String] the IPN notification type, should be set
|
385
|
+
# to CHANGE always
|
386
|
+
#
|
387
|
+
# @param merchant_reference [String] the unique id generated for the
|
388
|
+
# transaction by your application before posting the order
|
389
|
+
#
|
390
|
+
# @param transaction_tracking_id [String] the unique id assigned by Pesapal
|
391
|
+
# to the transaction after it's posted
|
392
|
+
#
|
393
|
+
# @return [Hash] contains the status and IPN response that should be sent
|
394
|
+
# back to Pesapal
|
395
|
+
def ipn_listener(notification_type, merchant_reference, transaction_tracking_id)
|
396
|
+
status = query_payment_status(merchant_reference, transaction_tracking_id)
|
397
|
+
output = { :status => status, :response => nil }
|
398
|
+
|
399
|
+
case status
|
400
|
+
when 'COMPLETED' then output[:response] = "pesapal_notification_type=CHANGE&pesapal_transaction_tracking_id=#{transaction_tracking_id}&pesapal_merchant_reference=#{merchant_reference}"
|
401
|
+
when 'FAILED' then output[:response] = "pesapal_notification_type=CHANGE&pesapal_transaction_tracking_id=#{transaction_tracking_id}&pesapal_merchant_reference=#{merchant_reference}"
|
135
402
|
end
|
136
403
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
status = query_payment_status(merchant_reference, transaction_tracking_id)
|
141
|
-
output = { :status => status, :response => nil }
|
142
|
-
|
143
|
-
case status
|
144
|
-
when 'COMPLETED' then output[:response] = "pesapal_notification_type=CHANGE&pesapal_transaction_tracking_id=#{transaction_tracking_id}&pesapal_merchant_reference=#{merchant_reference}"
|
145
|
-
when 'FAILED' then output[:response] = "pesapal_notification_type=CHANGE&pesapal_transaction_tracking_id=#{transaction_tracking_id}&pesapal_merchant_reference=#{merchant_reference}"
|
146
|
-
end
|
147
|
-
|
148
|
-
output
|
149
|
-
end
|
404
|
+
output
|
405
|
+
end
|
150
406
|
|
151
407
|
private
|
152
408
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
@api_domain = 'http://demo.pesapal.com'
|
160
|
-
end
|
161
|
-
|
162
|
-
@api_endpoints = {}
|
163
|
-
@api_endpoints[:postpesapaldirectorderv4] = "#{@api_domain}/API/PostPesapalDirectOrderV4"
|
164
|
-
@api_endpoints[:querypaymentstatus] = "#{@api_domain}/API/QueryPaymentStatus"
|
165
|
-
@api_endpoints[:querypaymentdetails] = "#{@api_domain}/API/QueryPaymentDetails"
|
166
|
-
|
167
|
-
return @api_endpoints
|
409
|
+
# Set API endpoints depending on the environment.
|
410
|
+
def set_endpoints
|
411
|
+
if @env == 'production'
|
412
|
+
@api_domain = 'https://www.pesapal.com'
|
413
|
+
else
|
414
|
+
@api_domain = 'http://demo.pesapal.com'
|
168
415
|
end
|
169
416
|
|
170
|
-
|
171
|
-
|
417
|
+
@api_endpoints = {}
|
418
|
+
@api_endpoints[:postpesapaldirectorderv4] = "#{@api_domain}/API/PostPesapalDirectOrderV4"
|
419
|
+
@api_endpoints[:querypaymentstatus] = "#{@api_domain}/API/QueryPaymentStatus"
|
420
|
+
@api_endpoints[:querypaymentdetails] = "#{@api_domain}/API/QueryPaymentDetails"
|
172
421
|
|
173
|
-
|
174
|
-
|
175
|
-
:consumer_key => '<YOUR_CONSUMER_KEY>',
|
176
|
-
:consumer_secret => '<YOUR_CONSUMER_SECRET>'
|
177
|
-
}
|
422
|
+
@api_endpoints
|
423
|
+
end
|
178
424
|
|
179
|
-
|
425
|
+
# Set credentials through hash that passed in (does a little processing to
|
426
|
+
# remove unwanted data & uses default if nothing is input).
|
427
|
+
def set_configuration(consumer_details = {})
|
428
|
+
# set the configuration
|
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
|
+
}
|
180
433
|
|
181
|
-
|
182
|
-
|
434
|
+
valid_config_keys = @config.keys
|
435
|
+
|
436
|
+
consumer_details.each { |k, v| @config[k.to_sym] = v if valid_config_keys.include? k.to_sym }
|
437
|
+
end
|
183
438
|
end
|
184
439
|
end
|