lib/active_merchant/billing/gateways/world_net.rb
require 'nokogiri'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
# See https://helpdesk.worldnettps.com/support/solutions/articles/1000167298-integrator-guide
class WorldNetGateway < Gateway
self.test_url = 'https://testpayments.worldnettps.com/merchant/xmlpayment'
self.live_url = 'https://payments.worldnettps.com/merchant/xmlpayment'
self.homepage_url = 'http://worldnettps.com/'
self.display_name = 'WorldNet'
self.supported_countries = %w(IE GB US)
self.default_currency = 'EUR'
CARD_TYPES = {
visa: 'VISA',
master: 'MASTERCARD',
discover: 'DISCOVER',
american_express: 'AMEX',
maestro: 'MAESTRO',
diners_club: 'DINERS',
jcb: 'JCB',
secure_card: 'SECURECARD'
}.freeze
self.supported_cardtypes = CARD_TYPES.keys
def initialize(options = {})
requires!(options, :terminal_id, :secret)
options[:terminal_type] ||= 2 # eCommerce
super
end
def purchase(money, payment, options = {})
requires!(options, :order_id)
post = {}
add_invoice(post, money, options)
add_payment(post, payment)
add_address(post, payment, options)
add_customer_data(post, options)
commit('PAYMENT', post)
end
def authorize(money, payment, options = {})
requires!(options, :order_id)
post = {}
add_invoice(post, money, options)
add_payment(post, payment)
add_address(post, payment, options)
add_customer_data(post, options)
commit('PREAUTH', post)
end
def capture(money, authorization, options = {})
post = {}
add_invoice(post, money, options)
post[:uniqueref] = authorization
commit('PREAUTHCOMPLETION', post)
end
def refund(money, authorization, options = {})
requires!(options, :operator, :reason)
post = {}
post[:uniqueref] = authorization
add_invoice(post, money, options)
post[:operator] = options[:operator]
post[:reason] = options[:reason]
commit('REFUND', post)
end
def void(authorization, _options = {})
post = {}
post[:uniqueref] = authorization
commit('VOID', post)
end
def verify(credit_card, options = {})
MultiResponse.run(:use_first_response) do |r|
r.process { authorize(100, credit_card, options) }
r.process(:ignore_result) { void(r.authorization, options) }
end
end
def store(payment, options = {})
requires!(options, :order_id)
post = {}
post[:merchantref] = options[:order_id]
add_payment(post, payment)
commit('SECURECARDREGISTRATION', post)
end
def unstore(payment, options = {})
requires!(options, :order_id)
post = {}
post[:merchantref] = options[:order_id]
add_card_reference(post, payment)
commit('SECURECARDREMOVAL', post)
end
def supports_scrubbing?
true
end
def scrub(transcript)
transcript.
gsub(%r{(<CARDNUMBER>\d{6})\d+(\d{4}</CARDNUMBER>)}, '\1...\2').
gsub(%r{(<CVV>)\d+(</CVV)}, '\1...\2')
end
private
def add_customer_data(post, options)
post[:email] = options[:email]
post[:ipaddress] = options[:ip]
end
def add_address(post, _creditcard, options)
address = options[:billing_address] || options[:address]
return unless address
post[:address1] = address[:address1]
post[:address2] = address[:address2]
post[:city] = address[:city]
post[:country] = address[:country] # ISO 3166-1-alpha-2 code.
post[:postcode] = address[:zip]
end
def add_invoice(post, money, options)
post[:orderid] = options[:order_id]
post[:amount] = amount(money)
post[:currency] = (options[:currency] || currency(money))
post[:description] = options[:description]
end
def add_payment(post, payment)
# a payment triggered with a secure_card (tokenised card) will not
# respond to `:number`
if payment.respond_to?(:number)
post[:cardholdername] = cardholdername(payment)
post[:cardtype] = CARD_TYPES[payment.brand.to_sym]
post[:cardnumber] = payment.number
post[:cvv] = payment.verification_value if payment.verification_value
post[:cardexpiry] = expdate(payment)
else
post[:cardtype] = CARD_TYPES[:secure_card]
post[:cardnumber] = payment
end
end
def add_card_reference(post, payment)
post[:cardreference] = payment
end
def cardholdername(payment)
[payment.first_name, payment.last_name].join(' ').slice(0, 60)
end
def parse(action, body)
results = {}
xml = Nokogiri::XML(body)
resp = xml.xpath("//#{action}RESPONSE | //ERROR")
resp.children.each do |element|
results[element.name.downcase.to_sym] = element.text
end
results
end
def commit(action, parameters)
url = (test? ? test_url : live_url)
response = parse(action, ssl_post(url, post_data(action, parameters)))
Response.new(
success_from(action, response),
message_from(response),
response,
authorization: authorization_from(action, response),
avs_result: AVSResult.new(code: response[:avs_response]),
cvv_result: CVVResult.new(response[:cvv_response]),
test: test?,
error_code: success_from(action, response) ? nil : message_to_standard_error_code_from(response)
)
end
def success_from(action, response)
case action
when 'SECURECARDREGISTRATION'
response[:cardreference].present?
when 'SECURECARDREMOVAL'
response[:datetime].present? && response[:hash].present?
else
response[:responsecode] == 'A'
end
end
def message_to_standard_error_code_from(response)
case message_from(response)
when /DECLINED/
STANDARD_ERROR_CODE[:card_declined]
when /CVV FAILURE/
STANDARD_ERROR_CODE[:incorrect_cvc]
when /Invalid CARDEXPIRY field/
STANDARD_ERROR_CODE[:invalid_expiry_date]
else
STANDARD_ERROR_CODE[:processing_error]
end
end
def message_from(response)
response[:responsetext] || response[:errorstring]
end
def authorization_from(action, response)
case action
when 'SECURECARDREGISTRATION'
response[:cardreference]
else
response[:uniqueref]
end
end
def post_data(action, parameters = {})
parameters[:terminalid] = @options[:terminal_id]
parameters[:terminaltype] = @options[:terminal_type]
parameters[:transactiontype] = 7 # eCommerce
parameters[:datetime] = create_time_stamp
parameters[:hash] = case action
when 'SECURECARDREGISTRATION'
build_store_signature(parameters)
when 'SECURECARDREMOVAL'
build_unstore_signature(parameters)
else
build_signature(parameters)
end
build_xml_request(action, fields(action), parameters)
end
def create_time_stamp
Time.now.gmtime.strftime('%d-%m-%Y:%H:%M:%S:%L')
end
def build_signature(parameters)
str = parameters[:terminalid]
str += (parameters[:uniqueref] || parameters[:orderid])
str += (parameters[:amount].to_s + parameters[:datetime])
Digest::MD5.hexdigest(str + @options[:secret])
end
def build_store_signature(parameters)
str = parameters[:terminalid]
str += parameters[:merchantref]
str += parameters[:datetime]
str += parameters[:cardnumber]
str += parameters[:cardexpiry]
str += parameters[:cardtype]
str += parameters[:cardholdername]
Digest::MD5.hexdigest(str + @options[:secret])
end
def build_unstore_signature(parameters)
str = parameters[:terminalid]
str += parameters[:merchantref]
str += parameters[:datetime]
str += parameters[:cardreference]
Digest::MD5.hexdigest(str + @options[:secret])
end
def fields(action)
# Gateway expects fields in fixed order below.
case action
when 'PAYMENT', 'PREAUTH'
%i[
orderid
terminalid
amount
datetime
cardnumber cardtype cardexpiry cardholdername
hash
currency
terminaltype
transactiontype
email
cvv
address1 address2
postcode
description
city country
ipaddress
]
when 'PREAUTHCOMPLETION'
%i[uniqueref terminalid amount datetime hash]
when 'REFUND'
%i[uniqueref terminalid amount datetime hash
operator reason]
when 'VOID'
[:uniqueref]
when 'SECURECARDREGISTRATION'
%i[
merchantref
terminalid
datetime
cardnumber cardexpiry cardtype cardholdername
hash
dontchecksecurity
cvv
issueno
]
when 'SECURECARDREMOVAL'
%i[
merchantref
cardreference
terminalid
datetime
hash
]
end
end
def build_xml_request(action, fields, data)
xml = Builder::XmlMarkup.new indent: 2
xml.instruct!(:xml, version: '1.0', encoding: 'utf-8')
xml.tag!(action) do
fields.each do |field|
xml.tag!(field.to_s.upcase, data[field]) if data[field]
end
end
xml.target!
end
def expdate(credit_card)
sprintf('%02d%02d', credit_card.month, credit_card.year % 100)
end
end
end
end