module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class EwayManagedGateway < Gateway
self.test_url = 'https://www.eway.com.au/gateway/ManagedPaymentService/test/managedCreditCardPayment.asmx'
self.live_url = 'https://www.eway.com.au/gateway/ManagedPaymentService/managedCreditCardPayment.asmx'
# The countries the gateway supports merchants from as 2 digit ISO country codes
self.supported_countries = ['AU']
# The card types supported by the payment gateway
self.supported_cardtypes = [:visa, :master]
self.default_currency = 'AUD'
# accepted money format
self.money_format = :cents
# The homepage URL of the gateway
self.homepage_url = 'http://www.eway.com.au/'
# The name of the gateway
self.display_name = 'eWay Managed Payments'
def initialize(options = {})
requires!(options, :login, :username, :password)
# eWay returns 500 code for faults, which AM snaffles.
# So, we tell it to allow them.
options[:ignore_http_status] = true
super
end
# add a new customer CC to your eway account and return unique ManagedCustomerID
# supports storing details required by eway see "add_creditcard" and "add_address"
def store(creditcard, options = {})
post = {}
# Handle our required fields
requires!(options, :billing_address)
# Handle eWay specific required fields.
billing_address = options[:billing_address]
eway_requires!(billing_address)
add_creditcard(post, creditcard)
add_address(post, billing_address)
add_misc_fields(post, options)
commit('CreateCustomer', post)
end
def update(billing_id, creditcard, options={})
post = {}
# Handle our required fields
requires!(options, :billing_address)
# Handle eWay specific required fields.
billing_address = options[:billing_address]
eway_requires!(billing_address)
post[:managedCustomerID] = billing_id
add_creditcard(post, creditcard)
add_address(post, billing_address)
add_misc_fields(post, options)
commit('UpdateCustomer', post)
end
# Process a payment in the given amount against the stored credit card given by billing_id
#
# ==== Parameters
#
# * money -- The amount to be purchased as an Integer value in cents.
# * billing_id -- The eWay provided card/customer token to charge (managedCustomerID)
# * options -- A hash of optional parameters.
#
# ==== Options
#
# * :order_id -- The order number, passed to eWay as the "Invoice Reference"
# * :invoice -- The invoice number, passed to eWay as the "Invoice Reference" unless :order_id is also given
# * :description -- A description of the payment, passed to eWay as the "Invoice Description"
def purchase(money, billing_id, options={})
post = {}
post[:managedCustomerID] = billing_id.to_s
post[:amount] = money
add_invoice(post, options)
commit('ProcessPayment', post)
end
# Get customer's stored credit card details given by billing_id
#
# ==== Parameters
#
# * billing_id -- The eWay provided card/customer token to charge (managedCustomerID)
def retrieve(billing_id)
post = {}
post[:managedCustomerID] = billing_id.to_s
commit('QueryCustomer', post)
end
# TODO: eWay API also provides QueryPayment
private
def eway_requires!(hash)
raise ArgumentError.new('Missing eWay required parameter in `billing_address`: title') unless hash.has_key?(:title)
raise ArgumentError.new('Missing eWay required parameter in `billing_address`: country') unless hash.has_key?(:country)
end
def add_address(post, address)
post[:Address] = address[:address1].to_s
post[:Phone] = address[:phone].to_s
post[:PostCode] = address[:zip].to_s
post[:Suburb] = address[:city].to_s
post[:Country] = address[:country].to_s.downcase
post[:State] = address[:state].to_s
post[:Mobile] = address[:mobile].to_s
post[:Fax] = address[:fax].to_s
end
def add_misc_fields(post, options)
post[:CustomerRef] = options[:billing_address][:customer_ref] || options[:customer]
post[:Title] = options[:billing_address][:title]
post[:Company] = options[:billing_address][:company]
post[:JobDesc] = options[:billing_address][:job_desc]
post[:Email] = options[:billing_address][:email] || options[:email]
post[:URL] = options[:billing_address][:url]
post[:Comments] = options[:description]
end
def add_invoice(post, options)
post[:invoiceReference] = options[:order_id] || options[:invoice]
post[:invoiceDescription] = options[:description]
end
# add credit card details to be stored by eway. NOTE eway requires "title" field
def add_creditcard(post, creditcard)
post[:CCNumber] = creditcard.number
post[:CCExpiryMonth] = sprintf('%.2i', creditcard.month)
post[:CCExpiryYear] = sprintf('%.4i', creditcard.year)[-2..-1]
post[:CCNameOnCard] = creditcard.name
post[:FirstName] = creditcard.first_name
post[:LastName] = creditcard.last_name
end
def parse(body)
reply = {}
xml = REXML::Document.new(body)
if root = REXML::XPath.first(xml, '//soap:Fault') then
reply = parse_fault(root)
else
if root = REXML::XPath.first(xml, '//ProcessPaymentResponse/ewayResponse') then
# Successful payment
reply = parse_purchase(root)
else
if root = REXML::XPath.first(xml, '//QueryCustomerResult') then
reply = parse_query_customer(root)
else
if root = REXML::XPath.first(xml, '//CreateCustomerResult') then
reply[:message] = 'OK'
reply[:CreateCustomerResult] = root.text
reply[:success] = true
else
if root = REXML::XPath.first(xml, '//UpdateCustomerResult') then
if root.text.casecmp('true').zero? then
reply[:message] = 'OK'
reply[:success] = true
else
# ERROR: This state should never occur. If there is a problem,
# a soap:Fault will be returned. The presence of this
# element always means a success.
raise StandardError, 'Unexpected "false" in UpdateCustomerResult'
end
else
# ERROR: This state should never occur currently. We have handled
# responses for all the methods which we support.
raise StandardError, 'Unexpected response'
end
end
end
end
end
return reply
end
def parse_fault(node)
reply = {}
reply[:message] = REXML::XPath.first(node, '//soap:Reason/soap:Text').text
reply[:success] = false
reply
end
def parse_purchase(node)
reply = {}
reply[:message] = REXML::XPath.first(node, '//ewayTrxnError').text
reply[:success] = (REXML::XPath.first(node, '//ewayTrxnStatus').text == 'True')
reply[:auth_code] = REXML::XPath.first(node, '//ewayAuthCode').text
reply[:transaction_number] = REXML::XPath.first(node, '//ewayTrxnNumber').text
reply
end
def parse_query_customer(node)
reply = {}
reply[:message] = 'OK'
reply[:success] = true
reply[:CCNumber] = REXML::XPath.first(node, '//CCNumber').text
reply[:CCName] = REXML::XPath.first(node, '//CCName').text
reply[:CCExpiryMonth] = REXML::XPath.first(node, '//CCExpiryMonth').text
reply[:CCExpiryYear] = REXML::XPath.first(node, '//CCExpiryYear').text
reply
end
def commit(action, post)
raw =
begin
ssl_post(test? ? self.test_url : self.live_url, soap_request(post, action), 'Content-Type' => 'application/soap+xml; charset=utf-8')
rescue ResponseError => e
e.response.body
end
response = parse(raw)
EwayResponse.new(response[:success], response[:message], response,
test: test?,
authorization: response[:auth_code]
)
end
# Where we build the full SOAP 1.2 request using builder
def soap_request(arguments, action)
# eWay demands all fields be sent, but contain an empty string if blank
post = case action
when 'QueryCustomer'
arguments
when 'ProcessPayment'
default_payment_fields.merge(arguments)
when 'CreateCustomer'
default_customer_fields.merge(arguments)
when 'UpdateCustomer'
default_customer_fields.merge(arguments)
end
xml = Builder::XmlMarkup.new indent: 2
xml.instruct!
xml.tag! 'soap12:Envelope', {'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema', 'xmlns:soap12' => 'http://www.w3.org/2003/05/soap-envelope'} do
xml.tag! 'soap12:Header' do
xml.tag! 'eWAYHeader', {'xmlns' => 'https://www.eway.com.au/gateway/managedpayment'} do
xml.tag! 'eWAYCustomerID', @options[:login]
xml.tag! 'Username', @options[:username]
xml.tag! 'Password', @options[:password]
end
end
xml.tag! 'soap12:Body' do |x|
x.tag! action, {'xmlns' => 'https://www.eway.com.au/gateway/managedpayment'} do |y|
post.each do |key, value|
y.tag! key, value
end
end
end
end
xml.target!
end
def default_customer_fields
hash = {}
%w(CustomerRef Title FirstName LastName Company JobDesc Email Address Suburb State PostCode Country Phone Mobile Fax URL Comments CCNumber CCNameOnCard CCExpiryMonth CCExpiryYear).each do |field|
hash[field.to_sym] = ''
end
return hash
end
def default_payment_fields
hash = {}
%w(managedCustomerID amount invoiceReference invoiceDescription).each do |field|
hash[field.to_sym] = ''
end
return hash
end
class EwayResponse < Response
# add a method to response so we can easily get the eway token "ManagedCustomerID"
def token
@params['CreateCustomerResult']
end
end
end
end
end