Class: Pesapal::Merchant
- Inherits:
-
Object
- Object
- Pesapal::Merchant
- Defined in:
- lib/pesapal/merchant.rb
Overview
Pesapal Merchant object responsible for posting and handling transaction queries.
Instance Attribute Summary collapse
-
#config ⇒ Hash
Holds configuration details for the Pesapal object.
-
#order_details ⇒ Hash
Holds the order details for the transaction.
Instance Method Summary collapse
-
#change_env(env = false) ⇒ Hash
Set the environment in use.
-
#generate_order_url ⇒ String
Generate URL that's used to post a transaction to PesaPal.
-
#initialize(env = false) ⇒ Merchant
constructor
Creates a new instance of Merchant.
-
#ipn_listener(notification_type, merchant_reference, transaction_tracking_id) ⇒ Hash
Generates the appropriate IPN response depending on the status of the transaction.
-
#query_payment_details(merchant_reference, transaction_tracking_id) ⇒ Hash
Same as #query_payment_status, but additional information is returned in a Hash.
-
#query_payment_status(merchant_reference, transaction_tracking_id = nil) ⇒ String
Query the status of a transaction.
Constructor Details
#initialize(env = false) ⇒ Merchant
You can change the environment at runtime using #change_env
Creates a new instance of Pesapal::Merchant.
Initialize Pesapal object and choose the environment, there are two
environments; :development
and :production
. They determine if the code
will interact with the testing or the live Pesapal API. Like so ...
# Sets environment intelligently to 'Rails.env' (if Rails) or :development (if non-Rails)
pesapal = Pesapal::Merchant.new
# Sets environment to :development
pesapal = Pesapal::Merchant.new(:development)
# Sets environment to :production
pesapal = Pesapal::Merchant.new(:production)
A few things to note about the constructor as it behaves differently depending on the context within which it is called i.e. Rails app vs non-Rails app ...
Case 1: Rails app
The constructor attempts to set configuration details that should be
available at runtime from Rails.application.config.pesapal_credentials
.
This contains values loaded at application start from a YAML file located
at config/pesapal.yml
which typically looks like this:
development:
callback_url: 'http://0.0.0.0:3000/pesapal/callback'
consumer_key: '<YOUR_DEV_CONSUMER_KEY>'
consumer_secret: '<YOUR_DEV_CONSUMER_SECRET>'
production:
callback_url: 'http://1.2.3.4:3000/pesapal/callback'
consumer_key: '<YOUR_PROD_CONSUMER_KEY>'
consumer_secret: '<YOUR_PROD_CONSUMER_SECRET>'
The appropriate credentials are picked and set to #config instance attribute depending on set environment. The setting of environment is explained above. It's worth nothing that if for some reason the YAML file could not be read, then it fallbacks to setting #config instance attribute with default values. The exact definition of default values is shown below.
Case 2: Non-Rails app
Since (and if) no predefined configuration files are available, the constructor sets the #config instance attribute up with default values as shown below:
{ :callback_url => 'http://0.0.0.0:3000/pesapal/callback',
:consumer_key => '<YOUR_CONSUMER_KEY>',
:consumer_secret => '<YOUR_CONSUMER_SECRET>'
}
148 149 150 151 152 153 154 155 |
# File 'lib/pesapal/merchant.rb', line 148 def initialize(env = false) change_env env if defined?(Rails) configure Rails.application.config.pesapal_credentials else configure end end |
Instance Attribute Details
#config ⇒ Hash
Holds configuration details for the Pesapal object.
:callback_url
- the page on your site that users will be redirected to, after they have made the payment on PesaPal:consumer_key
- your Pesapal consumer key sent to you via email or obtained from the dashboard:consumer_secret
- your Pesapal consumer secret sent to you via email or obtained from the dashboard
It typically looks like this:
{ :callback_url => 'http://0.0.0.0:3000/pesapal/callback',
:consumer_key => '<YOUR_CONSUMER_KEY>',
:consumer_secret => '<YOUR_CONSUMER_SECRET>'
}
23 24 25 |
# File 'lib/pesapal/merchant.rb', line 23 def config @config end |
#order_details ⇒ Hash
Make sure ALL expected hash attributes are present, the method
assumes they are and no checks are done to certify that this has been
done nor are any fallbacks built in. Also the :amount
should be a
number, no commas, or else Pesapal will convert the comma to a period (.)
which will result in the incorrect amount for the transaction.
Holds the order details for the transaction.
:amount
- the order amount:description
- a note about the order:type
- MERCHANT:reference
- the unique id generated for the transaction by your application before posting the order:first_name
- first name of the customer:last_name
- second name of the customer:email
- email of the customer:phonenumber
- phone number of the customer:currency
- ISO code for the currency
It typically looks like this:
{ :amount => 1000,
:description => 'this is the transaction description',
:type => 'MERCHANT',
:reference => '808-707-606',
:first_name => 'Swaleh',
:last_name => 'Mdoe',
:email => '[email protected]',
:phonenumber => '+254722222222',
:currency => 'KES'
}
61 62 63 |
# File 'lib/pesapal/merchant.rb', line 61 def order_details @order_details end |
Instance Method Details
#change_env(env = false) ⇒ Hash
For a Rails app, you'd expect that calling this would also flip the credentials if there was a YAML file containing both environment credentials but that's not the case. It could be something that we can add later.
Set the environment in use.
Useful especially if you want to change the environment at runtime from what was set during initialization in the constructor. It also makes sure that we use the appropriate endpoints when making calls to Pesapal. See below:
# endpoint values set if :development
{
:postpesapaldirectorderv4 => "http://demo.pesapal.com/API/PostPesapalDirectOrderV4",
:querypaymentstatus => "http://demo.pesapal.com/API/QueryPaymentStatus",
:querypaymentdetails => "http://demo.pesapal.com/API/QueryPaymentDetails"
}
# endpoint values set if :production
{
:postpesapaldirectorderv4 => "https://www.pesapal.com/API/PostPesapalDirectOrderV4",
:querypaymentstatus => "https://www.pesapal.com/API/QueryPaymentStatus",
:querypaymentdetails => "https://www.pesapal.com/API/QueryPaymentDetails"
}
343 344 345 346 347 348 349 350 351 352 |
# File 'lib/pesapal/merchant.rb', line 343 def change_env(env = false) env = env.to_s.downcase if env == 'production' @env = 'production' else @env = 'development' @env = Rails.env if defined?(Rails) end assign_endpoints end |
#generate_order_url ⇒ String
You MUST set up your order details before you call this method on the object.
Generate URL that's used to post a transaction to PesaPal.
PesaPal will present the user with a page which contains the available payment options and will redirect to your site to the callback url once the user has completed the payment process. A tracking id will be returned as a query parameter - this can be used subsequently to track the payment status on Pesapal for the transaction later on.
Generating the URL is a 3-step process:
- Initialize Pesapal::Merchant, making sure credentials are set. See #initialize for details.
- Set the order details. See #order_details for details.
- Call #generate_order_url on the object.
Example:
# generate transaction url after step #1 & #2
order_url = pesapal.generate_order_url
# order_url now contains a string with the order url.
# 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
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
# File 'lib/pesapal/merchant.rb', line 184 def generate_order_url # build xml with input data, the format is standard so no editing is # required @post_xml = Pesapal::Helper::Post.generate_post_xml @order_details # initialize setting of @params (oauth_signature left empty) @params = Pesapal::Helper::Post.set_parameters(@config[:callback_url], @config[:consumer_key], @post_xml) # generate oauth signature and add signature to the request parameters @params[:oauth_signature] = Pesapal::Oauth.generate_oauth_signature('GET', @api_endpoints[:postpesapaldirectorderv4], @params, @config[:consumer_secret], @token_secret) # change params (with signature) to a query string query_string = Pesapal::Oauth.generate_encoded_params_query_string @params "#{@api_endpoints[:postpesapaldirectorderv4]}?#{query_string}" end |
#ipn_listener(notification_type, merchant_reference, transaction_tracking_id) ⇒ Hash
It's up to you to send the response back to Pesapal by providing the
:response
back to the IPN. The hard part is done.
Generates the appropriate IPN response depending on the status of the transaction.
# pass in the notification type, merchant reference and transaction id
response_to_ipn = pesapal.ipn_listener("<NOTIFICATION_TYPE>", "<MERCHANT_REFERENCE>","<TRANSACTION_ID>")
The variable, response_to_ipn
, now holds a response as the one shown
below. Using the status you can customise any actions (e.g. database
inserts and updates).
{
:status => "<PAYMENT_STATUS>",
:response => "<IPN_RESPONSE>"
}
Ps: The response you send to PesaPal must be the same as what you
received from PesaPal if successful, which the method generates for you
and should be in :response
.
391 392 393 394 395 396 397 398 399 400 401 402 |
# File 'lib/pesapal/merchant.rb', line 391 def ipn_listener(notification_type, merchant_reference, transaction_tracking_id) notification_type = 'CHANGE' status = query_payment_status(merchant_reference, transaction_tracking_id) output = { status: status, response: nil } case status when 'COMPLETED' then output[:response] = "pesapal_notification_type=#{notification_type}&pesapal_transaction_tracking_id=#{transaction_tracking_id}&pesapal_merchant_reference=#{merchant_reference}" when 'FAILED' then output[:response] = "pesapal_notification_type=#{notification_type}&pesapal_transaction_tracking_id=#{transaction_tracking_id}&pesapal_merchant_reference=#{merchant_reference}" end output end |
#query_payment_details(merchant_reference, transaction_tracking_id) ⇒ Hash
Same as #query_payment_status, but additional information is returned in a Hash.
Call method on initialized Pesapal::Merchant object (see #initialize for details):
# pass in merchant reference and transaction id
payment_details = pesapal.query_payment_details("<MERCHANT_REFERENCE>","<TRANSACTION_ID>")
Response should contain the following:
:method
- the payment method used by the user to make the payment:status
- one ofPENDING | COMPLETED | FAILED | INVALID
:merchant_reference
- this is the same as the parameter you sent when making the query:transaction_tracking_id
- this is the same as the parameter you sent when making the query
Example:
{
:method => "<PAYMENT_METHOD>",
:status => "<PAYMENT_STATUS>",
:merchant_reference => "<MERCHANT_REFERENCE>",
:transaction_tracking_id => "<TRANSACTION_ID>"
}
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 |
# File 'lib/pesapal/merchant.rb', line 237 def query_payment_details(merchant_reference, transaction_tracking_id) # initialize setting of @params (oauth_signature left empty) @params = Pesapal::Helper::Details.set_parameters(@config[:consumer_key], merchant_reference, transaction_tracking_id) # generate oauth signature and add signature to the request parameters @params[:oauth_signature] = Pesapal::Oauth.generate_oauth_signature('GET', @api_endpoints[:querypaymentdetails], @params, @config[:consumer_secret], @token_secret) # change params (with signature) to a query string query_string = Pesapal::Oauth.generate_encoded_params_query_string @params # get status response uri = URI.parse "#{@api_endpoints[:querypaymentdetails]}?#{query_string}" http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_PEER response = http.request(Net::HTTP::Get.new(uri.request_uri)) response = CGI.parse response.body response = response['pesapal_response_data'][0].split(',') { method: response[1], status: response[2], merchant_reference: response[3], transaction_tracking_id: response[0] } end |
#query_payment_status(merchant_reference, transaction_tracking_id = nil) ⇒ String
If you don't ensure that the merchant reference is unique for each order on your system, you may get INVALID as the response. Because of this, it is recommended that you provide both the merchant reference and transaction tracking id as parameters to guarantee uniqueness.
Query the status of a transaction.
When a transaction is posted to PesaPal, it may be in a PENDING
,
COMPLETED
or FAILED
state. If the transaction is PENDING
, the
payment may complete or fail at a later stage.
# option 1: using merchant reference only
payment_status = pesapal.query_payment_status("<MERCHANT_REFERENCE>")
# option 2: using merchant reference and transaction id (recommended, see note for reason why)
payment_status = pesapal.query_payment_status("<MERCHANT_REFERENCE>","<TRANSACTION_ID>")
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 |
# File 'lib/pesapal/merchant.rb', line 290 def query_payment_status(merchant_reference, transaction_tracking_id = nil) # initialize setting of @params (oauth_signature left empty) @params = Pesapal::Helper::Status.set_parameters(@config[:consumer_key], merchant_reference, transaction_tracking_id) # generate oauth signature and add signature to the request parameters @params[:oauth_signature] = Pesapal::Oauth.generate_oauth_signature('GET', @api_endpoints[:querypaymentstatus], @params, @config[:consumer_secret], @token_secret) # change params (with signature) to a query string query_string = Pesapal::Oauth.generate_encoded_params_query_string @params # get status response uri = URI.parse "#{@api_endpoints[:querypaymentstatus]}?#{query_string}" http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_PEER response = http.request(Net::HTTP::Get.new(uri.request_uri)) response = CGI.parse response.body response['pesapal_response_data'][0] end |