lib/active_merchant/billing/gateways/pay_gate_xml.rb
require 'digest/md5'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
# This gateway accepts the following arguments:
# :login => your PayJunction username
# :password => your PayJunction pass
# Example use:
#
# gateway = ActiveMerchant::Billing::Base.gateway(:pay_gate_xml).new(
# :login => "my_account",
# :password => "my_pass"
# )
#
# # set up credit card obj as in main ActiveMerchant example
# creditcard = ActiveMerchant::Billing::CreditCard.new(
# :type => 'visa',
# :number => '4242424242424242',
# :month => 8,
# :year => 2009,
# :first_name => 'Bob',
# :last_name => 'Bobsen'
# )
#
# # run request
# response = gateway.purchase(1000, creditcard) # charge 10 dollars
#
# 1) Check whether the transaction was successful
#
# response.success?
#
# 2) Retrieve the message returned by PayJunction
#
# response.message
#
# 3) Retrieve the unique transaction ID returned by PayGateXML
#
# response.authorization
#
# This gateway has many other features which are not implemented here yet
# The basic setup here only supports auth/capture transactions.
#
# Test Transactions
#
# PayGateXML has a global test user/pass, but you can also sign up for your own.
# The class and the test come equipped with the global test creds
#
# Usage Details
#
# Below is a map of only SOME of the values accepted by PayGateXML and how you should submit
# each to ActiveMerchant
#
# PayGateXML Field ActiveMerchant Use
#
# pgid use :login value to gateway instantiation
# pwd use :password value to gateway instantiation
#
# cname credit_card.name
# cc credit_card.number
# exp credit_card values formatted to YYYYMMDD
# budp South Africa only - set to 0 if purchase is not on budget
# amt include as argument to method for your transaction type
# ver do nothing, always set to current API version
#
# cref provide as :invoice in options, varchar(80)
# cur 3 char field, currently only ZAR
# cvv credit_card.verification
# bno batch processing number, i.e. you supply this
#
# others -- not used in this implementation
# nurl, rurl - must remain blank or absent or they will use an alternative authentication mechanism
# email, ip - must remain blank or absent or they will use a PayGate extra service call PayProtector
# threed - must remain blank unless you are using your own 3D Secure server
#
class PayGateXmlGateway < Gateway
self.live_url = 'https://www.paygate.co.za/payxml/process.trans'
# The countries the gateway supports merchants from as 2 digit ISO country codes
self.supported_countries = %w[US ZA]
# The card types supported by the payment gateway
self.supported_cardtypes = %i[visa master american_express diners_club]
# The homepage URL of the gateway
self.homepage_url = 'http://paygate.co.za/'
# The name of the gateway
self.display_name = 'PayGate PayXML'
# PayGate only supports Rands
self.default_currency = 'ZAR'
# PayGate accepts only lowest denomination
self.money_format = :cents
# PayGateXML public test account - you can get a private one too
TEST_ID_3DSECURE = '10011013800'
TEST_ID = '10011021600'
TEST_PWD = 'test'
API_VERSION = '4.0'
DECLINE_CODES = {
# Credit Card Errors - These RESULT_CODEs are returned if the transaction cannot be authorized due to a problem with the card. The TRANSACTION_STATUS will be 2
900001 => 'Call for Approval',
900002 => 'Card Expired',
900003 => 'Insufficient Funds',
900004 => 'Invalid Card Number',
900005 => 'Bank Interface Timeout', # indicates a communications failure between the banks systems
900006 => 'Invalid Card',
900007 => 'Declined',
900009 => 'Lost Card',
900010 => 'Invalid Card Length',
900011 => 'Suspected Fraud',
900012 => 'Card Reported As Stolen',
900013 => 'Restricted Card',
900014 => 'Excessive Card Usage',
900015 => 'Card Blacklisted',
900207 => 'Declined; authentication failed', # indicates the cardholder did not enter their MasterCard SecureCode / Verified by Visa password correctly
990020 => 'Auth Declined',
991001 => 'Invalid expiry date',
991002 => 'Invalid amount',
# Communication Errors - These RESULT_CODEs are returned if the transaction cannot be completed due to an unexpected error. TRANSACTION_STATUS will be 0.
900205 => 'Unexpected authentication result (phase 1)',
900206 => 'Unexpected authentication result (phase 1)',
990001 => 'Could not insert into Database',
990022 => 'Bank not available',
990053 => 'Error processing transaction',
# Miscellaneous - Unless otherwise noted, the TRANSACTION_STATUS will be 0.
900209 => 'Transaction verification failed (phase 2)', # Indicates the verification data returned from MasterCard SecureCode / Verified by Visa has been altered
900210 => 'Authentication complete; transaction must be restarted', # Indicates that the MasterCard SecuerCode / Verified by Visa transaction has already been completed. Most likely caused by the customer clicking the refresh button
990024 => 'Duplicate Transaction Detected. Please check before submitting',
990028 => 'Transaction cancelled' # Customer clicks the 'Cancel' button on the payment page
}
SUCCESS_CODES = %w(990004 990005 990017 990012 990018 990031)
TRANSACTION_CODES = {
0 => 'Not Done',
1 => 'Approved',
2 => 'Declined',
3 => 'Paid',
4 => 'Refunded',
5 => 'Received by PayGate',
6 => 'Replied to Client'
}
def initialize(options = {})
requires!(options, :login, :password)
super
end
def purchase(money, creditcard, options = {})
MultiResponse.run do |r|
r.process { authorize(money, creditcard, options) }
r.process { capture(money, r.authorization, options) }
end
end
def authorize(money, creditcard, options = {})
action = 'authtx'
options[:money] = money
options[:creditcard] = creditcard
commit(action, build_request(action, options))
end
def capture(money, authorization, options = {})
action = 'settletx'
options[:money] = money
options[:authorization] = authorization
commit(action, build_request(action, options), authorization)
end
def refund(money, authorization, options = {})
action = 'refundtx'
options[:money] = money
options[:authorization] = authorization
commit(action, build_request(action, options))
end
private
def successful?(response)
SUCCESS_CODES.include?(response[:res])
end
def build_request(action, options = {})
xml = Builder::XmlMarkup.new
xml.instruct!
xml.tag! 'protocol', ver: API_VERSION, pgid: (test? ? TEST_ID : @options[:login]), pwd: @options[:password] do |protocol|
money = options.delete(:money)
authorization = options.delete(:authorization)
creditcard = options.delete(:creditcard)
case action
when 'authtx'
build_authorization(protocol, money, creditcard, options)
when 'settletx'
build_capture(protocol, money, authorization, options)
when 'refundtx'
build_refund(protocol, money, authorization, options)
else
raise 'no action specified for build_request'
end
end
xml.target!
end
def build_authorization(xml, money, creditcard, options = {})
xml.tag! 'authtx', {
cref: options[:order_id],
cname: creditcard.name,
cc: creditcard.number,
exp: "#{format(creditcard.month, :two_digits)}#{format(creditcard.year, :four_digits)}",
budp: 0,
amt: amount(money),
cur: (options[:currency] || currency(money)),
cvv: creditcard.verification_value,
email: options[:email],
ip: options[:ip]
}
end
def build_capture(xml, money, authorization, options = {})
xml.tag! 'settletx', {
tid: authorization
}
end
def build_refund(xml, money, authorization, options = {})
xml.tag! 'refundtx', {
tid: authorization,
amt: amount(money)
}
end
def parse(action, body)
hash = {}
xml = REXML::Document.new(body)
response_action = action.gsub(/tx/, 'rx')
root = REXML::XPath.first(xml.root, response_action)
# we might have gotten an error
root = REXML::XPath.first(xml.root, 'errorrx') if root.nil?
root.attributes.each do |name, value|
hash[name.to_sym] = value
end
hash
end
def commit(action, request, authorization = nil)
response = parse(action, ssl_post(self.live_url, request))
Response.new(
successful?(response),
message_from(response),
response,
test: test?,
authorization: authorization || response[:tid]
)
end
def message_from(response)
(response[:rdesc] || response[:edesc])
end
end
end
end