pesapal 1.5.4 → 1.5.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
- def api_domain
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
- def params
30
- @params ||= nil
31
- end
69
+ def params
70
+ @params ||= nil
71
+ end
32
72
 
33
- def post_xml
34
- @post_xml ||= nil
35
- end
73
+ def post_xml
74
+ @post_xml ||= nil
75
+ end
36
76
 
37
- def token_secret
38
- @token_secret ||= nil
39
- end
77
+ def token_secret
78
+ @token_secret ||= nil
79
+ end
40
80
 
41
81
  public
42
82
 
43
- # constructor
44
- def initialize(env = false)
45
- set_env env
46
- if defined?(Rails)
47
- set_configuration Rails.application.config.pesapal_credentials
48
- else
49
- set_configuration
50
- end
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
- # generate pesapal order url (often iframed)
54
- def generate_order_url
55
-
56
- # build xml with input data, the format is standard so no editing is
57
- # required
58
- @post_xml = Pesapal::Post::generate_post_xml @order_details
59
-
60
- # initialize setting of @params (oauth_signature left empty)
61
- @params = Pesapal::Post::set_parameters(@config[:callback_url], @config[:consumer_key], @post_xml)
62
-
63
- # generate oauth signature and add signature to the request parameters
64
- @params[:oauth_signature] = Pesapal::Oauth::generate_oauth_signature("GET", @api_endpoints[:postpesapaldirectorderv4], @params, @config[:consumer_secret], @token_secret)
65
-
66
- # change params (with signature) to a query string
67
- query_string = Pesapal::Oauth::generate_encoded_params_query_string @params
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
- "#{@api_endpoints[:postpesapaldirectorderv4]}?#{query_string}"
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
- # query the details of the transaction
73
- def query_payment_details(merchant_reference, transaction_tracking_id)
74
-
75
- # initialize setting of @params (oauth_signature left empty)
76
- @params = Pesapal::Details::set_parameters(@config[:consumer_key], merchant_reference, transaction_tracking_id)
77
-
78
- # generate oauth signature and add signature to the request parameters
79
- @params[:oauth_signature] = Pesapal::Oauth::generate_oauth_signature("GET", @api_endpoints[:querypaymentdetails], @params, @config[:consumer_secret], @token_secret)
80
-
81
- # change params (with signature) to a query string
82
- query_string = Pesapal::Oauth::generate_encoded_params_query_string @params
83
-
84
- # get status response
85
- uri = URI.parse "#{@api_endpoints[:querypaymentstatus]}?#{query_string}"
86
- http = Net::HTTP.new(uri.host, uri.port)
87
- if @env == 'production'
88
- http.use_ssl = true
89
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
90
- end
91
- response = http.request(Net::HTTP::Get.new(uri.request_uri))
92
- response = CGI::parse response.body
93
- response = response['pesapal_response_data'][0].split(',')
94
-
95
- details = { :method => response[1],
96
- :status => response[2],
97
- :merchant_reference => response[3],
98
- :transaction_tracking_id => response[0] }
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
- # query the status of the transaction
102
- def query_payment_status(merchant_reference, transaction_tracking_id = nil)
103
-
104
- # initialize setting of @params (oauth_signature left empty)
105
- @params = Pesapal::Status::set_parameters(@config[:consumer_key], merchant_reference, transaction_tracking_id)
106
-
107
- # generate oauth signature and add signature to the request parameters
108
- @params[:oauth_signature] = Pesapal::Oauth::generate_oauth_signature("GET", @api_endpoints[:querypaymentstatus], @params, @config[:consumer_secret], @token_secret)
109
-
110
- # change params (with signature) to a query string
111
- query_string = Pesapal::Oauth::generate_encoded_params_query_string @params
112
-
113
- # get status response
114
- uri = URI.parse "#{@api_endpoints[:querypaymentstatus]}?#{query_string}"
115
- http = Net::HTTP.new(uri.host, uri.port)
116
- if @env == 'production'
117
- http.use_ssl = true
118
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
119
- end
120
- response = http.request(Net::HTTP::Get.new(uri.request_uri))
121
- response = CGI::parse response.body
122
- response['pesapal_response_data'][0]
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
- # set env when called
126
- def set_env(env = false)
127
- env = env.to_s.downcase
128
- if env == 'production'
129
- @env = 'production'
130
- else
131
- @env = 'development'
132
- @env = Rails.env if defined?(Rails)
133
- end
134
- set_endpoints
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
- # listen to ipn response
138
- def ipn_listener(notification_type, merchant_reference, transaction_tracking_id)
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
- # set endpoints
154
- def set_endpoints
155
-
156
- if @env == 'production'
157
- @api_domain = 'https://www.pesapal.com'
158
- else
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
- # set credentialts through hash, uses default if nothing is input
171
- def set_configuration(consumer_details = {})
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
- # set the configuration
174
- @config = { :callback_url => 'http://0.0.0.0:3000/pesapal/callback',
175
- :consumer_key => '<YOUR_CONSUMER_KEY>',
176
- :consumer_secret => '<YOUR_CONSUMER_SECRET>'
177
- }
422
+ @api_endpoints
423
+ end
178
424
 
179
- valid_config_keys = @config.keys
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
- consumer_details.each { |k,v| @config[k.to_sym] = v if valid_config_keys.include? k.to_sym }
182
- end
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