Skip to content

Commit

Permalink
Add PxPay integration
Browse files Browse the repository at this point in the history
  • Loading branch information
Tom Burns committed Aug 2, 2012
1 parent 75e432d commit 01e807f
Show file tree
Hide file tree
Showing 10 changed files with 655 additions and 5 deletions.
11 changes: 6 additions & 5 deletions lib/active_merchant/billing/integrations/action_view_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,17 @@ module ActionViewHelper
# <% service.cancel_return_url 'http://mystore.com' %>
# <% end %>
#
def payment_service_for(order, account, options = {}, &proc)
def payment_service_for(order, account, options = {}, &proc)
raise ArgumentError, "Missing block" unless block_given?

integration_module = ActiveMerchant::Billing::Integrations.const_get(options.delete(:service).to_s.camelize)

result = []
result << form_tag(integration_module.service_url, options.delete(:html) || {})

service_class = integration_module.const_get('Helper')

form_options = options.delete(:html) || {}
service = service_class.new(order, account, options)
form_options[:method] = service.form_method
result = []
result << form_tag(integration_module.service_url, form_options)

result << capture(service, &proc)

Expand Down
4 changes: 4 additions & 0 deletions lib/active_merchant/billing/integrations/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ def test?
@test_mode ||= ActiveMerchant::Billing::Base.integration_mode == :test || @test
end

def form_method
"POST"
end

private

def add_address(key, params)
Expand Down
31 changes: 31 additions & 0 deletions lib/active_merchant/billing/integrations/pxpay.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
module Integrations #:nodoc:
module Pxpay
autoload :Helper, 'active_merchant/billing/integrations/pxpay/helper.rb'
autoload :Notification, 'active_merchant/billing/integrations/pxpay/notification.rb'
autoload :Return, 'active_merchant/billing/integrations/pxpay/return.rb'

TOKEN_URL = 'https://sec.paymentexpress.com/pxpay/pxaccess.aspx'

LIVE_URL = 'https://sec.paymentexpress.com/pxpay/pxpay.aspx'

def self.token_url
TOKEN_URL
end

def self.service_url
LIVE_URL
end

def self.notification(post, options={})
Notification.new(post, options)
end

def self.return(query_string, options={})
Return.new(query_string, options)
end
end
end
end
end
110 changes: 110 additions & 0 deletions lib/active_merchant/billing/integrations/pxpay/helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
require 'active_support/core_ext/float/rounding.rb' # Float#round(precision)

module ActiveMerchant #:nodoc:
module Billing #:nodoc:
module Integrations #:nodoc:
module Pxpay
# An example. Note the username as a parameter and transaction key you
# will want to use later. The amount that you pass in will be *rounded*,
# so preferably pass in X.2 decimal so that no rounding occurs. You need
# to set :credential2 to your PxPay secret key.
#
# PxPay accounts have Failproof Notification enabled by default which means
# in addition to the user being redirected to your return_url, the return_url will
# be accessed by the PxPay servers directly, immediately after transaction success.
#
# payment_service_for('order_id', 'pxpay_user_ID', :service => :pxpay,
# :amount => 157.0, :currency => 'USD', :credential2 => 'pxpay_key') do |service|
#
# service.customer :email => '[email protected]'
#
# service.description 'Order 123 for MyStore'
#
# # Must specify both a return_url or PxPay will show an error instead of
# # capturing credit card details.
#
# service.return_url "http://t/pxpay/payment_received_notification_sub_step"
#
# # These fields will be copied verbatim to the Notification
# service.custom1 'custom text 1'
# service.custom2 ''
# service.custom3 ''
# # See the helper.rb file for various custom fields
# end

class Helper < ActiveMerchant::Billing::Integrations::Helper
include PostsData
mapping :account, 'PxPayUserId'
mapping :credential2, 'PxPayKey'
mapping :currency, 'CurrencyInput'
mapping :description, 'MerchantReference'
mapping :order, 'TxnId'
mapping :customer, :email => 'EmailAddress'

mapping :custom1, 'TxnData1'
mapping :custom2, 'TxnData2'
mapping :custom3, 'TxnData3'

def initialize(order, account, options = {})
super
add_field 'AmountInput', "%.2f" % options[:amount].to_f.round(2)
add_field 'EnableAddBillCard', '0'
add_field 'TxnType', 'Purchase'
end

def return_url(url)
add_field 'UrlSuccess', url
add_field 'UrlFail', url
end

def form_fields
# if either return URLs are blank PxPay will generate a token but redirect user to error page.
raise "error - must specify return_url" if @fields['UrlSuccess'].blank?
raise "error - must specify cancel_return_url" if @fields['UrlFail'].blank?

