require 'securerandom'
require 'digest'
module ActiveMerchant #:nodoc:
module Billing #:nodoc:
# ==== USA ePay Advanced SOAP Interface
#
# This class encapuslates USA ePay's Advanced SOAP Interface. The Advanced Soap Interface allows
# standard transactions, storing customer information, and recurring billing. Storing sensitive
# information on USA ePay's servers can help with PCI DSS compliance, since customer and card data
# do not need to be stored locally.
#
# Make sure you have enabled this functionality for your account with USA ePay.
#
# Information about the Advanced SOAP interface is available on the {USA ePay wiki}[http://wiki.usaepay.com/developer/soap].
#
# ==== Login, Password, and Software ID
#
# Please follow all of USA ePay's directions for acquiring all accounts and settings.
#
# The value used for :login is the Key value found in the Merchant Console under Settings > Source
# Key. You will have to add this key in the USA ePay Merchant Console.
#
# The value used for :password is the pin value also found and assigned in the Merchant Console under
# Settings > Source Key. The pin is required to use all but basic transactions in the SOAP interface.
# You will have to add the pin to your source key, as it defaults to none.
#
# The value used for the :software_id is found in the Developer's Login under the Developers Center
# in your WSDL. It is the 8 character value in tag. A masked example:
#
# It is also found in the link to your WSDL. This is required as every account has a different path
# SOAP requests are submitted to. Optionally, you can provide the entire urls via :live_url and :test_url, if your prefer.
#
# ==== Responses
# * #success? -- +true+ if transmitted and returned correctly
# * #message -- response or fault message
# * #authorization -- reference_number or nil
# * #params -- hash of entire soap response contents
#
# ==== Address Options
# * :billing_address/:shipping_address -- contains some extra options
# * :name -- virtual attribute; will split to first and last name
# * :first_name
# * :last_name
# * :address1
# * :address2
# * :city
# * :state
# * :zip
# * :country
# * :phone
# * :email
# * :fax
# * :company
#
# ==== Support:
# * Questions: post to {active_merchant google group}[http://groups.google.com/group/activemerchant]
# * Feedback/fixes: matt (at) nearapogee (dot) com
#
# ==== Links:
# * {USA ePay Merchant Console}[https://sandbox.usaepay.com/login]
# * {USA ePay Developer Login}[https://www.usaepay.com/developer/login]
#
class UsaEpayAdvancedGateway < Gateway
API_VERSION = "1.4"
TEST_URL_BASE = 'https://sandbox.usaepay.com/soap/gate/' #:nodoc:
LIVE_URL_BASE = 'https://www.usaepay.com/soap/gate/' #:nodoc:
self.test_url = TEST_URL_BASE
self.live_url = LIVE_URL_BASE
FAILURE_MESSAGE = "Default Failure" #:nodoc:
self.supported_countries = ['US']
self.supported_cardtypes = [:visa, :master, :american_express, :discover, :diners_club, :jcb]
self.homepage_url = 'http://www.usaepay.com/'
self.display_name = 'USA ePay Advanced SOAP Interface'
CUSTOMER_OPTIONS = {
:id => [:string, 'CustomerID'], # merchant assigned number
:notes => [:string, 'Notes'],
:data => [:string, 'CustomData'],
:url => [:string, 'URL'],
# Recurring Billing
:enabled => [:boolean, 'Enabled'],
:schedule => [:string, 'Schedule'],
:number_left => [:integer, 'NumLeft'],
:currency => [:string, 'Currency'],
:description => [:string, 'Description'],
:order_id => [:string, 'OrderID'],
:user => [:string, 'User'],
:source => [:string, 'Source'],
:send_receipt => [:boolean, 'SendReceipt'],
:receipt_note => [:string, 'ReceiptNote'],
# Point of Sale
:price_tier => [:string, 'PriceTier'],
:tax_class => [:string, 'TaxClass'],
:lookup_code => [:string, 'LookupCode']
} #:nodoc:
ADDRESS_OPTIONS = {
:first_name => [:string, 'FirstName'],
:last_name => [:string, 'LastName'],
:address1 => [:string, 'Street'],
:address2 => [:string, 'Street2'],
:city => [:string, 'City'],
:state => [:string, 'State'],
:zip => [:string, 'Zip'],
:country => [:string, 'Country'],
:phone => [:string, 'Phone'],
:email => [:string, 'Email'],
:fax => [:string, 'Fax'],
:company => [:string, 'Company']
} #:nodoc:
CUSTOMER_TRANSACTION_REQUEST_OPTIONS = {
:command => [:string, 'Command'],
:ignore_duplicate => [:boolean, 'IgnoreDuplicate'],
:client_ip => [:string, 'ClientIP'],
:customer_receipt => [:boolean, 'CustReceipt'],
:customer_email => [:boolean, 'CustReceiptEmail'],
:customer_template => [:boolean, 'CustReceiptName'],
:merchant_receipt => [:boolean, 'MerchReceipt'],
:merchant_email => [:boolean, 'MerchReceiptEmail'],
:merchant_template => [:boolean, 'MerchReceiptName'],
:verification_value => [:boolean, 'isRecurring'],
:software => [:string, 'Software']
} #:nodoc:
TRANSACTION_REQUEST_OBJECT_OPTIONS = {
:command => [:string, 'Command'],
:ignore_duplicate => [:boolean, 'IgnoreDuplicate'],
:authorization_code => [:string, 'AuthCode'],
:reference_number => [:string, 'RefNum'],
:account_holder => [:string, 'AccountHolder'],
:client_ip => [:string, 'ClientIP'],
:customer_id => [:string, 'CustomerID'],
:customer_receipt => [:boolean, 'CustReceipt'],
:customer_template => [:boolean, 'CustReceiptName'],
:software => [:string, 'Software']
} #:nodoc:
TRANSACTION_DETAIL_OPTIONS = {
:invoice => [:string, 'Invoice'],
:po_number => [:string, 'PONum'],
:order_id => [:string, 'OrderID'],
:clerk => [:string, 'Clerk'],
:terminal => [:string, 'Terminal'],
:table => [:string, 'Table'],
:description => [:string, 'Description'],
:comments => [:string, 'Comments'],
:allow_partial_auth => [:boolean, 'AllowPartialAuth'],
:currency => [:string, 'Currency'],
:non_tax => [:boolean, 'NonTax'],
} #:nodoc:
TRANSACTION_DETAIL_MONEY_OPTIONS = {
:amount => [:double, 'Amount'],
:tax => [:double, 'Tax'],
:tip => [:double, 'Tip'],
:non_tax => [:boolean, 'NonTax'],
:shipping => [:double, 'Shipping'],
:discount => [:double, 'Discount'],
:subtotal => [:double, 'Subtotal']
} #:nodoc:
CREDIT_CARD_DATA_OPTIONS = {
:magnetic_stripe => [:string, 'MagStripe'],
:dukpt => [:string, 'DUKPT'],
:signature => [:string, 'Signature'],
:terminal_type => [:string, 'TermType'],
:magnetic_support => [:string, 'MagSupport'],
:xid => [:string, 'XID'],
:cavv => [:string, 'CAVV'],
:eci => [:integer, 'ECI'],
:internal_card_authorization => [:boolean, 'InternalCardAuth'],
:pares => [:string, 'Pares']
} #:nodoc:
CHECK_DATA_OPTIONS = {
:drivers_license => [:string, 'DriversLicense'],
:drivers_license_state => [:string, 'DriversLicenseState'],
:record_type => [:string, 'RecordType'],
:aux_on_us => [:string, 'AuxOnUS'],
:epc_code => [:string, 'EpcCode'],
:front_image => [:string, 'FrontImage'],
:back_image => [:string, 'BackImage']
} #:nodoc:
RECURRING_BILLING_OPTIONS = {
:schedule => [:string, 'Schedule'],
:number_left => [:integer, 'NumLeft'],
:enabled => [:boolean, 'Enabled']
} #:nodoc:
AVS_RESULTS = {
'Y' => %w( YYY Y YYA YYD ),
'Z' => %w( NYZ Z ),
'A' => %w( YNA A YNY ),
'N' => %w( NNN N NN ),
'X' => %w( YYX X ),
'W' => %w( NYW W ),
'XXW' => %w( XXW ),
'XXU' => %w( XXU ),
'R' => %w( XXR R U E ),
'S' => %w( XXS S ),
'XXE' => %w( XXE ),
'G' => %w( XXG G C I ),
'B' => %w( YYG B M ),
'D' => %w( GGG D ),
'P' => %w( YGG P )
}.inject({}) do |map, (type, codes)|
codes.each { |code| map[code] = type }
map
end #:nodoc:
AVS_CUSTOM_MESSAGES = {
'XXW' => 'Card number not on file.',
'XXU' => 'Address information not verified for domestic transaction.',
'XXE' => 'Address verification not allowed for card type.'
} #:nodoc:
# Create a new gateway.
#
# ==== Required
# * At least the live_url OR the software_id must be present.
# * :software_id -- 8 character software id
# OR
# * :test_url -- full url for testing
# * :live_url -- full url for live/production
#
# ==== Optional
# * :soap_response -- set to +true+ to add :soap_response to the params hash containing the entire soap xml message
#
def initialize(options = {})
requires!(options, :login, :password)
if options[:software_id]
self.live_url = "#{LIVE_URL_BASE}#{options[:software_id].to_s}"
self.test_url = "#{TEST_URL_BASE}#{options[:software_id].to_s}"
else
self.live_url = options[:live_url].to_s
self.test_url = options[:test_url].to_s if options[:test_url]
end
super
end
# Standard Gateway Methods ======================================
# Make a purchase with a credit card. (Authorize and
# capture for settlement.)
#
# Note: See run_transaction for additional options.
#
def purchase(money, creditcard, options={})
run_sale(options.merge!(:amount => money, :payment_method => creditcard))
end
# Authorize an amount on a credit card or account.
#
# Note: See run_transaction for additional options.
#
def authorize(money, creditcard, options={})
run_auth_only(options.merge!(:amount => money, :payment_method => creditcard))
end
# Capture an authorized transaction.
#
# Note: See run_transaction for additional options.
#
def capture(money, identification, options={})
capture_transaction(options.merge!(:amount => money, :reference_number => identification))
end
# Void a previous transaction that has not been settled.
#
# Note: See run_transaction for additional options.
#
def void(identification, options={})
void_transaction(options.merge!(:reference_number => identification))
end
# Refund a previous transaction.
#
# Note: See run_transaction for additional options.
#
def refund(money, identification, options={})
refund_transaction(options.merge!(:amount => money, :reference_number => identification))
end
def credit(money, identification, options={})
deprecated CREDIT_DEPRECATION_MESSAGE
refund(money, identification, options)
end
# Customer ======================================================
# Add a customer.
#
# ==== Options
# * :id -- merchant assigned id
# * :notes -- notes about customer
# * :data -- base64 data about customer
# * :url -- customer website
# * :billing_address -- usual options
# * :payment_methods -- array of payment method hashes.
# * :method -- credit_card or check
# * :name -- optional name/label for the method
# * :sort -- optional integer value specifying the backup sort order, 0 is default
#
# ==== Recurring Options
# * :enabled -- +true+ enables recurring
# * :schedule -- daily, weekly, bi-weekly (every two weeks), monthly, bi-monthly (every two months), quarterly, bi-annually (every six months), annually, first of month, last day of month
# * :number_left -- number of payments left; -1 for unlimited
# * :next -- date of next payment (Date/Time)
# * :amount -- amount of recurring payment
# * :tax -- tax portion of amount
# * :currency -- numeric currency code
# * :description -- description of transaction
# * :order_id -- transaction order id
# * :user -- merchant username assigned to transaction
# * :source -- name of source key assigned to billing
# * :send_receipt -- +true+ to send client a receipt
# * :receipt_note -- leave a note on the receipt
#
# ==== Point of Sale Options
# * :price_tier -- name of customer price tier
# * :tax_class -- tax class
# * :lookup_code -- lookup code from customer/member id card; barcode or magnetic stripe; can be assigned by merchant; defaults to system assigned if blank
#
# ==== Response
# * #message -- customer number assigned by gateway
#
def add_customer(options={})
request = build_request(__method__, options)
commit(__method__, request)
end
# Update a customer by replacing all of the customer details.
#
# ==== Required
# * :customer_number -- customer to update
#
# ==== Options
# * Same as add_customer
#
def update_customer(options={})
requires! options, :customer_number
request = build_request(__method__, options)
commit(__method__, request)
end
# Enable a customer for recurring billing.
#
# Note: Customer does not need to have all recurring parameters to succeed.
#
# ==== Required
# * :customer_number
#
def enable_customer(options={})
requires! options, :customer_number
request = build_request(__method__, options)
commit(__method__, request)
end
# Disable a customer for recurring billing.
#
# ==== Required
# * :customer_number
#
def disable_customer(options={})
requires! options, :customer_number
request = build_request(__method__, options)
commit(__method__, request)
end
# Add a payment method to a customer.
#
# ==== Required
# * :customer_number -- number returned by add_customer response.message
# * :payment_method
# * :method -- credit_card or check
# * :name -- optional name/label for the method
# * :sort -- an integer value specifying the backup sort order, 0 is default
#
# ==== Optional
# * :make_default -- set +true+ to make default
# * :verify -- set +true+ to run auth_only verification; throws fault if cannot verify
#
# ==== Response
# * #message -- method_id of new customer payment method
#
def add_customer_payment_method(options={})
requires! options, :customer_number
request = build_request(__method__, options)
commit(__method__, request)
end
# Retrive all of the payment methods belonging to a customer
#
# ==== Required
# * :customer_number
#
# ==== Response
# * #message -- either a single hash or an array of hashes of payment methods
#
def get_customer_payment_methods(options={})
requires! options, :customer_number
request = build_request(__method__, options)
commit(__method__, request)
end
# Retrive one of the payment methods belonging to a customer
#
# ==== Required
# * :customer_number
# * :method_id
#
# ==== Response
# * #message -- hash of payment method
#
def get_customer_payment_method(options={})
requires! options, :customer_number, :method_id
request = build_request(__method__, options)
commit(__method__, request)
end
# Update a customer payment method.
#
# ==== Required
# * :method_id -- method_id to update
#
# ==== Options
# * :method -- credit_card or check
# * :name -- optional name/label for the method
# * :sort -- an integer value specifying the backup sort order, 0 is default
# * :verify -- set +true+ to run auth_only verification; throws fault if cannot verify
#
# ==== Response
# * #message -- hash of payment method
#
def update_customer_payment_method(options={})
requires! options, :method_id
request = build_request(__method__, options)
commit(__method__, request)
end
# Delete one the payment methods belonging to a customer
#
# ==== Required
# * :customer_number
# * :method_id
#
def delete_customer_payment_method(options={})
requires! options, :customer_number, :method_id
request = build_request(__method__, options)
commit(__method__, request)
end
# Delete a customer.
#
# ==== Required
# * :customer_number
#
def delete_customer(options={})
requires! options, :customer_number
request = build_request(__method__, options)
commit(__method__, request)
end
# Run a transaction for an existing customer in the database.
#
# ==== Required Options
# * :customer_number -- gateway assigned identifier
# * :command -- Sale, AuthOnly, Credit, Check, CheckCredit
# * :amount -- total amount
#
# ==== Options
# * :method_id -- which payment method to use, 0/nil/omitted for default method
# * :ignore_duplicate -- +true+ overrides duplicate transaction
# * :client_ip -- client ip address
# * :customer_receipt -- +true+, sends receipt to customer. active_merchant defaults to +false+
# * :customer_email -- specify if different than customer record
# * :customer_template -- name of template
# * :merchant_receipt -- +true+, sends receipt to merchant. active_merchant defaults to +false+
# * :merchant_email -- required if :merchant_receipt set to +true+
# * :merchant_template -- name of template
# * :recurring -- defaults to +false+ *see documentation*
# * :verification_value -- pci forbids storage of this value, only required for CVV2 validation
# * :software -- active_merchant sets to required gateway option value
# * :line_items -- XXX not implemented yet
# * :custom_fields -- XXX not implemented yet
#
# ==== Transaction Options
# * :invoice -- transaction invoice number; truncated to 10 characters; defaults to reference_number
# * :po_number -- commercial purchase order number; upto 25 characters
# * :order_id -- should be used to assign a unique id; upto 64 characters
# * :clerk -- sales clerk
# * :terminal -- terminal name
# * :table -- table name/number
# * :description -- description
# * :comments -- comments
# * :allow_partial_auth -- allow partial authorization if full amount is not available; defaults +false+
# * :currency -- numeric currency code
# * :tax -- tax portion of amount
# * :tip -- tip portion of amount
# * :non_tax -- +true+ if transaction is non-taxable
# * :shipping -- shipping portion of amount
# * :discount -- amount of discount
# * :subtotal -- amount of transaction before tax, tip, shipping, and discount are applied
#
# ==== Response
# * #message -- transaction response hash
#
def run_customer_transaction(options={})
requires! options, :customer_number, :command, :amount
request = build_request(__method__, options)
commit(__method__, request)
end
# Transactions ==================================================
# Run a transaction.
#
# Note: run_sale, run_auth_only, run_credit, run_check_sale, run_check_credit
# methods are also available. Each takes the same options as
# run_transaction, but the :command option is not required.
#
# Recurring Note: If recurring options are included USA ePay will create a
# new customer record with the supplied information. The customer number
# will be returned in the response.
#
# ==== Options
# * :payment_method -- credit_card or check
# * :command -- sale, credit, void, creditvoid, authonly, capture, postauth, check, checkcredit; defaults to sale; only required for run_transaction when other than sale
# * :reference_number -- for the original transaction; obtained by sale or authonly
# * :authorization_code -- required for postauth; obtained offline
# * :ignore_duplicate -- set +true+ if you want to override the duplicate transaction handling
# * :account_holder -- name of account holder
# * :customer_id -- merchant assigned id
# * :customer_receipt -- set +true+ to email receipt to billing email address
# * :customer_template -- name of template
# * :software -- stamp merchant software version for tracking
# * :billing_address -- see UsaEpayCimGateway documentation for all address fields
# * :shipping_address -- see UsaEpayCimGateway documentation for all address fields
# * :recurring -- used for recurring billing transactions
# * :schedule -- disabled, daily, weekly, bi-weekly (every two weeks), monthly, bi-monthly (every two months), quarterly, bi-annually (every six months), annually
# * :next -- date customer billed next (Date/Time)
# * :expire -- date the recurring transactions end (Date/Time)
# * :number_left -- transactions remaining in billing cycle
# * :amount -- amount to be billed each recurring transaction
# * :enabled -- states if currently active
# * :line_items -- XXX not implemented yet
# * :custom_fields -- XXX not implemented yet
#
# ==== Transaction Options
# * :amount -- total amount
# * :invoice -- transaction invoice number; truncated to 10 characters; defaults to reference_number
# * :po_number -- commercial purchase order number; upto 25 characters
# * :order_id -- should be used to assign a unique id; upto 64 characters
# * :clerk -- sales clerk
# * :terminal -- terminal name
# * :table -- table name/number
# * :description -- description
# * :comments -- comments
# * :allow_partial_auth -- allow partial authorization if full amount is not available; defaults +false+
# * :currency -- numeric currency code
# * :tax -- tax portion of amount
# * :tip -- tip portion of amount
# * :non_tax -- +true+ if transaction is non-taxable
# * :shipping -- shipping portion of amount
# * :discount -- amount of discount
# * :subtotal -- amount of transaction before tax, tip, shipping, and discount are applied
#
# ==== Response
# * #message -- transaction response hash
#
def run_transaction(options={})
request = build_request(__method__, options)
commit(__method__, request)
end
TRANSACTION_METHODS = [
:run_sale, :run_auth_only, :run_credit,
:run_check_sale, :run_check_credit
] #:nodoc:
TRANSACTION_METHODS.each do |method|
define_method method do |options|
request = build_request(method, options)
commit(method, request)
end
end
# Post an authorization code obtained offline.
#
# ==== Required
# * :authorization_code -- obtained offline
#
# ==== Options
# * Same as run_transaction
#
# ==== Response
# * #message -- transaction response hash
#
def post_auth(options={})
requires! options, :authorization_code
request = build_request(__method__, options)
commit(__method__, request)
end
# Capture an authorized transaction and move it into the current batch
# for settlement.
#
# Note: Check with merchant bank for details/restrictions on differing
# amounts than the original authorization.
#
# ==== Required
# * :reference_number
#
# ==== Options
# * :amount -- may be different than original amount; 0 will void authorization
#
# ==== Response
# * #message -- transaction response hash
#
def capture_transaction(options={})
requires! options, :reference_number
request = build_request(__method__, options)
commit(__method__, request)
end
# Void a transaction.
#
# Note: Can only be voided before being settled.
#
# ==== Required
# * :reference_number
#
# ==== Response
# * #message -- transaction response hash
#
def void_transaction(options={})
requires! options, :reference_number
request = build_request(__method__, options)
commit(__method__, request)
end
# Refund transaction.
#
# Note: Required after a transaction has been settled. Refunds
# both credit card and check transactions.
#
# ==== Required
# * :reference_number
# * :amount -- amount to refund; 0 will refund original amount
#
# ==== Response
# * #message -- transaction response hash
#
def refund_transaction(options={})
requires! options, :reference_number, :amount
request = build_request(__method__, options)
commit(__method__, request)
end
# Override transaction flagged for manager approval.
#
# Note: Checks only!
#
# ==== Required
# * :reference_number
#
# ==== Options
# * :reason
#
# ==== Response
# * #message -- transaction response hash
#
def override_transaction(options={})
requires! options, :reference_number
request = build_request(__method__, options)
commit(__method__, request)
end
# Quick Transactions ============================================
# Run a sale transaction based off of a past transaction.
#
# Transfers referenced transaction's payment method to this
# transaction. As of 6/2011, USA ePay blocks credit card numbers
# at 3 years.
#
# ==== Required
# * :reference_number -- transaction to reference payment from
# * :amount -- total amount
#
# ==== Options
# * :authorize_only -- set +true+ if you just want to authorize
#
# ==== Transaction Options
# * :invoice -- transaction invoice number; truncated to 10 characters; defaults to reference_number
# * :po_number -- commercial purchase order number; upto 25 characters
# * :order_id -- should be used to assign a unique id; upto 64 characters
# * :clerk -- sales clerk
# * :terminal -- terminal name
# * :table -- table name/number
# * :description -- description
# * :comments -- comments
# * :allow_partial_auth -- allow partial authorization if full amount is not available; defaults +false+
# * :currency -- numeric currency code
# * :tax -- tax portion of amount
# * :tip -- tip portion of amount
# * :non_tax -- +true+ if transaction is non-taxable
# * :shipping -- shipping portion of amount
# * :discount -- amount of discount
# * :subtotal -- amount of transaction before tax, tip, shipping, and discount are applied
#
# ==== Response
# * #message -- transaction response hash
#
def run_quick_sale(options={})
requires! options, :reference_number, :amount
request = build_request(__method__, options)
commit(__method__, request)
end
# Run a credit based off of a past transaction.
#
# Transfers referenced transaction's payment method to this
# transaction. As of 6/2011, USA ePay blocks credit card numbers
# at 3 years.
#
# ==== Required
# * :reference_number -- transaction to reference payment from
#
# ==== Transaction Options
# * :amount -- total amount
# * :invoice -- transaction invoice number; truncated to 10 characters; defaults to reference_number
# * :po_number -- commercial purchase order number; upto 25 characters
# * :order_id -- should be used to assign a unique id; upto 64 characters
# * :clerk -- sales clerk
# * :terminal -- terminal name
# * :table -- table name/number
# * :description -- description
# * :comments -- comments
# * :allow_partial_auth -- allow partial authorization if full amount is not available; defaults +false+
# * :currency -- numeric currency code
# * :tax -- tax portion of amount
# * :tip -- tip portion of amount
# * :non_tax -- +true+ if transaction is non-taxable
# * :shipping -- shipping portion of amount
# * :discount -- amount of discount
# * :subtotal -- amount of transaction before tax, tip, shipping, and discount are applied
#
# ==== Response
# * #message -- transaction response hash
#
def run_quick_credit(options={})
requires! options, :reference_number
request = build_request(__method__, options)
commit(__method__, request)
end
# Transaction Status ============================================
# Retrieve details of a specified transaction.
#
# ==== Required
# * :reference_number
#
# ==== Response
# * #message -- transaction hash
#
def get_transaction(options={})
requires! options, :reference_number
request = build_request(__method__, options)
commit(__method__, request)
end
# Check status of a transaction.
#
# ==== Required
# * :reference_number
#
# ==== Response
# * response.success -- success of the referenced transaction
# * response.message -- message of the referenced transaction
# * response.authorization -- same as :reference_number in options
#
def get_transaction_status(options={})
requires! options, :reference_number
request = build_request(__method__, options)
commit(__method__, request)
end
# Check status of a transaction (custom).
#
# ==== Required
# * :reference_number
# * :fields -- string array of fields to retrieve
# * Response.AuthCode
# * Response.AvsResult
# * Response.AvsResultCode
# * Response.BatchNum
# * Response.CardCodeResult
# * Response.CardCodeResultCode
# * Response.ConversionRate
# * Response.ConvertedAmount
# * Response.ConvertedAmountCurrency
# * Response.Error
# * Response.ErrorCode
# * Response.RefNum
# * Response.Result
# * Response.ResultCode
# * Response.Status
# * Response.StatusCode
# * CheckTrace.TrackingNum
# * CheckTrace.Effective
# * CheckTrace.Processed
# * CheckTrace.Settled
# * CheckTrace.Returned
# * CheckTrace.BankNote
# * DateTime
# * AccountHolder
# * Details.Invoice
# * Details.PoNum
# * Details.OrderID
# * Details.Clerk
# * Details.Terminal
# * Details.Table
# * Details.Description
# * Details.Amount
# * Details.Currency
# * Details.Tax
# * Details.Tip
# * Details.NonTax
# * Details.Shipping
# * Details.Discount
# * Details.Subtotal
# * CreditCardData.CardType
# * CreditCardData.CardNumber
# * CreditCardData.CardExpiration
# * CreditCardData.CardCode
# * CreditCardData.AvsStreet
# * CreditCardData.AvsZip
# * CreditCardData.CardPresent
# * CheckData.CheckNumber
# * CheckData.Routing
# * CheckData.Account
# * CheckData.SSN
# * CheckData.DriversLicense
# * CheckData.DriversLicenseState
# * CheckData.RecordType
# * User
# * Source
# * ServerIP
# * ClientIP
# * CustomerID
# * BillingAddress.FirstName
# * BillingAddress.LastName
# * BillingAddress.Company
# * BillingAddress.Street
# * BillingAddress.Street2
# * BillingAddress.City
# * BillingAddress.State
# * BillingAddress.Zip
# * BillingAddress.Country
# * BillingAddress.Phone
# * BillingAddress.Fax
# * BillingAddress.Email
# * ShippingAddress.FirstName
# * ShippingAddress.LastName
# * ShippingAddress.Company
# * ShippingAddress.Street
# * ShippingAddress.Street2
# * ShippingAddress.City
# * ShippingAddress.State
# * ShippingAddress.Zip
# * ShippingAddress.Country
# * ShippingAddress.Phone
# * ShippingAddress.Fax
# * ShippingAddress.Email
#
# ==== Response
# * #message -- hash; keys are the field values
#
def get_transaction_custom(options={})
requires! options, :reference_number, :fields
request = build_request(__method__, options)
commit(__method__, request)
end
# Check status of a check transaction.
#
# ==== Required
# * :reference_number
#
# ==== Response
# * #message -- check trace hash
#
def get_check_trace(options={})
requires! options, :reference_number
request = build_request(__method__, options)
commit(__method__, request)
end
# Account =======================================================
# Retrieve merchant account details
#
# ==== Response
# * #message -- account hash
#
def get_account_details
request = build_request(__method__)
commit(__method__, request)
end
# Builders ======================================================
private
# Build soap header, etc.
def build_request(action, options = {})
soap = Builder::XmlMarkup.new
soap.instruct!(:xml, :version => '1.0', :encoding => 'utf-8')
soap.tag! "SOAP-ENV:Envelope",
'xmlns:SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope/',
'xmlns:ns1' => 'urn:usaepay',
'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
'xmlns:SOAP-ENC' => 'http://schemas.xmlsoap.org/soap/encoding/',
'SOAP-ENV:encodingStyle' => 'http://schemas.xmlsoap.org/soap/encoding/' do |soap|
soap.tag! "SOAP-ENV:Body" do |soap|
send("build_#{action}", soap, options)
end
end
soap.target!
end
# Build generic tag.
def build_tag(soap, type, tag, value)
soap.tag!(tag, value, 'xsi:type' => "xsd:#{type}") if value != nil
end
# Build token.
def build_token(soap, options)
seed = SecureRandom.base64(32)
hash = Digest::SHA1.hexdigest("#{@options[:login]}#{seed}#{@options[:password].to_s.strip}")
soap.Token 'xsi:type' => 'ns1:ueSecurityToken' do |soap|
build_tag soap, :string, 'ClientIP', options[:client_ip]
soap.PinHash 'xsi:type' => 'ns1:ueHash' do |soap|
build_tag soap, :string, "HashValue", hash
build_tag soap, :string, "Seed", seed
build_tag soap, :string, "Type", 'sha1'
end
build_tag soap, :string, 'SourceKey', @options[:login]
end
end
# Customer ======================================================
def build_add_customer(soap, options)
soap.tag! "ns1:addCustomer" do |soap|
build_token soap, options
build_customer_data soap, options
build_tag soap, :double, 'Amount', amount(options[:amount])
build_tag soap, :double, 'Tax', amount(options[:tax])
build_tag soap, :string, 'Next', options[:next].strftime("%Y-%m-%d") if options[:next]
end
end
def build_customer(soap, options, type, add_customer_data=false)
soap.tag! "ns1:#{type}" do |soap|
build_token soap, options
build_tag soap, :integer, 'CustNum', options[:customer_number]
build_customer_data soap, options if add_customer_data
end
end
def build_update_customer(soap, options)
build_customer(soap, options, 'updateCustomer', true)
end
def build_enable_customer(soap, options)
build_customer(soap, options, 'enableCustomer')
end
def build_disable_customer(soap, options)
build_customer(soap, options, 'disableCustomer')
end
def build_delete_customer(soap, options)
build_customer(soap, options, 'deleteCustomer')
end
def build_add_customer_payment_method(soap, options)
soap.tag! "ns1:addCustomerPaymentMethod" do |soap|
build_token soap, options
build_tag soap, :integer, 'CustNum', options[:customer_number]
build_customer_payment_methods soap, options
build_tag soap, :boolean, 'MakeDefault', options[:make_default]
build_tag soap, :boolean, 'Verify', options[:verify]
end
end
def build_get_customer_payment_method(soap, options)
soap.tag! 'ns1:getCustomerPaymentMethod' do |soap|
build_token soap, options
build_tag soap, :integer, 'CustNum', options[:customer_number]
build_tag soap, :integer, 'MethodID', options[:method_id]
end
end
def build_get_customer_payment_methods(soap, options)
build_customer(soap, options, 'getCustomerPaymentMethods')
end
def build_update_customer_payment_method(soap, options)
soap.tag! 'ns1:updateCustomerPaymentMethod' do |soap|
build_token soap, options
build_customer_payment_methods soap, options
build_tag soap, :boolean, 'Verify', options[:verify]
end
end
def build_delete_customer_payment_method(soap, options)
soap.tag! "ns1:deleteCustomerPaymentMethod" do |soap|
build_token soap, options
build_tag soap, :integer, 'Custnum', options[:customer_number]
build_tag soap, :integer, 'PaymentMethodID', options[:method_id]
end
end
def build_run_customer_transaction(soap, options)
soap.tag! "ns1:runCustomerTransaction" do |soap|
build_token soap, options
build_tag soap, :integer, 'CustNum', options[:customer_number]
build_tag soap, :integer, 'PaymentMethodID', options[:method_id] || 0
build_customer_transaction soap, options
end
end
# Transactions ==================================================
def build_run_transaction(soap, options)
soap.tag! 'ns1:runTransaction' do |soap|
build_token soap, options
build_transaction_request_object soap, options, 'Parameters'
end
end
def build_run_sale(soap, options)
soap.tag! 'ns1:runSale' do |soap|
build_token soap, options
build_transaction_request_object soap, options
end
end
def build_run_auth_only(soap, options)
soap.tag! 'ns1:runAuthOnly' do |soap|
build_token soap, options
build_transaction_request_object soap, options
end
end
def build_run_credit(soap, options)
soap.tag! 'ns1:runCredit' do |soap|
build_token soap, options
build_transaction_request_object soap, options
end
end
def build_run_check_sale(soap, options)
soap.tag! 'ns1:runCheckSale' do |soap|
build_token soap, options
build_transaction_request_object soap, options
end
end
def build_run_check_credit(soap, options)
soap.tag! 'ns1:runCheckCredit' do |soap|
build_token soap, options
build_transaction_request_object soap, options
end
end
def build_post_auth(soap, options)
soap.tag! 'ns1:postAuth' do |soap|
build_token soap, options
build_transaction_request_object soap, options
end
end
def build_run_quick_sale(soap, options)
soap.tag! 'ns1:runQuickSale' do |soap|
build_token soap, options
build_tag soap, :integer, 'RefNum', options[:reference_number]
build_transaction_detail soap, options
build_tag soap, :boolean, 'AuthOnly', options[:authorize_only] || false
end
end
def build_run_quick_credit(soap, options)
soap.tag! 'ns1:runQuickCredit' do |soap|
build_token soap, options
build_tag soap, :integer, 'RefNum', options[:reference_number]
build_transaction_detail soap, options
end
end
def build_get_transaction(soap, options)
soap.tag! "ns1:getTransaction" do |soap|
build_token soap, options
build_tag soap, :integer, 'RefNum', options[:reference_number]
end
end
def build_get_transaction_status(soap, options)
soap.tag! "ns1:getTransactionStatus" do |soap|
build_token soap, options
build_tag soap, :integer, 'RefNum', options[:reference_number]
end
end
def build_get_transaction_custom(soap, options)
soap.tag! "ns1:getTransactionCustom" do |soap|
build_token soap, options
build_tag soap, :integer, 'RefNum', options[:reference_number]
build_transaction_field_array soap, options
end
end
def build_get_check_trace(soap, options)
soap.tag! "ns1:getCheckTrace" do |soap|
build_token soap, options
build_tag soap, :integer, 'RefNum', options[:reference_number]
end
end
def build_capture_transaction(soap, options)
soap.tag! "ns1:captureTransaction" do |soap|
build_token soap, options
build_tag soap, :integer, 'RefNum', options[:reference_number]
build_tag soap, :double, 'Amount', amount(options[:amount])
end
end
def build_void_transaction(soap, options)
soap.tag! "ns1:voidTransaction" do |soap|
build_token soap, options
build_tag soap, :integer, 'RefNum', options[:reference_number]
end
end
def build_refund_transaction(soap, options)
soap.tag! "ns1:refundTransaction" do |soap|
build_token soap, options
build_tag soap, :integer, 'RefNum', options[:reference_number]
build_tag soap, :integer, 'Amount', amount(options[:amount])
end
end
def build_override_transaction(soap, options)
soap.tag! "ns1:overrideTransaction" do |soap|
build_token soap, options
build_tag soap, :integer, 'RefNum', options[:reference_number]
build_tag soap, :string, 'Reason', options[:reason]
end
end
# Account =======================================================
def build_get_account_details(soap, options)
soap.tag! "ns1:getAccountDetails" do |soap|
build_token soap, options
end
end
# Customer Helpers ==============================================
def build_customer_data(soap, options)
soap.CustomerData 'xsi:type' => 'ns1:CustomerObject' do
CUSTOMER_OPTIONS.each do |k,v|
build_tag soap, v[0], v[1], options[k]
end
build_billing_address soap, options
build_customer_payments soap, options
build_custom_fields soap, options
end
end
def build_customer_payments(soap, options)
if options[:payment_methods]
length = options[:payment_methods].length
soap.PaymentMethods 'SOAP-ENC:arrayType' => "ns1:PaymentMethod[#{length}]",
'xsi:type' =>"ns1:PaymentMethodArray" do |soap|
build_customer_payment_methods soap, options
end
end
end
def extract_methods_and_tag(options)
case
when options[:payment_method] && !options[:payment_methods]
payment_methods = [options[:payment_method]]
tag_name = 'PaymentMethod'
when options[:payment_methods] && !options[:payment_method]
payment_methods = options[:payment_methods]
tag_name = 'item'
else
payment_methods = [options]
tag_name = 'PaymentMethod'
end
[payment_methods, tag_name]
end
def build_credit_card_or_check(soap, payment_method)
case
when payment_method[:method].kind_of?(ActiveMerchant::Billing::CreditCard)
build_tag soap, :string, 'CardNumber', payment_method[:method].number
build_tag soap, :string, 'CardExpiration',
"#{"%02d" % payment_method[:method].month}#{payment_method[:method].year.to_s[-2..-1]}"
if options[:billing_address]
build_tag soap, :string, 'AvsStreet', options[:billing_address][:address1]
build_tag soap, :string, 'AvsZip', options[:billing_address][:zip]
end
build_tag soap, :string, 'CardCode', payment_method[:method].verification_value
when payment_method[:method].kind_of?(ActiveMerchant::Billing::Check)
build_tag soap, :string, 'Account', payment_method[:method].account_number
build_tag soap, :string, 'Routing', payment_method[:method].routing_number
unless payment_method[:method].account_type.nil?
build_tag soap, :string, 'AccountType', payment_method[:method].account_type.capitalize
end
build_tag soap, :string, 'DriversLicense', options[:drivers_license]
build_tag soap, :string, 'DriversLicenseState', options[:drivers_license_state]
build_tag soap, :string, 'RecordType', options[:record_type]
end
end
def build_customer_payment_methods(soap, options)
payment_methods, tag_name = extract_methods_and_tag(options)
payment_methods.each do |payment_method|
soap.tag! tag_name, 'xsi:type' => "ns1:PaymentMethod" do |soap|
build_tag soap, :integer, 'MethodID', payment_method[:method_id]
build_tag soap, :string, 'MethodType', payment_method[:type]
build_tag soap, :string, 'MethodName', payment_method[:name]
build_tag soap, :integer, 'SecondarySort', payment_method[:sort]
build_credit_card_or_check(soap, payment_method)
end
end
end
def build_customer_transaction(soap, options)
soap.Parameters 'xsi:type' => "ns1:CustomerTransactionRequest" do |soap|
build_transaction_detail soap, options
CUSTOMER_TRANSACTION_REQUEST_OPTIONS.each do |k,v|
build_tag soap, v[0], v[1], options[k]
end
build_custom_fields soap, options
build_line_items soap, options
end
end
# Transaction Helpers ===========================================
def build_transaction_request_object(soap, options, name='Params')
soap.tag! name, 'xsi:type' => "ns1:TransactionRequestObject" do |soap|
TRANSACTION_REQUEST_OBJECT_OPTIONS.each do |k,v|
build_tag soap, v[0], v[1], options[k]
end
case
when options[:payment_method] == nil
when options[:payment_method].kind_of?(ActiveMerchant::Billing::CreditCard)
build_credit_card_data soap, options
when options[:payment_method].kind_of?(ActiveMerchant::Billing::Check)
build_check_data soap, options
else
raise ArgumentError, 'options[:payment_method] must be a CreditCard or Check'
end
build_transaction_detail soap, options
build_billing_address soap, options
build_shipping_address soap, options
build_recurring_billing soap, options
build_line_items soap, options
build_custom_fields soap, options
end
end
def build_transaction_detail(soap, options)
soap.Details 'xsi:type' => "ns1:TransactionDetail" do |soap|
TRANSACTION_DETAIL_OPTIONS.each do |k,v|
build_tag soap, v[0], v[1], options[k]
end
TRANSACTION_DETAIL_MONEY_OPTIONS.each do |k,v|
build_tag soap, v[0], v[1], amount(options[k])
end
end
end
def build_credit_card_data(soap, options)
soap.CreditCardData 'xsi:type' => "ns1:CreditCardData" do |soap|
build_tag soap, :string, 'CardNumber', options[:payment_method].number
build_tag soap, :string, 'CardExpiration', build_card_expiration(options)
if options[:billing_address]
build_tag soap, :string, 'AvsStreet', options[:billing_address][:address1]
build_tag soap, :string, 'AvsZip', options[:billing_address][:zip]
end
build_tag soap, :string, 'CardCode', options[:payment_method].verification_value
build_tag soap, :boolean, 'CardPresent', options[:card_present] || false
CREDIT_CARD_DATA_OPTIONS.each do |k,v|
build_tag soap, v[0], v[1], options[k]
end
end
end
def build_card_expiration(options)
month = options[:payment_method].month
year = options[:payment_method].year
unless month.nil? || year.nil?
"#{"%02d" % month}#{year.to_s[-2..-1]}"
end
end
def build_check_data(soap, options)
soap.CheckData 'xsi:type' => "ns1:CheckData" do |soap|
build_tag soap, :integer, 'CheckNumber', options[:payment_method].number
build_tag soap, :string, 'Account', options[:payment_method].account_number
build_tag soap, :string, 'Routing', options[:payment_method].routing_number
build_tag soap, :string, 'AccountType', options[:payment_method].account_type.capitalize
CHECK_DATA_OPTIONS.each do |k,v|
build_tag soap, v[0], v[1], options[k]
end
end
end
def build_recurring_billing(soap, options)
if options[:recurring]
soap.RecurringBilling 'xsi:type' => "ns1:RecurringBilling" do |soap|
build_tag soap, :double, 'Amount', amount(options[:recurring][:amount])
build_tag soap, :string, 'Next', options[:recurring][:next].strftime("%Y-%m-%d") if options[:recurring][:next]
build_tag soap, :string, 'Expire', options[:recurring][:expire].strftime("%Y-%m-%d") if options[:recurring][:expire]
RECURRING_BILLING_OPTIONS.each do |k,v|
build_tag soap, v[0], v[1], options[:recurring][k]
end
end
end
end
def build_transaction_field_array(soap, options)
soap.Fields 'SOAP-ENC:arryType' => "xsd:string[#{options[:fields].length}]", 'xsi:type' => 'ns1:stringArray' do |soap|
options[:fields].each do |field|
build_tag soap, :string, 'item', field
end
end
end
# General Helpers ===============================================
def build_billing_address(soap, options)
if options[:billing_address]
if options[:billing_address][:name]
name = options[:billing_address][:name].split(nil,2) # divide name
options[:billing_address][:first_name], options[:billing_address][:last_name] = name[0], name[1]
end
soap.BillingAddress 'xsi:type' => "ns1:Address" do
ADDRESS_OPTIONS.each do |k,v|
build_tag soap, v[0], v[1], options[:billing_address][k]
end
end
end
end
def build_shipping_address(soap, options)
if options[:shipping_address]
if options[:shipping_address][:name]
name = options[:shipping_address][:name].split(nil,2) # divide name
options[:shipping_address][:first_name], options[:shipping_address][:last_name] = name[0], name[1]
end
soap.ShippingAddress 'xsi:type' => "ns1:Address" do
ADDRESS_OPTIONS.each do |k,v|
build_tag soap, v[0], v[1], options[:shipping_address][k]
end
end
end
end
def build_line_items(soap, options) # TODO
end
def build_custom_fields(soap, options) # TODO
end
# Request =======================================================
def commit(action, request)
url = test? ? test_url : live_url
begin
soap = ssl_post(url, request, "Content-Type" => "text/xml")
rescue ActiveMerchant::ResponseError => error
soap = error.response.body
end
response = build_response(action, soap)
end
def build_response(action, soap)
response_params, success, message, authorization, avs, cvv = parse(action, soap)
response_params.merge!('soap_response' => soap) if @options[:soap_response]
response = Response.new(
success, message, response_params,
:test => test?, :authorization => authorization,
:avs_result => avs_from(avs),
:cvv_result => cvv
)
end
def avs_from(avs)
avs_params = { :code => avs }
avs_params.merge!(:message => AVS_CUSTOM_MESSAGES[avs]) if AVS_CUSTOM_MESSAGES.key?(avs)
avs_params
end
def parse(action, soap)
xml = REXML::Document.new(soap)
root = REXML::XPath.first(xml, "//SOAP-ENV:Body")
response = root ? parse_element(root[0]) : { :response => soap }
success, message, authorization, avs, cvv = false, FAILURE_MESSAGE, nil, nil, nil
fault = (!response) || (response.length < 1) || response.has_key?('faultcode')
return [response, success, response['faultstring'], authorization, avs, cvv] if fault
if response.respond_to?(:[]) && p = response["#{action}_return"]
if p.respond_to?(:key?) && p.key?('result_code')
success = p['result_code'] == 'A' ? true : false
authorization = p['ref_num']
avs = AVS_RESULTS[p['avs_result_code']]
cvv = p['card_code_result_code']
else
success = true
end
message = case action
when :get_customer_payment_methods
p['item']
when :get_transaction_custom
items = p['item'].kind_of?(Array) ? p['item'] : [p['item']]
items.inject({}) { |hash, item| hash[item['field']] = item['value']; hash }
else
p
end
elsif response.respond_to?(:[]) && p = response[:response]
message = p # when response is html
end
[response, success, message, authorization, avs, cvv]
end
def parse_element(node)
if node.has_elements?
response = {}
node.elements.each do |e|
key = e.name.underscore
value = parse_element(e)
if response.has_key?(key)
if response[key].is_a?(Array)
response[key].push(value)
else
response[key] = [response[key], value]
end
else
response[key] = parse_element(e)
end
end
else
response = node.text
end
response
end
end
end
end