require 'nokogiri'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
class ProPayGateway < Gateway
self.test_url = 'https://xmltest.propay.com/API/PropayAPI.aspx'
self.live_url = 'https://epay.propay.com/api/propayapi.aspx'
self.supported_countries = ['US', 'CA']
self.default_currency = 'USD'
self.money_format = :cents
self.supported_cardtypes = [:visa, :master, :american_express, :discover]
self.homepage_url = 'https://www.propay.com/'
self.display_name = 'ProPay'
STATUS_RESPONSE_CODES = {
"00" => "Success",
"20" => "Invalid username",
"21" => "Invalid transType",
"22" => "Invalid Currency Code",
"23" => "Invalid accountType",
"24" => "Invalid sourceEmail",
"25" => "Invalid firstName",
"26" => "Invalid mInitial",
"27" => "Invalid lastName",
"28" => "Invalid billAddr",
"29" => "Invalid aptNum",
"30" => "Invalid city",
"31" => "Invalid state",
"32" => "Invalid billZip",
"33" => "Invalid mailAddr",
"34" => "Invalid mailApt",
"35" => "Invalid mailCity",
"36" => "Invalid mailState",
"37" => "Invalid mailZip",
"38" => "Invalid dayPhone",
"39" => "Invalid evenPhone",
"40" => "Invalid ssn",
"41" => "Invalid dob",
"42" => "Invalid recEmail",
"43" => "Invalid knownAccount",
"44" => "Invalid amount",
"45" => "Invalid invNum",
"46" => "Invalid rtNum",
"47" => "Invalid accntNum",
"48" => "Invalid ccNum",
"49" => "Invalid expDate",
"50" => "Invalid cvv2",
"51" => "Invalid transNum and/or Unable to act perform actions on transNum due to funding",
"52" => "Invalid splitNum",
"53" => "A ProPay account with this email address already exists AND/OR User has no account number",
"54" => "A ProPay account with this social security number already exists",
"55" => "The email address provided does not correspond to a ProPay account.",
"56" => "Recipient’s email address shouldn’t have a ProPay account and does",
"57" => "Cannot settle transaction because it already expired",
"58" => "Credit card declined",
"59" => "Invalid Credential or IP address not allowed",
"60" => "Credit card authorization timed out; retry at a later time",
"61" => "Amount exceeds single transaction limit",
"62" => "Amount exceeds monthly volume limit",
"63" => "Insufficient funds in account",
"64" => "Over credit card use limit",
"65" => "Miscellaneous error",
"66" => "Denied a ProPay account",
"67" => "Unauthorized service requested",
"68" => "Account not affiliated",
"69" => "Duplicate invoice number (The same card was charged for the same amount with the same invoice number (including blank invoices) in a 1 minute period. Details about the original transaction are included whenever a 69 response is returned. These details include a repeat of the auth code, the original AVS response, and the original CVV response.)",
"70" => "Duplicate external ID",
"71" => "Account previously set up, but problem affiliating it with partner",
"72" => "The ProPay Account has already been upgraded to a Premium Account",
"73" => "Invalid Destination Account",
"74" => "Account or Trans Error",
"75" => "Money already pulled",
"76" => "Not Premium (used only for push/pull transactions)",
"77" => "Empty results",
"78" => "Invalid Authentication",
"79" => "Generic account status error",
"80" => "Invalid Password",
"81" => "Account Expired",
"82" => "InvalidUserID",
"83" => "BatchTransCountError",
"84" => "InvalidBeginDate",
"85" => "InvalidEndDate",
"86" => "InvalidExternalID",
"87" => "DuplicateUserID",
"88" => "Invalid track 1",
"89" => "Invalid track 2",
"90" => "Transaction already refunded",
"91" => "Duplicate Batch ID"
}
TRANSACTION_RESPONSE_CODES = {
"00" => "Success",
"1" => "Transaction blocked by issuer",
"4" => "Pick up card and deny transaction",
"5" => "Problem with the account",
"6" => "Customer requested stop to recurring payment",
"7" => "Customer requested stop to all recurring payments",
"8" => "Honor with ID only",
"9" => "Unpaid items on customer account",
"12" => "Invalid transaction",
"13" => "Amount Error",
"14" => "Invalid card number",
"15" => "No such issuer. Could not route transaction",
"16" => "Refund error",
"17" => "Over limit",
"19" => "Reenter transaction or the merchant account may be boarded incorrectly",
"25" => "Invalid terminal 41 Lost card",
"43" => "Stolen card",
"51" => "Insufficient funds",
"52" => "No such account",
"54" => "Expired card",
"55" => "Incorrect PIN",
"57" => "Bank does not allow this type of purchase",
"58" => "Credit card network does not allow this type of purchase for your merchant account.",
"61" => "Exceeds issuer withdrawal limit",
"62" => "Issuer does not allow this card to be charged for your business.",
"63" => "Security Violation",
"65" => "Activity limit exceeded",
"75" => "PIN tries exceeded",
"76" => "Unable to locate account",
"78" => "Account not recognized",
"80" => "Invalid Date",
"82" => "Invalid CVV2",
"83" => "Cannot verify the PIN",
"85" => "Service not supported for this card",
"93" => "Cannot complete transaction. Customer should call 800 number.",
"95" => "Misc Error Transaction failure",
"96" => "Issuer system malfunction or timeout.",
"97" => "Approved for a lesser amount. ProPay will not settle and consider this a decline.",
"98" => "Failure HV",
"99" => "Generic decline or unable to parse issuer response code"
}
def initialize(options={})
requires!(options, :cert_str)
super
end
def purchase(money, payment, options={})
request = build_xml_request do |xml|
add_invoice(xml, money, options)
add_payment(xml, payment, options)
add_address(xml, options)
add_account(xml, options)
add_recurring(xml, options)
xml.transType "04"
end
commit(request)
end
def authorize(money, payment, options={})
request = build_xml_request do |xml|
add_invoice(xml, money, options)
add_payment(xml, payment, options)
add_address(xml, options)
add_account(xml, options)
add_recurring(xml, options)
xml.transType "05"
end
commit(request)
end
def capture(money, authorization, options={})
request = build_xml_request do |xml|
add_invoice(xml, money, options)
add_account(xml, options)
xml.transNum authorization
xml.transType "06"
end
commit(request)
end
def refund(money, authorization, options={})
request = build_xml_request do |xml|
add_invoice(xml, money, options)
add_account(xml, options)
xml.transNum authorization
xml.transType "07"
end
commit(request)
end
def void(authorization, options={})
refund(nil, authorization, options)
end
def credit(money, payment, options={})
request = build_xml_request do |xml|
add_invoice(xml, money, options)
add_payment(xml, payment, options)
add_account(xml, options)
xml.transType "35"
end
commit(request)
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 supports_scrubbing?
true
end
def scrub(transcript)
transcript.
gsub(%r(().+()), '\1[FILTERED]\2').
gsub(%r(().+()), '\1[FILTERED]\2').
gsub(%r(().+()), '\1[FILTERED]\2')
end
private
def add_payment(xml, payment, options)
xml.ccNum payment.number
xml.expDate "#{format(payment.month, :two_digits)}#{format(payment.year, :two_digits)}"
xml.CVV2 payment.verification_value
xml.cardholderName payment.name
end
def add_address(xml, options)
if address = options[:billing_address] || options[:address]
xml.addr address[:address1]
xml.aptNum address[:address2]
xml.city address[:city]
xml.state address[:state]
xml.zip address[:zip]
end
end
def add_account(xml, options)
xml.accountNum options[:account_num]
end
def add_invoice(xml, money, options)
xml.amount amount(money)
xml.currencyCode options[:currency] || currency(money)
xml.invNum options[:order_id] || SecureRandom.hex(25)
end
def add_recurring(xml, options)
xml.recurringPayment options[:recurring_payment]
end
def parse(body)
results = {}
xml = Nokogiri::XML(body)
resp = xml.xpath("//XMLResponse/XMLTrans")
resp.children.each do |element|
results[element.name.underscore.downcase.to_sym] = element.text
end
results
end
def commit(parameters)
url = (test? ? test_url : live_url)
response = parse(ssl_post(url, parameters))
Response.new(
success_from(response),
message_from(response),
response,
authorization: authorization_from(response),
avs_result: AVSResult.new(code: response[:avs]),
cvv_result: CVVResult.new(response[:cvv2_resp]),
test: test?,
error_code: error_code_from(response)
)
end
def success_from(response)
response[:status] == "00"
end
def message_from(response)
return "Success" if success_from(response)
message = STATUS_RESPONSE_CODES[response[:status]]
message += " - #{TRANSACTION_RESPONSE_CODES[response[:response_code]]}" if response[:response_code]
message
end
def authorization_from(response)
response[:trans_num]
end
def error_code_from(response)
unless success_from(response)
response[:status]
end
end
def build_xml_request
builder = Nokogiri::XML::Builder.new do |xml|
xml.XMLRequest do
xml.certStr @options[:cert_str]
xml.class_ "partner"
xml.XMLTrans do
yield(xml)
end
end
end
builder.to_xml
end
end
def underscore(camel_cased_word)
camel_cased_word.to_s.gsub(/::/, '/').
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
gsub(/([a-z\d])([A-Z])/,'\1_\2').
tr("-", "_").
downcase
end
end
end