result = request_secure_redirect
raise "error - failed to get token - message was #{result[:redirect]}" unless result[:valid] == "1"

url = URI.parse(result[:redirect])

CGI.parse(url.query)
end

def form_method
"GET"
end

private
def generate_request
xml = REXML::Document.new
root = xml.add_element('GenerateRequest')

@fields.each do | k, v |
root.add_element(k).text = v
end

xml.to_s
end

def request_secure_redirect
request = generate_request

response = ssl_post(Pxpay.token_url, request)
xml = REXML::Document.new(response)
root = REXML::XPath.first(xml, "//Request")
valid = root.attributes["valid"]
redirect = root.elements["URI"].text

# example positive response:
# <Request valid="1"><URI>https://sec.paymentexpress.com/pxpay/pxpay.aspx?userid=PxpayUser&amp;request=REQUEST_TOKEN</URI></Request>

# example negative response:
# <Request valid="0"><URI>Invalid TxnType</URI></Request>

{:valid => valid, :redirect => redirect}
end
end
end
end
end
end
157 changes: 157 additions & 0 deletions lib/active_merchant/billing/integrations/pxpay/notification.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
module Integrations #:nodoc:

module Pxpay
class Notification < ActiveMerchant::Billing::Integrations::Notification
include PostsData
include RequiresParameters

def initialize(query_string, options={})
# PxPay appends ?result=...&userid=... to whatever return_url was specified, even if that URL ended with a ?query.
# So switch the first ? if present to a &
query_string[/\?/] = '&' if query_string[/\?/]
super

@encrypted_params = @params
@params = {}

requires! @encrypted_params, "result"
requires! @options, :credential1, :credential2

decrypt_transaction_result(@encrypted_params["result"])
end

# was the notification a validly formed request?
def acknowledge
@valid == '1'
end

def status
return 'Failed' unless success?
return 'Completed' if complete?
'Error'
end

def complete?
@params['TxnType'] == 'Purchase' && success?
end

def cancelled?
!success?
end

# for field definitions see
# http://www.paymentexpress.com/Technical_Resources/Ecommerce_Hosted/PxPay

def success?
@params['Success'] == '1'
end

def gross
@params['AmountSettlement']
end

def currency
@params['CurrencySettlement']
end

def account
@params['userid']
end

def item_id
@params['TxnId']
end

def currency_input
@params['CurrencyInput']
end

def auth_code
@params['AuthCode']
end

def card_type
@params['CardName']
end

def card_holder_name
@params['CardHolderName']
end

def card_number
@params['CardNumber']
end

def expiry_date
@params['DateExpiry']
end

def client_ip
@params['ClientInfo']
end

def order_id
item_id
end

def payer_email
@params['EmailAddress']
end

def transaction_id
@params['DpsTxnRef']
end

def settlement_date
@params['DateSettlement']
end

# Indication of the uniqueness of a card number
def txn_mac
@params['TxnMac']
end

def message
@params['ResponseText']
end

def optional_data
[@params['TxnData1'],@fields['TxnData2'],@fields['TxnData3']]
end

# When was this payment was received by the client.
def received_at
settlement_date
end

# Was this a test transaction?
def test?
nil
end

private

def decrypt_transaction_result(encrypted_result)
request_xml = REXML::Document.new
root = request_xml.add_element('ProcessResponse')

root.add_element('PxPayUserId').text = @options[:credential1]
root.add_element('PxPayKey').text = @options[:credential2]
root.add_element('Response').text = encrypted_result

@raw = ssl_post(Pxpay.token_url, request_xml.to_s)

response_xml = REXML::Document.new(@raw)
root = REXML::XPath.first(response_xml)
@valid = root.attributes["valid"]
@params = {}
root.elements.each { |e| @params[e.name] = e.text }
end

end
end
end
end
end
25 changes: 25 additions & 0 deletions lib/active_merchant/billing/integrations/pxpay/return.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
module Integrations #:nodoc:
module Pxpay
class Return < ActiveMerchant::Billing::Integrations::Return
def initialize(query_string, options={})
@notification = Notification.new(query_string, options)
end

def success?
@notification && @notification.complete?
end

def cancelled?
@notification && @notification.cancelled?
end

def message
@notification.message
end
end
end
end
end
end
4 changes: 4 additions & 0 deletions test/fixtures.yml
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,10 @@ plugnpay:
login: LOGIN
password: PASSWORD

pxpay:
login: LOGIN
password: PASSWORD

sage_pay:
login: LOGIN

Expand Down
Loading

0 comments on commit 01e807f

Please sign in to comment.